From 38163af068810b388f6723a681dfd8c7b3680d38 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Tue, 14 Oct 2025 23:54:58 +0000 Subject: bpf: Introduce SK_BPF_BYPASS_PROT_MEM. If a socket has sk->sk_bypass_prot_mem flagged, the socket opts out of the global protocol memory accounting. This is easily controlled by net.core.bypass_prot_mem sysctl, but it lacks flexibility. Let's support flagging (and clearing) sk->sk_bypass_prot_mem via bpf_setsockopt() at the BPF_CGROUP_INET_SOCK_CREATE hook. int val = 1; bpf_setsockopt(ctx, SOL_SOCKET, SK_BPF_BYPASS_PROT_MEM, &val, sizeof(val)); As with net.core.bypass_prot_mem, this is inherited to child sockets, and BPF always takes precedence over sysctl at socket(2) and accept(2). SK_BPF_BYPASS_PROT_MEM is only supported at BPF_CGROUP_INET_SOCK_CREATE and not supported on other hooks for some reasons: 1. UDP charges memory under sk->sk_receive_queue.lock instead of lock_sock() 2. Modifying the flag after skb is charged to sk requires such adjustment during bpf_setsockopt() and complicates the logic unnecessarily We can support other hooks later if a real use case justifies that. Most changes are inline and hard to trace, but a microbenchmark on __sk_mem_raise_allocated() during neper/tcp_stream showed that more samples completed faster with sk->sk_bypass_prot_mem == 1. This will be more visible under tcp_mem pressure (but it's not a fair comparison). # bpftrace -e 'kprobe:__sk_mem_raise_allocated { @start[tid] = nsecs; } kretprobe:__sk_mem_raise_allocated /@start[tid]/ { @end[tid] = nsecs - @start[tid]; @times = hist(@end[tid]); delete(@start[tid]); }' # tcp_stream -6 -F 1000 -N -T 256 Without bpf prog: [128, 256) 3846 | | [256, 512) 1505326 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| [512, 1K) 1371006 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ | [1K, 2K) 198207 |@@@@@@ | [2K, 4K) 31199 |@ | With bpf prog in the next patch: (must be attached before tcp_stream) # bpftool prog load sk_bypass_prot_mem.bpf.o /sys/fs/bpf/test type cgroup/sock_create # bpftool cgroup attach /sys/fs/cgroup/test cgroup_inet_sock_create pinned /sys/fs/bpf/test [128, 256) 6413 | | [256, 512) 1868425 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| [512, 1K) 1101697 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ | [1K, 2K) 117031 |@@@@ | [2K, 4K) 11773 | | Signed-off-by: Kuniyuki Iwashima Signed-off-by: Martin KaFai Lau Acked-by: Roman Gushchin Link: https://patch.msgid.link/20251014235604.3057003-6-kuniyu@google.com --- include/uapi/linux/bpf.h | 2 ++ 1 file changed, 2 insertions(+) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index 6829936d33f5..6eb75ad900b1 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -7200,6 +7200,8 @@ enum { TCP_BPF_SYN_MAC = 1007, /* Copy the MAC, IP[46], and TCP header */ TCP_BPF_SOCK_OPS_CB_FLAGS = 1008, /* Get or Set TCP sock ops flags */ SK_BPF_CB_FLAGS = 1009, /* Get or set sock ops flags in socket */ + SK_BPF_BYPASS_PROT_MEM = 1010, /* Get or Set sk->sk_bypass_prot_mem */ + }; enum { -- cgit v1.2.3 From a63ca4236e6799cf4343f9aec9d92afdfa582446 Mon Sep 17 00:00:00 2001 From: Ackerley Tng Date: Thu, 16 Oct 2025 10:28:44 -0700 Subject: KVM: guest_memfd: Use guest mem inodes instead of anonymous inodes guest_memfd's inode represents memory the guest_memfd is providing. guest_memfd's file represents a struct kvm's view of that memory. Using a custom inode allows customization of the inode teardown process via callbacks. For example, ->evict_inode() allows customization of the truncation process on file close, and ->destroy_inode() and ->free_inode() allow customization of the inode freeing process. Customizing the truncation process allows flexibility in management of guest_memfd memory and customization of the inode freeing process allows proper cleanup of memory metadata stored on the inode. Memory metadata is more appropriately stored on the inode (as opposed to the file), since the metadata is for the memory and is not unique to a specific binding and struct kvm. Acked-by: David Hildenbrand Co-developed-by: Fuad Tabba Signed-off-by: Fuad Tabba Signed-off-by: Ackerley Tng Signed-off-by: Shivank Garg Tested-by: Ashish Kalra [sean: drop helpers, open code logic in __kvm_gmem_create()] Link: https://lore.kernel.org/r/20251016172853.52451-4-seanjc@google.com Signed-off-by: Sean Christopherson --- include/uapi/linux/magic.h | 1 + virt/kvm/guest_memfd.c | 82 ++++++++++++++++++++++++++++++++++++++-------- virt/kvm/kvm_main.c | 7 +++- virt/kvm/kvm_mm.h | 9 ++--- 4 files changed, 80 insertions(+), 19 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/magic.h b/include/uapi/linux/magic.h index bb575f3ab45e..638ca21b7a90 100644 --- a/include/uapi/linux/magic.h +++ b/include/uapi/linux/magic.h @@ -103,5 +103,6 @@ #define DEVMEM_MAGIC 0x454d444d /* "DMEM" */ #define SECRETMEM_MAGIC 0x5345434d /* "SECM" */ #define PID_FS_MAGIC 0x50494446 /* "PIDF" */ +#define GUEST_MEMFD_MAGIC 0x474d454d /* "GMEM" */ #endif /* __LINUX_MAGIC_H__ */ diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c index 5cce20ff418d..ce04fc85e631 100644 --- a/virt/kvm/guest_memfd.c +++ b/virt/kvm/guest_memfd.c @@ -1,12 +1,16 @@ // SPDX-License-Identifier: GPL-2.0 +#include #include #include +#include #include +#include #include -#include #include "kvm_mm.h" +static struct vfsmount *kvm_gmem_mnt; + /* * A guest_memfd instance can be associated multiple VMs, each with its own * "view" of the underlying physical memory. @@ -424,11 +428,6 @@ static struct file_operations kvm_gmem_fops = { .fallocate = kvm_gmem_fallocate, }; -void kvm_gmem_init(struct module *module) -{ - kvm_gmem_fops.owner = module; -} - static int kvm_gmem_migrate_folio(struct address_space *mapping, struct folio *dst, struct folio *src, enum migrate_mode mode) @@ -500,7 +499,7 @@ bool __weak kvm_arch_supports_gmem_init_shared(struct kvm *kvm) static int __kvm_gmem_create(struct kvm *kvm, loff_t size, u64 flags) { - const char *anon_name = "[kvm-gmem]"; + static const char *name = "[kvm-gmem]"; struct gmem_file *f; struct inode *inode; struct file *file; @@ -516,16 +515,17 @@ static int __kvm_gmem_create(struct kvm *kvm, loff_t size, u64 flags) goto err_fd; } - file = anon_inode_create_getfile(anon_name, &kvm_gmem_fops, f, O_RDWR, NULL); - if (IS_ERR(file)) { - err = PTR_ERR(file); + /* __fput() will take care of fops_put(). */ + if (!fops_get(&kvm_gmem_fops)) { + err = -ENOENT; goto err_gmem; } - file->f_flags |= O_LARGEFILE; - - inode = file->f_inode; - WARN_ON(file->f_mapping != inode->i_mapping); + inode = anon_inode_make_secure_inode(kvm_gmem_mnt->mnt_sb, name, NULL); + if (IS_ERR(inode)) { + err = PTR_ERR(inode); + goto err_fops; + } inode->i_private = (void *)(unsigned long)flags; inode->i_op = &kvm_gmem_iops; @@ -537,6 +537,15 @@ static int __kvm_gmem_create(struct kvm *kvm, loff_t size, u64 flags) /* Unmovable mappings are supposed to be marked unevictable as well. */ WARN_ON_ONCE(!mapping_unevictable(inode->i_mapping)); + file = alloc_file_pseudo(inode, kvm_gmem_mnt, name, O_RDWR, &kvm_gmem_fops); + if (IS_ERR(file)) { + err = PTR_ERR(file); + goto err_inode; + } + + file->f_flags |= O_LARGEFILE; + file->private_data = f; + kvm_get_kvm(kvm); f->kvm = kvm; xa_init(&f->bindings); @@ -545,6 +554,10 @@ static int __kvm_gmem_create(struct kvm *kvm, loff_t size, u64 flags) fd_install(fd, file); return fd; +err_inode: + iput(inode); +err_fops: + fops_put(&kvm_gmem_fops); err_gmem: kfree(f); err_fd: @@ -816,3 +829,44 @@ put_folio_and_exit: } EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_gmem_populate); #endif + +static int kvm_gmem_init_fs_context(struct fs_context *fc) +{ + if (!init_pseudo(fc, GUEST_MEMFD_MAGIC)) + return -ENOMEM; + + fc->s_iflags |= SB_I_NOEXEC; + fc->s_iflags |= SB_I_NODEV; + + return 0; +} + +static struct file_system_type kvm_gmem_fs = { + .name = "guest_memfd", + .init_fs_context = kvm_gmem_init_fs_context, + .kill_sb = kill_anon_super, +}; + +static int kvm_gmem_init_mount(void) +{ + kvm_gmem_mnt = kern_mount(&kvm_gmem_fs); + + if (IS_ERR(kvm_gmem_mnt)) + return PTR_ERR(kvm_gmem_mnt); + + kvm_gmem_mnt->mnt_flags |= MNT_NOEXEC; + return 0; +} + +int kvm_gmem_init(struct module *module) +{ + kvm_gmem_fops.owner = module; + + return kvm_gmem_init_mount(); +} + +void kvm_gmem_exit(void) +{ + kern_unmount(kvm_gmem_mnt); + kvm_gmem_mnt = NULL; +} diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c index b7a0ae2a7b20..4845e5739436 100644 --- a/virt/kvm/kvm_main.c +++ b/virt/kvm/kvm_main.c @@ -6517,7 +6517,9 @@ int kvm_init(unsigned vcpu_size, unsigned vcpu_align, struct module *module) if (WARN_ON_ONCE(r)) goto err_vfio; - kvm_gmem_init(module); + r = kvm_gmem_init(module); + if (r) + goto err_gmem; r = kvm_init_virtualization(); if (r) @@ -6538,6 +6540,8 @@ int kvm_init(unsigned vcpu_size, unsigned vcpu_align, struct module *module) err_register: kvm_uninit_virtualization(); err_virt: + kvm_gmem_exit(); +err_gmem: kvm_vfio_ops_exit(); err_vfio: kvm_async_pf_deinit(); @@ -6569,6 +6573,7 @@ void kvm_exit(void) for_each_possible_cpu(cpu) free_cpumask_var(per_cpu(cpu_kick_mask, cpu)); kmem_cache_destroy(kvm_vcpu_cache); + kvm_gmem_exit(); kvm_vfio_ops_exit(); kvm_async_pf_deinit(); kvm_irqfd_exit(); diff --git a/virt/kvm/kvm_mm.h b/virt/kvm/kvm_mm.h index 31defb08ccba..9fcc5d5b7f8d 100644 --- a/virt/kvm/kvm_mm.h +++ b/virt/kvm/kvm_mm.h @@ -68,17 +68,18 @@ static inline void gfn_to_pfn_cache_invalidate_start(struct kvm *kvm, #endif /* HAVE_KVM_PFNCACHE */ #ifdef CONFIG_KVM_GUEST_MEMFD -void kvm_gmem_init(struct module *module); +int kvm_gmem_init(struct module *module); +void kvm_gmem_exit(void); int kvm_gmem_create(struct kvm *kvm, struct kvm_create_guest_memfd *args); int kvm_gmem_bind(struct kvm *kvm, struct kvm_memory_slot *slot, unsigned int fd, loff_t offset); void kvm_gmem_unbind(struct kvm_memory_slot *slot); #else -static inline void kvm_gmem_init(struct module *module) +static inline int kvm_gmem_init(struct module *module) { - + return 0; } - +static inline void kvm_gmem_exit(void) {}; static inline int kvm_gmem_bind(struct kvm *kvm, struct kvm_memory_slot *slot, unsigned int fd, loff_t offset) -- cgit v1.2.3 From ca4709843b7e72f96976cd6b35bca148a4071673 Mon Sep 17 00:00:00 2001 From: David Yang Date: Fri, 17 Oct 2025 14:08:54 +0800 Subject: net: dsa: tag_yt921x: add support for Motorcomm YT921x tags Add support for Motorcomm YT921x tags, which includes a proper configurable ethertype field (default to 0x9988). Signed-off-by: David Yang Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20251017060859.326450-3-mmyangfl@gmail.com Signed-off-by: Jakub Kicinski --- include/net/dsa.h | 2 + include/uapi/linux/if_ether.h | 1 + net/dsa/Kconfig | 6 ++ net/dsa/Makefile | 1 + net/dsa/tag_yt921x.c | 141 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 151 insertions(+) create mode 100644 net/dsa/tag_yt921x.c (limited to 'include/uapi/linux') diff --git a/include/net/dsa.h b/include/net/dsa.h index d73ea0880066..67762fdaf3c7 100644 --- a/include/net/dsa.h +++ b/include/net/dsa.h @@ -55,6 +55,7 @@ struct tc_action; #define DSA_TAG_PROTO_LAN937X_VALUE 27 #define DSA_TAG_PROTO_VSC73XX_8021Q_VALUE 28 #define DSA_TAG_PROTO_BRCM_LEGACY_FCS_VALUE 29 +#define DSA_TAG_PROTO_YT921X_VALUE 30 enum dsa_tag_protocol { DSA_TAG_PROTO_NONE = DSA_TAG_PROTO_NONE_VALUE, @@ -87,6 +88,7 @@ enum dsa_tag_protocol { DSA_TAG_PROTO_RZN1_A5PSW = DSA_TAG_PROTO_RZN1_A5PSW_VALUE, DSA_TAG_PROTO_LAN937X = DSA_TAG_PROTO_LAN937X_VALUE, DSA_TAG_PROTO_VSC73XX_8021Q = DSA_TAG_PROTO_VSC73XX_8021Q_VALUE, + DSA_TAG_PROTO_YT921X = DSA_TAG_PROTO_YT921X_VALUE, }; struct dsa_switch; diff --git a/include/uapi/linux/if_ether.h b/include/uapi/linux/if_ether.h index 69e0457eb200..cfd200c87e5e 100644 --- a/include/uapi/linux/if_ether.h +++ b/include/uapi/linux/if_ether.h @@ -114,6 +114,7 @@ #define ETH_P_QINQ1 0x9100 /* deprecated QinQ VLAN [ NOT AN OFFICIALLY REGISTERED ID ] */ #define ETH_P_QINQ2 0x9200 /* deprecated QinQ VLAN [ NOT AN OFFICIALLY REGISTERED ID ] */ #define ETH_P_QINQ3 0x9300 /* deprecated QinQ VLAN [ NOT AN OFFICIALLY REGISTERED ID ] */ +#define ETH_P_YT921X 0x9988 /* Motorcomm YT921x DSA [ NOT AN OFFICIALLY REGISTERED ID ] */ #define ETH_P_EDSA 0xDADA /* Ethertype DSA [ NOT AN OFFICIALLY REGISTERED ID ] */ #define ETH_P_DSA_8021Q 0xDADB /* Fake VLAN Header for DSA [ NOT AN OFFICIALLY REGISTERED ID ] */ #define ETH_P_DSA_A5PSW 0xE001 /* A5PSW Tag Value [ NOT AN OFFICIALLY REGISTERED ID ] */ diff --git a/net/dsa/Kconfig b/net/dsa/Kconfig index 869cbe57162f..6b94028b1fcc 100644 --- a/net/dsa/Kconfig +++ b/net/dsa/Kconfig @@ -190,4 +190,10 @@ config NET_DSA_TAG_XRS700X Say Y or M if you want to enable support for tagging frames for Arrow SpeedChips XRS700x switches that use a single byte tag trailer. +config NET_DSA_TAG_YT921X + tristate "Tag driver for Motorcomm YT921x switches" + help + Say Y or M if you want to enable support for tagging frames for + Motorcomm YT921x switches. + endif diff --git a/net/dsa/Makefile b/net/dsa/Makefile index 555c07cfeb71..4b011a1d5c87 100644 --- a/net/dsa/Makefile +++ b/net/dsa/Makefile @@ -39,6 +39,7 @@ obj-$(CONFIG_NET_DSA_TAG_SJA1105) += tag_sja1105.o obj-$(CONFIG_NET_DSA_TAG_TRAILER) += tag_trailer.o obj-$(CONFIG_NET_DSA_TAG_VSC73XX_8021Q) += tag_vsc73xx_8021q.o obj-$(CONFIG_NET_DSA_TAG_XRS700X) += tag_xrs700x.o +obj-$(CONFIG_NET_DSA_TAG_YT921X) += tag_yt921x.o # for tracing framework to find trace.h CFLAGS_trace.o := -I$(src) diff --git a/net/dsa/tag_yt921x.c b/net/dsa/tag_yt921x.c new file mode 100644 index 000000000000..995da44f0a2a --- /dev/null +++ b/net/dsa/tag_yt921x.c @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Motorcomm YT921x Switch Extended CPU Port Tagging + * + * Copyright (c) 2025 David Yang + * + * +----+----+-------+-----+----+--------- + * | DA | SA | TagET | Tag | ET | Payload ... + * +----+----+-------+-----+----+--------- + * 6 6 2 6 2 N + * + * Tag Ethertype: CPU_TAG_TPID_TPID (default: ETH_P_YT921X = 0x9988) + * * Hardcoded for the moment, but still configurable. Discuss it if there + * are conflicts somewhere and/or you want to change it for some reason. + * Tag: + * 2: VLAN Tag + * 2: Rx Port + * 15b: Rx Port Valid + * 14b-11b: Rx Port + * 10b-0b: Cmd? + * 2: Tx Port(s) + * 15b: Tx Port(s) Valid + * 10b-0b: Tx Port(s) Mask + */ + +#include + +#include "tag.h" + +#define YT921X_TAG_NAME "yt921x" + +#define YT921X_TAG_LEN 8 + +#define YT921X_TAG_PORT_EN BIT(15) +#define YT921X_TAG_RX_PORT_M GENMASK(14, 11) +#define YT921X_TAG_RX_CMD_M GENMASK(10, 0) +#define YT921X_TAG_RX_CMD(x) FIELD_PREP(YT921X_TAG_RX_CMD_M, (x)) +#define YT921X_TAG_RX_CMD_FORWARDED 0x80 +#define YT921X_TAG_RX_CMD_UNK_UCAST 0xb2 +#define YT921X_TAG_RX_CMD_UNK_MCAST 0xb4 +#define YT921X_TAG_TX_PORTS_M GENMASK(10, 0) +#define YT921X_TAG_TX_PORTn(port) BIT(port) + +static struct sk_buff * +yt921x_tag_xmit(struct sk_buff *skb, struct net_device *netdev) +{ + struct dsa_port *dp = dsa_user_to_port(netdev); + unsigned int port = dp->index; + __be16 *tag; + u16 tx; + + skb_push(skb, YT921X_TAG_LEN); + dsa_alloc_etype_header(skb, YT921X_TAG_LEN); + + tag = dsa_etype_header_pos_tx(skb); + + tag[0] = htons(ETH_P_YT921X); + /* VLAN tag unrelated when TX */ + tag[1] = 0; + tag[2] = 0; + tx = YT921X_TAG_PORT_EN | YT921X_TAG_TX_PORTn(port); + tag[3] = htons(tx); + + return skb; +} + +static struct sk_buff * +yt921x_tag_rcv(struct sk_buff *skb, struct net_device *netdev) +{ + unsigned int port; + __be16 *tag; + u16 cmd; + u16 rx; + + if (unlikely(!pskb_may_pull(skb, YT921X_TAG_LEN))) + return NULL; + + tag = dsa_etype_header_pos_rx(skb); + + if (unlikely(tag[0] != htons(ETH_P_YT921X))) { + dev_warn_ratelimited(&netdev->dev, + "Unexpected EtherType 0x%04x\n", + ntohs(tag[0])); + return NULL; + } + + /* Locate which port this is coming from */ + rx = ntohs(tag[2]); + if (unlikely((rx & YT921X_TAG_PORT_EN) == 0)) { + dev_warn_ratelimited(&netdev->dev, + "Unexpected rx tag 0x%04x\n", rx); + return NULL; + } + + port = FIELD_GET(YT921X_TAG_RX_PORT_M, rx); + skb->dev = dsa_conduit_find_user(netdev, 0, port); + if (unlikely(!skb->dev)) { + dev_warn_ratelimited(&netdev->dev, + "Couldn't decode source port %u\n", port); + return NULL; + } + + cmd = FIELD_GET(YT921X_TAG_RX_CMD_M, rx); + switch (cmd) { + case YT921X_TAG_RX_CMD_FORWARDED: + /* Already forwarded by hardware */ + dsa_default_offload_fwd_mark(skb); + break; + case YT921X_TAG_RX_CMD_UNK_UCAST: + case YT921X_TAG_RX_CMD_UNK_MCAST: + /* NOTE: hardware doesn't distinguish between TRAP (copy to CPU + * only) and COPY (forward and copy to CPU). In order to perform + * a soft switch, NEVER use COPY action in the switch driver. + */ + break; + default: + dev_warn_ratelimited(&netdev->dev, + "Unexpected rx cmd 0x%02x\n", cmd); + break; + } + + /* Remove YT921x tag and update checksum */ + skb_pull_rcsum(skb, YT921X_TAG_LEN); + dsa_strip_etype_header(skb, YT921X_TAG_LEN); + + return skb; +} + +static const struct dsa_device_ops yt921x_netdev_ops = { + .name = YT921X_TAG_NAME, + .proto = DSA_TAG_PROTO_YT921X, + .xmit = yt921x_tag_xmit, + .rcv = yt921x_tag_rcv, + .needed_headroom = YT921X_TAG_LEN, +}; + +MODULE_DESCRIPTION("DSA tag driver for Motorcomm YT921x switches"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_YT921X, YT921X_TAG_NAME); + +module_dsa_tag_driver(yt921x_netdev_ops); -- cgit v1.2.3 From 1cba30bf9fdd6c982708f3587f609a30c370d889 Mon Sep 17 00:00:00 2001 From: Keith Busch Date: Thu, 16 Oct 2025 11:09:38 -0700 Subject: io_uring: add support for IORING_SETUP_SQE_MIXED Normal rings support 64b SQEs for posting submissions, while certain features require the ring to be configured with IORING_SETUP_SQE128, as they need to convey more information per submission. This, in turn, makes ALL the SQEs be 128b in size. This is somewhat wasteful and inefficient, particularly when only certain SQEs need to be of the bigger variant. This adds support for setting up a ring with mixed SQE sizes, using IORING_SETUP_SQE_MIXED. When setup in this mode, SQEs posted to the ring may be either 64b or 128b in size. If a SQE is 128b in size, then opcode will be set to a variante to indicate that this is the case. Any other non-128b opcode will assume the SQ's default size. SQEs on these types of mixed rings may also utilize NOP with skip success set. This can happen if the ring is one (small) SQE entry away from wrapping, and an attempt is made to get a 128b SQE. As SQEs must be contiguous in the SQ ring, a 128b SQE cannot wrap the ring. For this case, a single NOP SQE should be inserted with the SKIP_SUCCESS flag set. The kernel will process this as a normal NOP and without posting a CQE. Signed-off-by: Keith Busch [axboe: {} style fix and assign sqe before opcode read] Signed-off-by: Jens Axboe --- include/uapi/linux/io_uring.h | 8 ++++++++ io_uring/fdinfo.c | 34 +++++++++++++++++++++++++++------- io_uring/io_uring.c | 37 +++++++++++++++++++++++++++++++++---- io_uring/io_uring.h | 14 ++------------ io_uring/opdef.c | 26 ++++++++++++++++++++++++++ io_uring/opdef.h | 2 ++ io_uring/register.c | 2 +- io_uring/uring_cmd.c | 17 +++++++++++++++-- 8 files changed, 114 insertions(+), 26 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/io_uring.h b/include/uapi/linux/io_uring.h index 263bed13473e..04797a9b76bc 100644 --- a/include/uapi/linux/io_uring.h +++ b/include/uapi/linux/io_uring.h @@ -231,6 +231,12 @@ enum io_uring_sqe_flags_bit { */ #define IORING_SETUP_CQE_MIXED (1U << 18) +/* + * Allow both 64b and 128b SQEs. If a 128b SQE is posted, it will have + * a 128b opcode. + */ +#define IORING_SETUP_SQE_MIXED (1U << 19) + enum io_uring_op { IORING_OP_NOP, IORING_OP_READV, @@ -295,6 +301,8 @@ enum io_uring_op { IORING_OP_READV_FIXED, IORING_OP_WRITEV_FIXED, IORING_OP_PIPE, + IORING_OP_NOP128, + IORING_OP_URING_CMD128, /* this goes last, obviously */ IORING_OP_LAST, diff --git a/io_uring/fdinfo.c b/io_uring/fdinfo.c index ff3364531c77..1a806ad16840 100644 --- a/io_uring/fdinfo.c +++ b/io_uring/fdinfo.c @@ -14,6 +14,7 @@ #include "fdinfo.h" #include "cancel.h" #include "rsrc.h" +#include "opdef.h" #ifdef CONFIG_NET_RX_BUSY_POLL static __cold void common_tracking_show_fdinfo(struct io_ring_ctx *ctx, @@ -66,7 +67,6 @@ static void __io_uring_show_fdinfo(struct io_ring_ctx *ctx, struct seq_file *m) unsigned int cq_head = READ_ONCE(r->cq.head); unsigned int cq_tail = READ_ONCE(r->cq.tail); unsigned int sq_shift = 0; - unsigned int sq_entries; int sq_pid = -1, sq_cpu = -1; u64 sq_total_time = 0, sq_work_time = 0; unsigned int i; @@ -89,26 +89,45 @@ static void __io_uring_show_fdinfo(struct io_ring_ctx *ctx, struct seq_file *m) seq_printf(m, "CqTail:\t%u\n", cq_tail); seq_printf(m, "CachedCqTail:\t%u\n", data_race(ctx->cached_cq_tail)); seq_printf(m, "SQEs:\t%u\n", sq_tail - sq_head); - sq_entries = min(sq_tail - sq_head, ctx->sq_entries); - for (i = 0; i < sq_entries; i++) { - unsigned int entry = i + sq_head; + while (sq_head < sq_tail) { struct io_uring_sqe *sqe; unsigned int sq_idx; + bool sqe128 = false; + u8 opcode; if (ctx->flags & IORING_SETUP_NO_SQARRAY) break; - sq_idx = READ_ONCE(ctx->sq_array[entry & sq_mask]); + sq_idx = READ_ONCE(ctx->sq_array[sq_head & sq_mask]); if (sq_idx > sq_mask) continue; + sqe = &ctx->sq_sqes[sq_idx << sq_shift]; + opcode = READ_ONCE(sqe->opcode); + if (sq_shift) { + sqe128 = true; + } else if (io_issue_defs[opcode].is_128) { + if (!(ctx->flags & IORING_SETUP_SQE_MIXED)) { + seq_printf(m, + "%5u: invalid sqe, 128B entry on non-mixed sq\n", + sq_idx); + break; + } + if ((++sq_head & sq_mask) == 0) { + seq_printf(m, + "%5u: corrupted sqe, wrapping 128B entry\n", + sq_idx); + break; + } + sqe128 = true; + } seq_printf(m, "%5u: opcode:%s, fd:%d, flags:%x, off:%llu, " "addr:0x%llx, rw_flags:0x%x, buf_index:%d " "user_data:%llu", - sq_idx, io_uring_get_opcode(sqe->opcode), sqe->fd, + sq_idx, io_uring_get_opcode(opcode), sqe->fd, sqe->flags, (unsigned long long) sqe->off, (unsigned long long) sqe->addr, sqe->rw_flags, sqe->buf_index, sqe->user_data); - if (sq_shift) { + if (sqe128) { u64 *sqeb = (void *) (sqe + 1); int size = sizeof(struct io_uring_sqe) / sizeof(u64); int j; @@ -120,6 +139,7 @@ static void __io_uring_show_fdinfo(struct io_ring_ctx *ctx, struct seq_file *m) } } seq_printf(m, "\n"); + sq_head++; } seq_printf(m, "CQEs:\t%u\n", cq_tail - cq_head); while (cq_head < cq_tail) { diff --git a/io_uring/io_uring.c b/io_uring/io_uring.c index e4ede0bad36f..be44d636fe1f 100644 --- a/io_uring/io_uring.c +++ b/io_uring/io_uring.c @@ -2164,7 +2164,7 @@ static __cold int io_init_fail_req(struct io_kiocb *req, int err) } static int io_init_req(struct io_ring_ctx *ctx, struct io_kiocb *req, - const struct io_uring_sqe *sqe) + const struct io_uring_sqe *sqe, unsigned int *left) __must_hold(&ctx->uring_lock) { const struct io_issue_def *def; @@ -2190,6 +2190,24 @@ static int io_init_req(struct io_ring_ctx *ctx, struct io_kiocb *req, opcode = array_index_nospec(opcode, IORING_OP_LAST); def = &io_issue_defs[opcode]; + if (def->is_128 && !(ctx->flags & IORING_SETUP_SQE128)) { + /* + * A 128b op on a non-128b SQ requires mixed SQE support as + * well as 2 contiguous entries. + */ + if (!(ctx->flags & IORING_SETUP_SQE_MIXED) || *left < 2 || + !(ctx->cached_sq_head & (ctx->sq_entries - 1))) + return io_init_fail_req(req, -EINVAL); + /* + * A 128b operation on a mixed SQ uses two entries, so we have + * to increment the head and cached refs, and decrement what's + * left. + */ + current->io_uring->cached_refs++; + ctx->cached_sq_head++; + (*left)--; + } + if (unlikely(sqe_flags & ~SQE_COMMON_FLAGS)) { /* enforce forwards compatibility on users */ if (sqe_flags & ~SQE_VALID_FLAGS) @@ -2299,13 +2317,13 @@ static __cold int io_submit_fail_init(const struct io_uring_sqe *sqe, } static inline int io_submit_sqe(struct io_ring_ctx *ctx, struct io_kiocb *req, - const struct io_uring_sqe *sqe) + const struct io_uring_sqe *sqe, unsigned int *left) __must_hold(&ctx->uring_lock) { struct io_submit_link *link = &ctx->submit_state.link; int ret; - ret = io_init_req(ctx, req, sqe); + ret = io_init_req(ctx, req, sqe, left); if (unlikely(ret)) return io_submit_fail_init(sqe, req, ret); @@ -2457,7 +2475,7 @@ int io_submit_sqes(struct io_ring_ctx *ctx, unsigned int nr) * Continue submitting even for sqe failure if the * ring was setup with IORING_SETUP_SUBMIT_ALL */ - if (unlikely(io_submit_sqe(ctx, req, sqe)) && + if (unlikely(io_submit_sqe(ctx, req, sqe, &left)) && !(ctx->flags & IORING_SETUP_SUBMIT_ALL)) { left--; break; @@ -2802,6 +2820,10 @@ unsigned long rings_size(unsigned int flags, unsigned int sq_entries, if (cq_entries < 2) return SIZE_MAX; } + if (flags & IORING_SETUP_SQE_MIXED) { + if (sq_entries < 2) + return SIZE_MAX; + } #ifdef CONFIG_SMP off = ALIGN(off, SMP_CACHE_BYTES); @@ -3726,6 +3748,13 @@ static int io_uring_sanitise_params(struct io_uring_params *p) if ((flags & (IORING_SETUP_CQE32|IORING_SETUP_CQE_MIXED)) == (IORING_SETUP_CQE32|IORING_SETUP_CQE_MIXED)) return -EINVAL; + /* + * Nonsensical to ask for SQE128 and mixed SQE support, it's not + * supported to post 64b SQEs on a ring setup with SQE128. + */ + if ((flags & (IORING_SETUP_SQE128|IORING_SETUP_SQE_MIXED)) == + (IORING_SETUP_SQE128|IORING_SETUP_SQE_MIXED)) + return -EINVAL; return 0; } diff --git a/io_uring/io_uring.h b/io_uring/io_uring.h index 78777bf1ea4b..44b8091c7fcd 100644 --- a/io_uring/io_uring.h +++ b/io_uring/io_uring.h @@ -54,7 +54,8 @@ IORING_SETUP_REGISTERED_FD_ONLY |\ IORING_SETUP_NO_SQARRAY |\ IORING_SETUP_HYBRID_IOPOLL |\ - IORING_SETUP_CQE_MIXED) + IORING_SETUP_CQE_MIXED |\ + IORING_SETUP_SQE_MIXED) #define IORING_ENTER_FLAGS (IORING_ENTER_GETEVENTS |\ IORING_ENTER_SQ_WAKEUP |\ @@ -565,17 +566,6 @@ static inline void io_req_queue_tw_complete(struct io_kiocb *req, s32 res) io_req_task_work_add(req); } -/* - * IORING_SETUP_SQE128 contexts allocate twice the normal SQE size for each - * slot. - */ -static inline size_t uring_sqe_size(struct io_ring_ctx *ctx) -{ - if (ctx->flags & IORING_SETUP_SQE128) - return 2 * sizeof(struct io_uring_sqe); - return sizeof(struct io_uring_sqe); -} - static inline bool io_file_can_poll(struct io_kiocb *req) { if (req->flags & REQ_F_CAN_POLL) diff --git a/io_uring/opdef.c b/io_uring/opdef.c index 932319633eac..df52d760240e 100644 --- a/io_uring/opdef.c +++ b/io_uring/opdef.c @@ -575,6 +575,24 @@ const struct io_issue_def io_issue_defs[] = { .prep = io_pipe_prep, .issue = io_pipe, }, + [IORING_OP_NOP128] = { + .audit_skip = 1, + .iopoll = 1, + .is_128 = 1, + .prep = io_nop_prep, + .issue = io_nop, + }, + [IORING_OP_URING_CMD128] = { + .buffer_select = 1, + .needs_file = 1, + .plug = 1, + .iopoll = 1, + .iopoll_queue = 1, + .is_128 = 1, + .async_size = sizeof(struct io_async_cmd), + .prep = io_uring_cmd_prep, + .issue = io_uring_cmd, + }, }; const struct io_cold_def io_cold_defs[] = { @@ -825,6 +843,14 @@ const struct io_cold_def io_cold_defs[] = { [IORING_OP_PIPE] = { .name = "PIPE", }, + [IORING_OP_NOP128] = { + .name = "NOP128", + }, + [IORING_OP_URING_CMD128] = { + .name = "URING_CMD128", + .sqe_copy = io_uring_cmd_sqe_copy, + .cleanup = io_uring_cmd_cleanup, + }, }; const char *io_uring_get_opcode(u8 opcode) diff --git a/io_uring/opdef.h b/io_uring/opdef.h index c2f0907ed78c..aa37846880ff 100644 --- a/io_uring/opdef.h +++ b/io_uring/opdef.h @@ -27,6 +27,8 @@ struct io_issue_def { unsigned iopoll_queue : 1; /* vectored opcode, set if 1) vectored, and 2) handler needs to know */ unsigned vectored : 1; + /* set to 1 if this opcode uses 128b sqes in a mixed sq */ + unsigned is_128 : 1; /* size of async data needed, if any */ unsigned short async_size; diff --git a/io_uring/register.c b/io_uring/register.c index 43eb02004824..1a3e05be6e7b 100644 --- a/io_uring/register.c +++ b/io_uring/register.c @@ -394,7 +394,7 @@ static void io_register_free_rings(struct io_ring_ctx *ctx, #define RESIZE_FLAGS (IORING_SETUP_CQSIZE | IORING_SETUP_CLAMP) #define COPY_FLAGS (IORING_SETUP_NO_SQARRAY | IORING_SETUP_SQE128 | \ IORING_SETUP_CQE32 | IORING_SETUP_NO_MMAP | \ - IORING_SETUP_CQE_MIXED) + IORING_SETUP_CQE_MIXED | IORING_SETUP_SQE_MIXED) static int io_register_resize_rings(struct io_ring_ctx *ctx, void __user *arg) { diff --git a/io_uring/uring_cmd.c b/io_uring/uring_cmd.c index 1225f8124e4b..9d67a2a721aa 100644 --- a/io_uring/uring_cmd.c +++ b/io_uring/uring_cmd.c @@ -216,6 +216,18 @@ int io_uring_cmd_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe) return 0; } +/* + * IORING_SETUP_SQE128 contexts allocate twice the normal SQE size for each + * slot. + */ +static inline size_t uring_sqe_size(struct io_kiocb *req) +{ + if (req->ctx->flags & IORING_SETUP_SQE128 || + req->opcode == IORING_OP_URING_CMD128) + return 2 * sizeof(struct io_uring_sqe); + return sizeof(struct io_uring_sqe); +} + void io_uring_cmd_sqe_copy(struct io_kiocb *req) { struct io_uring_cmd *ioucmd = io_kiocb_to_cmd(req, struct io_uring_cmd); @@ -224,7 +236,7 @@ void io_uring_cmd_sqe_copy(struct io_kiocb *req) /* Should not happen, as REQ_F_SQE_COPIED covers this */ if (WARN_ON_ONCE(ioucmd->sqe == ac->sqes)) return; - memcpy(ac->sqes, ioucmd->sqe, uring_sqe_size(req->ctx)); + memcpy(ac->sqes, ioucmd->sqe, uring_sqe_size(req)); ioucmd->sqe = ac->sqes; } @@ -242,7 +254,8 @@ int io_uring_cmd(struct io_kiocb *req, unsigned int issue_flags) if (ret) return ret; - if (ctx->flags & IORING_SETUP_SQE128) + if (ctx->flags & IORING_SETUP_SQE128 || + req->opcode == IORING_OP_URING_CMD128) issue_flags |= IO_URING_F_SQE128; if (ctx->flags & (IORING_SETUP_CQE32 | IORING_SETUP_CQE_MIXED)) issue_flags |= IO_URING_F_CQE32; -- cgit v1.2.3 From 0d8627cc936de8ea04f3cc1e6921c63fb72cc199 Mon Sep 17 00:00:00 2001 From: Johannes Thumshirn Date: Wed, 22 Oct 2025 13:41:06 +0200 Subject: blktrace: add definitions for blk_user_trace_setup2 Add definitions for a version 2 of the blk_user_trace_setup ioctl. This new ioctl will enable a different struct layout of the binary data passed to user-space when using a new version of the blktrace utility requesting the new struct layout. Reviewed-by: Damien Le Moal Reviewed-by: Christoph Hellwig Reviewed-by: Martin K. Petersen Signed-off-by: Johannes Thumshirn Signed-off-by: Jens Axboe --- include/uapi/linux/blktrace_api.h | 16 ++++++++++++++++ include/uapi/linux/fs.h | 1 + kernel/trace/blktrace.c | 3 +++ 3 files changed, 20 insertions(+) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/blktrace_api.h b/include/uapi/linux/blktrace_api.h index 1bfb635e309b..a6958708d477 100644 --- a/include/uapi/linux/blktrace_api.h +++ b/include/uapi/linux/blktrace_api.h @@ -129,6 +129,7 @@ enum { }; #define BLKTRACE_BDEV_SIZE 32 +#define BLKTRACE_BDEV_SIZE2 64 /* * User setup structure passed with BLKTRACESETUP @@ -143,4 +144,19 @@ struct blk_user_trace_setup { __u32 pid; }; +/* + * User setup structure passed with BLKTRACESETUP2 + */ +struct blk_user_trace_setup2 { + char name[BLKTRACE_BDEV_SIZE2]; /* output */ + __u64 act_mask; /* input */ + __u32 buf_size; /* input */ + __u32 buf_nr; /* input */ + __u64 start_lba; + __u64 end_lba; + __u32 pid; + __u32 flags; /* currently unused */ + __u64 reserved[11]; +}; + #endif /* _UAPIBLKTRACE_H */ diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h index beb4c2d1e41c..957ce3343a4f 100644 --- a/include/uapi/linux/fs.h +++ b/include/uapi/linux/fs.h @@ -300,6 +300,7 @@ struct file_attr { #define BLKGETDISKSEQ _IOR(0x12,128,__u64) /* 130-136 are used by zoned block device ioctls (uapi/linux/blkzoned.h) */ /* 137-141 are used by blk-crypto ioctls (uapi/linux/blk-crypto.h) */ +#define BLKTRACESETUP2 _IOWR(0x12, 142, struct blk_user_trace_setup2) #define BMAP_IOCTL 1 /* obsolete - kept for compatibility */ #define FIBMAP _IO(0x00,1) /* bmap access */ diff --git a/kernel/trace/blktrace.c b/kernel/trace/blktrace.c index df90422ae613..c31b8f433116 100644 --- a/kernel/trace/blktrace.c +++ b/kernel/trace/blktrace.c @@ -1601,6 +1601,9 @@ static int __init init_blk_tracer(void) return 1; } + BUILD_BUG_ON(__alignof__(struct blk_user_trace_setup2) % + __alignof__(long)); + return 0; } -- cgit v1.2.3 From c44347d606260f36a81f6d8415a5af33cb3015fa Mon Sep 17 00:00:00 2001 From: Johannes Thumshirn Date: Wed, 22 Oct 2025 13:41:08 +0200 Subject: blktrace: add definitions for struct blk_io_trace2 Add definitions for the extended version of the blktrace protocol using a wider action type to be able to record new actions in the kernel. Reviewed-by: Christoph Hellwig Reviewed-by: Damien Le Moal Reviewed-by: Martin K. Petersen Signed-off-by: Johannes Thumshirn Signed-off-by: Jens Axboe --- include/uapi/linux/blktrace_api.h | 16 ++++++++++++++++ kernel/trace/blktrace.c | 1 + 2 files changed, 17 insertions(+) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/blktrace_api.h b/include/uapi/linux/blktrace_api.h index a6958708d477..9f9834d76e00 100644 --- a/include/uapi/linux/blktrace_api.h +++ b/include/uapi/linux/blktrace_api.h @@ -94,6 +94,7 @@ enum blktrace_notify { #define BLK_IO_TRACE_MAGIC 0x65617400 #define BLK_IO_TRACE_VERSION 0x07 +#define BLK_IO_TRACE2_VERSION 0x08 /* * The trace itself @@ -113,6 +114,21 @@ struct blk_io_trace { /* cgroup id will be stored here if exists */ }; +struct blk_io_trace2 { + __u32 magic; /* MAGIC << 8 | BLK_IO_TRACE2_VERSION */ + __u32 sequence; /* event number */ + __u64 time; /* in nanoseconds */ + __u64 sector; /* disk offset */ + __u32 bytes; /* transfer length */ + __u32 pid; /* who did it */ + __u64 action; /* what happened */ + __u32 device; /* device number */ + __u32 cpu; /* on what cpu did it happen */ + __u16 error; /* completion error */ + __u16 pdu_len; /* length of data after this trace */ + __u8 pad[12]; + /* cgroup id will be stored here if it exists */ +}; /* * The remap event */ diff --git a/kernel/trace/blktrace.c b/kernel/trace/blktrace.c index d1532df84cc8..185f19c9f772 100644 --- a/kernel/trace/blktrace.c +++ b/kernel/trace/blktrace.c @@ -1616,6 +1616,7 @@ static int __init init_blk_tracer(void) BUILD_BUG_ON(__alignof__(struct blk_user_trace_setup2) % __alignof__(long)); + BUILD_BUG_ON(__alignof__(struct blk_io_trace2) % __alignof__(long)); return 0; } -- cgit v1.2.3 From f9ee38bbf70fb20584625849a253c8652176fa66 Mon Sep 17 00:00:00 2001 From: Johannes Thumshirn Date: Wed, 22 Oct 2025 13:41:12 +0200 Subject: blktrace: add block trace commands for zone operations Add block trace commands for zone operations. These commands can only be handled with version 2 of the blktrace protocol. For version 1, warn if a command that does not fit into the 16 bits reserved for the command in this version is passed in. Reviewed-by: Martin K. Petersen Signed-off-by: Johannes Thumshirn Reviewed-by: Damien Le Moal Signed-off-by: Jens Axboe --- include/uapi/linux/blktrace_api.h | 13 +++++++++++-- kernel/trace/blktrace.c | 29 +++++++++++++++++++++++++---- 2 files changed, 36 insertions(+), 6 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/blktrace_api.h b/include/uapi/linux/blktrace_api.h index 9f9834d76e00..190a3c5ab0a0 100644 --- a/include/uapi/linux/blktrace_api.h +++ b/include/uapi/linux/blktrace_api.h @@ -26,11 +26,20 @@ enum blktrace_cat { BLK_TC_DRV_DATA = 1 << 14, /* binary per-driver data */ BLK_TC_FUA = 1 << 15, /* fua requests */ - BLK_TC_END = 1 << 15, /* we've run out of bits! */ + BLK_TC_END_V1 = 1 << 15, /* we've run out of bits! */ + + BLK_TC_ZONE_APPEND = 1ull << 16, /* zone append */ + BLK_TC_ZONE_RESET = 1ull << 17, /* zone reset */ + BLK_TC_ZONE_RESET_ALL = 1ull << 18, /* zone reset all */ + BLK_TC_ZONE_FINISH = 1ull << 19, /* zone finish */ + BLK_TC_ZONE_OPEN = 1ull << 20, /* zone open */ + BLK_TC_ZONE_CLOSE = 1ull << 21, /* zone close */ + + BLK_TC_END_V2 = 1ull << 21, }; #define BLK_TC_SHIFT (16) -#define BLK_TC_ACT(act) ((act) << BLK_TC_SHIFT) +#define BLK_TC_ACT(act) ((u64)(act) << BLK_TC_SHIFT) /* * Basic trace actions diff --git a/kernel/trace/blktrace.c b/kernel/trace/blktrace.c index 49f73cb3cb33..fb5935885abc 100644 --- a/kernel/trace/blktrace.c +++ b/kernel/trace/blktrace.c @@ -163,8 +163,8 @@ static void relay_blktrace_event(struct blk_trace *bt, unsigned long sequence, bytes, what, error, cgid, cgid_len, pdu_data, pdu_len); return relay_blktrace_event1(bt, sequence, pid, cpu, sector, bytes, - lower_32_bits(what), error, cgid, cgid_len, - pdu_data, pdu_len); + what, error, cgid, cgid_len, pdu_data, + pdu_len); } /* @@ -342,10 +342,32 @@ static void __blk_add_trace(struct blk_trace *bt, sector_t sector, int bytes, case REQ_OP_FLUSH: what |= BLK_TC_ACT(BLK_TC_FLUSH); break; + case REQ_OP_ZONE_APPEND: + what |= BLK_TC_ACT(BLK_TC_ZONE_APPEND); + break; + case REQ_OP_ZONE_RESET: + what |= BLK_TC_ACT(BLK_TC_ZONE_RESET); + break; + case REQ_OP_ZONE_RESET_ALL: + what |= BLK_TC_ACT(BLK_TC_ZONE_RESET_ALL); + break; + case REQ_OP_ZONE_FINISH: + what |= BLK_TC_ACT(BLK_TC_ZONE_FINISH); + break; + case REQ_OP_ZONE_OPEN: + what |= BLK_TC_ACT(BLK_TC_ZONE_OPEN); + break; + case REQ_OP_ZONE_CLOSE: + what |= BLK_TC_ACT(BLK_TC_ZONE_CLOSE); + break; default: break; } + if (WARN_ON_ONCE(bt->version == 1 && + (what >> BLK_TC_SHIFT) > BLK_TC_END_V1)) + return; + if (cgid) what |= __BLK_TA_CGROUP; @@ -386,8 +408,7 @@ static void __blk_add_trace(struct blk_trace *bt, sector_t sector, int bytes, sequence = per_cpu_ptr(bt->sequence, cpu); (*sequence)++; relay_blktrace_event(bt, *sequence, pid, cpu, sector, bytes, - lower_32_bits(what), error, cgid, cgid_len, - pdu_data, pdu_len); + what, error, cgid, cgid_len, pdu_data, pdu_len); local_irq_restore(flags); } -- cgit v1.2.3 From 1c164fcc1b08e75f1cad1532718f09cddc0ddebe Mon Sep 17 00:00:00 2001 From: Johannes Thumshirn Date: Wed, 22 Oct 2025 13:41:13 +0200 Subject: blktrace: expose ZONE APPEND completions to blktrace Expose ZONE APPEND completions as a block trace completion action to blktrace. As tracing of zoned block commands needs the upper 32bit of the widened 64bit action, only add traces to blktrace if user-space has requested version 2 of the blktrace protocol. Reviewed-by: Damien Le Moal Reviewed-by: Christoph Hellwig Reviewed-by: Martin K. Petersen Signed-off-by: Johannes Thumshirn Signed-off-by: Jens Axboe --- include/uapi/linux/blktrace_api.h | 3 +++ kernel/trace/blktrace.c | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/blktrace_api.h b/include/uapi/linux/blktrace_api.h index 190a3c5ab0a0..289872e51fc5 100644 --- a/include/uapi/linux/blktrace_api.h +++ b/include/uapi/linux/blktrace_api.h @@ -97,6 +97,9 @@ enum blktrace_notify { #define BLK_TA_ABORT (__BLK_TA_ABORT | BLK_TC_ACT(BLK_TC_QUEUE)) #define BLK_TA_DRV_DATA (__BLK_TA_DRV_DATA | BLK_TC_ACT(BLK_TC_DRV_DATA)) +#define BLK_TA_ZONE_APPEND (__BLK_TA_COMPLETE |\ + BLK_TC_ACT(BLK_TC_ZONE_APPEND)) + #define BLK_TN_PROCESS (__BLK_TN_PROCESS | BLK_TC_ACT(BLK_TC_NOTIFY)) #define BLK_TN_TIMESTAMP (__BLK_TN_TIMESTAMP | BLK_TC_ACT(BLK_TC_NOTIFY)) #define BLK_TN_MESSAGE (__BLK_TN_MESSAGE | BLK_TC_ACT(BLK_TC_NOTIFY)) diff --git a/kernel/trace/blktrace.c b/kernel/trace/blktrace.c index fb5935885abc..c83577096607 100644 --- a/kernel/trace/blktrace.c +++ b/kernel/trace/blktrace.c @@ -978,6 +978,22 @@ static void blk_add_trace_rq_complete(void *ignore, struct request *rq, blk_trace_request_get_cgid(rq)); } +static void blk_add_trace_zone_update_request(void *ignore, struct request *rq) +{ + struct blk_trace *bt; + + rcu_read_lock(); + bt = rcu_dereference(rq->q->blk_trace); + if (likely(!bt) || bt->version < 2) { + rcu_read_unlock(); + return; + } + rcu_read_unlock(); + + blk_add_trace_rq(rq, 0, blk_rq_bytes(rq), BLK_TA_ZONE_APPEND, + blk_trace_request_get_cgid(rq)); +} + /** * blk_add_trace_bio - Add a trace for a bio oriented action * @q: queue the io is for @@ -1208,6 +1224,9 @@ static void blk_register_tracepoints(void) WARN_ON(ret); ret = register_trace_block_getrq(blk_add_trace_getrq, NULL); WARN_ON(ret); + ret = register_trace_blk_zone_append_update_request_bio( + blk_add_trace_zone_update_request, NULL); + WARN_ON(ret); ret = register_trace_block_plug(blk_add_trace_plug, NULL); WARN_ON(ret); ret = register_trace_block_unplug(blk_add_trace_unplug, NULL); @@ -1227,6 +1246,8 @@ static void blk_unregister_tracepoints(void) unregister_trace_block_split(blk_add_trace_split, NULL); unregister_trace_block_unplug(blk_add_trace_unplug, NULL); unregister_trace_block_plug(blk_add_trace_plug, NULL); + unregister_trace_blk_zone_append_update_request_bio( + blk_add_trace_zone_update_request, NULL); unregister_trace_block_getrq(blk_add_trace_getrq, NULL); unregister_trace_block_bio_queue(blk_add_trace_bio_queue, NULL); unregister_trace_block_bio_frontmerge(blk_add_trace_bio_frontmerge, NULL); -- cgit v1.2.3 From 3f6722816a73e2017599d965683dbe71833afd7a Mon Sep 17 00:00:00 2001 From: Johannes Thumshirn Date: Wed, 22 Oct 2025 13:41:14 +0200 Subject: blktrace: trace zone write plugging operations Trace zone write plugging operations on block devices. As tracing of zoned block commands needs the upper 32bit of the widened 64bit action, only add traces to blktrace if user-space has requested version 2 of the blktrace protocol. Reviewed-by: Damien Le Moal Reviewed-by: Christoph Hellwig Reviewed-by: Martin K. Petersen Signed-off-by: Johannes Thumshirn Signed-off-by: Jens Axboe --- include/uapi/linux/blktrace_api.h | 5 +++++ kernel/trace/blktrace.c | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/blktrace_api.h b/include/uapi/linux/blktrace_api.h index 289872e51fc5..30f3d2589365 100644 --- a/include/uapi/linux/blktrace_api.h +++ b/include/uapi/linux/blktrace_api.h @@ -62,6 +62,8 @@ enum blktrace_act { __BLK_TA_REMAP, /* bio was remapped */ __BLK_TA_ABORT, /* request aborted */ __BLK_TA_DRV_DATA, /* driver-specific binary data */ + __BLK_TA_ZONE_PLUG, /* zone write plug was plugged */ + __BLK_TA_ZONE_UNPLUG, /* zone write plug was unplugged */ __BLK_TA_CGROUP = 1 << 8, /* from a cgroup*/ }; @@ -99,6 +101,9 @@ enum blktrace_notify { #define BLK_TA_ZONE_APPEND (__BLK_TA_COMPLETE |\ BLK_TC_ACT(BLK_TC_ZONE_APPEND)) +#define BLK_TA_ZONE_PLUG (__BLK_TA_ZONE_PLUG | BLK_TC_ACT(BLK_TC_QUEUE)) +#define BLK_TA_ZONE_UNPLUG (__BLK_TA_ZONE_UNPLUG |\ + BLK_TC_ACT(BLK_TC_QUEUE)) #define BLK_TN_PROCESS (__BLK_TN_PROCESS | BLK_TC_ACT(BLK_TC_NOTIFY)) #define BLK_TN_TIMESTAMP (__BLK_TN_TIMESTAMP | BLK_TC_ACT(BLK_TC_NOTIFY)) diff --git a/kernel/trace/blktrace.c b/kernel/trace/blktrace.c index c83577096607..6bfe1b36a1d3 100644 --- a/kernel/trace/blktrace.c +++ b/kernel/trace/blktrace.c @@ -1084,6 +1084,37 @@ static void blk_add_trace_unplug(void *ignore, struct request_queue *q, rcu_read_unlock(); } +static void blk_add_trace_zone_plug(void *ignore, struct request_queue *q, + unsigned int zno, sector_t sector, + unsigned int sectors) +{ + struct blk_trace *bt; + + rcu_read_lock(); + bt = rcu_dereference(q->blk_trace); + if (bt && bt->version >= 2) + __blk_add_trace(bt, sector, sectors << SECTOR_SHIFT, 0, + BLK_TA_ZONE_PLUG, 0, 0, NULL, 0); + rcu_read_unlock(); + + return; +} + +static void blk_add_trace_zone_unplug(void *ignore, struct request_queue *q, + unsigned int zno, sector_t sector, + unsigned int sectors) +{ + struct blk_trace *bt; + + rcu_read_lock(); + bt = rcu_dereference(q->blk_trace); + if (bt && bt->version >= 2) + __blk_add_trace(bt, sector, sectors << SECTOR_SHIFT, 0, + BLK_TA_ZONE_UNPLUG, 0, 0, NULL, 0); + rcu_read_unlock(); + return; +} + static void blk_add_trace_split(void *ignore, struct bio *bio, unsigned int pdu) { struct request_queue *q = bio->bi_bdev->bd_disk->queue; @@ -1227,6 +1258,12 @@ static void blk_register_tracepoints(void) ret = register_trace_blk_zone_append_update_request_bio( blk_add_trace_zone_update_request, NULL); WARN_ON(ret); + ret = register_trace_disk_zone_wplug_add_bio(blk_add_trace_zone_plug, + NULL); + WARN_ON(ret); + ret = register_trace_blk_zone_wplug_bio(blk_add_trace_zone_unplug, + NULL); + WARN_ON(ret); ret = register_trace_block_plug(blk_add_trace_plug, NULL); WARN_ON(ret); ret = register_trace_block_unplug(blk_add_trace_unplug, NULL); @@ -1246,6 +1283,8 @@ static void blk_unregister_tracepoints(void) unregister_trace_block_split(blk_add_trace_split, NULL); unregister_trace_block_unplug(blk_add_trace_unplug, NULL); unregister_trace_block_plug(blk_add_trace_plug, NULL); + unregister_trace_blk_zone_wplug_bio(blk_add_trace_zone_unplug, NULL); + unregister_trace_disk_zone_wplug_add_bio(blk_add_trace_zone_plug, NULL); unregister_trace_blk_zone_append_update_request_bio( blk_add_trace_zone_update_request, NULL); unregister_trace_block_getrq(blk_add_trace_getrq, NULL); -- cgit v1.2.3 From bd26631ccdfd11701fa29e665a7f041875ba9423 Mon Sep 17 00:00:00 2001 From: Changwoo Min Date: Tue, 21 Oct 2025 07:09:07 +0900 Subject: PM: EM: Add em.yaml and autogen files Add a generic netlink spec in YAML format and autogenerate boilerplate code using ynl-regen.sh to introduce a generic netlink for the energy model. It allows a userspace program to read the performance domain and its energy model. It notifies the userspace program when a performance domain is created or deleted or its energy model is updated through a multicast interface. Specifically, it supports two commands: - EM_CMD_GET_PDS: Get the list of information for all performance domains. - EM_CMD_GET_PD_TABLE: Get the energy model table of a performance domain. Also, it supports three notification events: - EM_CMD_PD_CREATED: When a performance domain is created. - EM_CMD_PD_DELETED: When a performance domain is deleted. - EM_CMD_PD_UPDATED: When the energy model table of a performance domain is updated. Finally, update MAINTAINERS to include new files. Signed-off-by: Changwoo Min Reviewed-by: Lukasz Luba Link: https://patch.msgid.link/20251020220914.320832-4-changwoo@igalia.com Signed-off-by: Rafael J. Wysocki --- Documentation/netlink/specs/em.yaml | 113 ++++++++++++++++++++++++++++++++++++ MAINTAINERS | 3 + include/uapi/linux/energy_model.h | 62 ++++++++++++++++++++ kernel/power/em_netlink_autogen.c | 48 +++++++++++++++ kernel/power/em_netlink_autogen.h | 23 ++++++++ 5 files changed, 249 insertions(+) create mode 100644 Documentation/netlink/specs/em.yaml create mode 100644 include/uapi/linux/energy_model.h create mode 100644 kernel/power/em_netlink_autogen.c create mode 100644 kernel/power/em_netlink_autogen.h (limited to 'include/uapi/linux') diff --git a/Documentation/netlink/specs/em.yaml b/Documentation/netlink/specs/em.yaml new file mode 100644 index 000000000000..9905ca482325 --- /dev/null +++ b/Documentation/netlink/specs/em.yaml @@ -0,0 +1,113 @@ +# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) + +name: em + +doc: | + Energy model netlink interface to notify its changes. + +protocol: genetlink + +uapi-header: linux/energy_model.h + +attribute-sets: + - + name: pds + attributes: + - + name: pd + type: nest + nested-attributes: pd + multi-attr: true + - + name: pd + attributes: + - + name: pad + type: pad + - + name: pd-id + type: u32 + - + name: flags + type: u64 + - + name: cpus + type: string + - + name: pd-table + attributes: + - + name: pd-id + type: u32 + - + name: ps + type: nest + nested-attributes: ps + multi-attr: true + - + name: ps + attributes: + - + name: pad + type: pad + - + name: performance + type: u64 + - + name: frequency + type: u64 + - + name: power + type: u64 + - + name: cost + type: u64 + - + name: flags + type: u64 + +operations: + list: + - + name: get-pds + attribute-set: pds + doc: Get the list of information for all performance domains. + do: + reply: + attributes: + - pd + - + name: get-pd-table + attribute-set: pd-table + doc: Get the energy model table of a performance domain. + do: + request: + attributes: + - pd-id + reply: + attributes: + - pd-id + - ps + - + name: pd-created + doc: A performance domain is created. + notify: get-pd-table + mcgrp: event + - + name: pd-updated + doc: A performance domain is updated. + notify: get-pd-table + mcgrp: event + - + name: pd-deleted + doc: A performance domain is deleted. + attribute-set: pd-table + event: + attributes: + - pd-id + mcgrp: event + +mcast-groups: + list: + - + name: event diff --git a/MAINTAINERS b/MAINTAINERS index 545a4776795e..e6b3bab9dbeb 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9181,6 +9181,9 @@ S: Maintained F: kernel/power/energy_model.c F: include/linux/energy_model.h F: Documentation/power/energy-model.rst +F: Documentation/netlink/specs/em.yaml +F: include/uapi/linux/energy_model.h +F: kernel/power/em_netlink_autogen.* EPAPR HYPERVISOR BYTE CHANNEL DEVICE DRIVER M: Laurentiu Tudor diff --git a/include/uapi/linux/energy_model.h b/include/uapi/linux/energy_model.h new file mode 100644 index 000000000000..4ec4c0eabbbb --- /dev/null +++ b/include/uapi/linux/energy_model.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */ +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/em.yaml */ +/* YNL-GEN uapi header */ + +#ifndef _UAPI_LINUX_ENERGY_MODEL_H +#define _UAPI_LINUX_ENERGY_MODEL_H + +#define EM_FAMILY_NAME "em" +#define EM_FAMILY_VERSION 1 + +enum { + EM_A_PDS_PD = 1, + + __EM_A_PDS_MAX, + EM_A_PDS_MAX = (__EM_A_PDS_MAX - 1) +}; + +enum { + EM_A_PD_PAD = 1, + EM_A_PD_PD_ID, + EM_A_PD_FLAGS, + EM_A_PD_CPUS, + + __EM_A_PD_MAX, + EM_A_PD_MAX = (__EM_A_PD_MAX - 1) +}; + +enum { + EM_A_PD_TABLE_PD_ID = 1, + EM_A_PD_TABLE_PS, + + __EM_A_PD_TABLE_MAX, + EM_A_PD_TABLE_MAX = (__EM_A_PD_TABLE_MAX - 1) +}; + +enum { + EM_A_PS_PAD = 1, + EM_A_PS_PERFORMANCE, + EM_A_PS_FREQUENCY, + EM_A_PS_POWER, + EM_A_PS_COST, + EM_A_PS_FLAGS, + + __EM_A_PS_MAX, + EM_A_PS_MAX = (__EM_A_PS_MAX - 1) +}; + +enum { + EM_CMD_GET_PDS = 1, + EM_CMD_GET_PD_TABLE, + EM_CMD_PD_CREATED, + EM_CMD_PD_UPDATED, + EM_CMD_PD_DELETED, + + __EM_CMD_MAX, + EM_CMD_MAX = (__EM_CMD_MAX - 1) +}; + +#define EM_MCGRP_EVENT "event" + +#endif /* _UAPI_LINUX_ENERGY_MODEL_H */ diff --git a/kernel/power/em_netlink_autogen.c b/kernel/power/em_netlink_autogen.c new file mode 100644 index 000000000000..a7a09ab1d1c2 --- /dev/null +++ b/kernel/power/em_netlink_autogen.c @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/em.yaml */ +/* YNL-GEN kernel source */ + +#include +#include + +#include "em_netlink_autogen.h" + +#include + +/* EM_CMD_GET_PD_TABLE - do */ +static const struct nla_policy em_get_pd_table_nl_policy[EM_A_PD_TABLE_PD_ID + 1] = { + [EM_A_PD_TABLE_PD_ID] = { .type = NLA_U32, }, +}; + +/* Ops table for em */ +static const struct genl_split_ops em_nl_ops[] = { + { + .cmd = EM_CMD_GET_PDS, + .doit = em_nl_get_pds_doit, + .flags = GENL_CMD_CAP_DO, + }, + { + .cmd = EM_CMD_GET_PD_TABLE, + .doit = em_nl_get_pd_table_doit, + .policy = em_get_pd_table_nl_policy, + .maxattr = EM_A_PD_TABLE_PD_ID, + .flags = GENL_CMD_CAP_DO, + }, +}; + +static const struct genl_multicast_group em_nl_mcgrps[] = { + [EM_NLGRP_EVENT] = { "event", }, +}; + +struct genl_family em_nl_family __ro_after_init = { + .name = EM_FAMILY_NAME, + .version = EM_FAMILY_VERSION, + .netnsok = true, + .parallel_ops = true, + .module = THIS_MODULE, + .split_ops = em_nl_ops, + .n_split_ops = ARRAY_SIZE(em_nl_ops), + .mcgrps = em_nl_mcgrps, + .n_mcgrps = ARRAY_SIZE(em_nl_mcgrps), +}; diff --git a/kernel/power/em_netlink_autogen.h b/kernel/power/em_netlink_autogen.h new file mode 100644 index 000000000000..78ce609641f1 --- /dev/null +++ b/kernel/power/em_netlink_autogen.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */ +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/em.yaml */ +/* YNL-GEN kernel header */ + +#ifndef _LINUX_EM_GEN_H +#define _LINUX_EM_GEN_H + +#include +#include + +#include + +int em_nl_get_pds_doit(struct sk_buff *skb, struct genl_info *info); +int em_nl_get_pd_table_doit(struct sk_buff *skb, struct genl_info *info); + +enum { + EM_NLGRP_EVENT, +}; + +extern struct genl_family em_nl_family; + +#endif /* _LINUX_EM_GEN_H */ -- cgit v1.2.3 From 531b87d865eb9e625c2e46ec8f06a65a6157ee45 Mon Sep 17 00:00:00 2001 From: Mykyta Yatsenko Date: Sun, 26 Oct 2025 20:38:45 +0000 Subject: bpf: widen dynptr size/offset to 64 bit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dynptr currently caps size and offset at 24 bits, which isn’t sufficient for file-backed use cases; even 32 bits can be limiting. Refactor dynptr helpers/kfuncs to use 64-bit size and offset, ensuring consistency across the APIs. This change does not affect internals of xdp, skb or other dynptrs, which continue to behave as before. Also it does not break binary compatibility. The widening enables large-file access support via dynptr, implemented in the next patches. Signed-off-by: Mykyta Yatsenko Acked-by: Eduard Zingerman Link: https://lore.kernel.org/r/20251026203853.135105-3-mykyta.yatsenko5@gmail.com Signed-off-by: Alexei Starovoitov --- include/linux/bpf.h | 20 +++---- include/uapi/linux/bpf.h | 8 +-- kernel/bpf/helpers.c | 66 +++++++++++----------- kernel/trace/bpf_trace.c | 46 +++++++-------- tools/include/uapi/linux/bpf.h | 8 +-- tools/testing/selftests/bpf/bpf_kfuncs.h | 12 ++-- tools/testing/selftests/bpf/progs/dynptr_success.c | 12 ++-- 7 files changed, 86 insertions(+), 86 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/linux/bpf.h b/include/linux/bpf.h index e53cda0aabb6..907c69295293 100644 --- a/include/linux/bpf.h +++ b/include/linux/bpf.h @@ -1387,19 +1387,19 @@ enum bpf_dynptr_type { BPF_DYNPTR_TYPE_SKB_META, }; -int bpf_dynptr_check_size(u32 size); -u32 __bpf_dynptr_size(const struct bpf_dynptr_kern *ptr); -const void *__bpf_dynptr_data(const struct bpf_dynptr_kern *ptr, u32 len); -void *__bpf_dynptr_data_rw(const struct bpf_dynptr_kern *ptr, u32 len); +int bpf_dynptr_check_size(u64 size); +u64 __bpf_dynptr_size(const struct bpf_dynptr_kern *ptr); +const void *__bpf_dynptr_data(const struct bpf_dynptr_kern *ptr, u64 len); +void *__bpf_dynptr_data_rw(const struct bpf_dynptr_kern *ptr, u64 len); bool __bpf_dynptr_is_rdonly(const struct bpf_dynptr_kern *ptr); -int __bpf_dynptr_write(const struct bpf_dynptr_kern *dst, u32 offset, - void *src, u32 len, u64 flags); -void *bpf_dynptr_slice_rdwr(const struct bpf_dynptr *p, u32 offset, - void *buffer__opt, u32 buffer__szk); +int __bpf_dynptr_write(const struct bpf_dynptr_kern *dst, u64 offset, + void *src, u64 len, u64 flags); +void *bpf_dynptr_slice_rdwr(const struct bpf_dynptr *p, u64 offset, + void *buffer__opt, u64 buffer__szk); -static inline int bpf_dynptr_check_off_len(const struct bpf_dynptr_kern *ptr, u32 offset, u32 len) +static inline int bpf_dynptr_check_off_len(const struct bpf_dynptr_kern *ptr, u64 offset, u64 len) { - u32 size = __bpf_dynptr_size(ptr); + u64 size = __bpf_dynptr_size(ptr); if (len > size || offset > size - len) return -E2BIG; diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index 6829936d33f5..77edd0253989 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -5618,7 +5618,7 @@ union bpf_attr { * Return * *sk* if casting is valid, or **NULL** otherwise. * - * long bpf_dynptr_from_mem(void *data, u32 size, u64 flags, struct bpf_dynptr *ptr) + * long bpf_dynptr_from_mem(void *data, u64 size, u64 flags, struct bpf_dynptr *ptr) * Description * Get a dynptr to local memory *data*. * @@ -5661,7 +5661,7 @@ union bpf_attr { * Return * Nothing. Always succeeds. * - * long bpf_dynptr_read(void *dst, u32 len, const struct bpf_dynptr *src, u32 offset, u64 flags) + * long bpf_dynptr_read(void *dst, u64 len, const struct bpf_dynptr *src, u64 offset, u64 flags) * Description * Read *len* bytes from *src* into *dst*, starting from *offset* * into *src*. @@ -5671,7 +5671,7 @@ union bpf_attr { * of *src*'s data, -EINVAL if *src* is an invalid dynptr or if * *flags* is not 0. * - * long bpf_dynptr_write(const struct bpf_dynptr *dst, u32 offset, void *src, u32 len, u64 flags) + * long bpf_dynptr_write(const struct bpf_dynptr *dst, u64 offset, void *src, u64 len, u64 flags) * Description * Write *len* bytes from *src* into *dst*, starting from *offset* * into *dst*. @@ -5692,7 +5692,7 @@ union bpf_attr { * is a read-only dynptr or if *flags* is not correct. For skb-type dynptrs, * other errors correspond to errors returned by **bpf_skb_store_bytes**\ (). * - * void *bpf_dynptr_data(const struct bpf_dynptr *ptr, u32 offset, u32 len) + * void *bpf_dynptr_data(const struct bpf_dynptr *ptr, u64 offset, u64 len) * Description * Get a pointer to the underlying dynptr data. * diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c index b9ec6ee21c94..a2ce17ea5edb 100644 --- a/kernel/bpf/helpers.c +++ b/kernel/bpf/helpers.c @@ -1684,19 +1684,19 @@ static enum bpf_dynptr_type bpf_dynptr_get_type(const struct bpf_dynptr_kern *pt return (ptr->size & ~(DYNPTR_RDONLY_BIT)) >> DYNPTR_TYPE_SHIFT; } -u32 __bpf_dynptr_size(const struct bpf_dynptr_kern *ptr) +u64 __bpf_dynptr_size(const struct bpf_dynptr_kern *ptr) { return ptr->size & DYNPTR_SIZE_MASK; } -static void bpf_dynptr_set_size(struct bpf_dynptr_kern *ptr, u32 new_size) +static void bpf_dynptr_set_size(struct bpf_dynptr_kern *ptr, u64 new_size) { u32 metadata = ptr->size & ~DYNPTR_SIZE_MASK; - ptr->size = new_size | metadata; + ptr->size = (u32)new_size | metadata; } -int bpf_dynptr_check_size(u32 size) +int bpf_dynptr_check_size(u64 size) { return size > DYNPTR_MAX_SIZE ? -E2BIG : 0; } @@ -1715,7 +1715,7 @@ void bpf_dynptr_set_null(struct bpf_dynptr_kern *ptr) memset(ptr, 0, sizeof(*ptr)); } -BPF_CALL_4(bpf_dynptr_from_mem, void *, data, u32, size, u64, flags, struct bpf_dynptr_kern *, ptr) +BPF_CALL_4(bpf_dynptr_from_mem, void *, data, u64, size, u64, flags, struct bpf_dynptr_kern *, ptr) { int err; @@ -1750,8 +1750,8 @@ static const struct bpf_func_proto bpf_dynptr_from_mem_proto = { .arg4_type = ARG_PTR_TO_DYNPTR | DYNPTR_TYPE_LOCAL | MEM_UNINIT | MEM_WRITE, }; -static int __bpf_dynptr_read(void *dst, u32 len, const struct bpf_dynptr_kern *src, - u32 offset, u64 flags) +static int __bpf_dynptr_read(void *dst, u64 len, const struct bpf_dynptr_kern *src, + u64 offset, u64 flags) { enum bpf_dynptr_type type; int err; @@ -1787,8 +1787,8 @@ static int __bpf_dynptr_read(void *dst, u32 len, const struct bpf_dynptr_kern *s } } -BPF_CALL_5(bpf_dynptr_read, void *, dst, u32, len, const struct bpf_dynptr_kern *, src, - u32, offset, u64, flags) +BPF_CALL_5(bpf_dynptr_read, void *, dst, u64, len, const struct bpf_dynptr_kern *, src, + u64, offset, u64, flags) { return __bpf_dynptr_read(dst, len, src, offset, flags); } @@ -1804,8 +1804,8 @@ static const struct bpf_func_proto bpf_dynptr_read_proto = { .arg5_type = ARG_ANYTHING, }; -int __bpf_dynptr_write(const struct bpf_dynptr_kern *dst, u32 offset, void *src, - u32 len, u64 flags) +int __bpf_dynptr_write(const struct bpf_dynptr_kern *dst, u64 offset, void *src, + u64 len, u64 flags) { enum bpf_dynptr_type type; int err; @@ -1848,8 +1848,8 @@ int __bpf_dynptr_write(const struct bpf_dynptr_kern *dst, u32 offset, void *src, } } -BPF_CALL_5(bpf_dynptr_write, const struct bpf_dynptr_kern *, dst, u32, offset, void *, src, - u32, len, u64, flags) +BPF_CALL_5(bpf_dynptr_write, const struct bpf_dynptr_kern *, dst, u64, offset, void *, src, + u64, len, u64, flags) { return __bpf_dynptr_write(dst, offset, src, len, flags); } @@ -1865,7 +1865,7 @@ static const struct bpf_func_proto bpf_dynptr_write_proto = { .arg5_type = ARG_ANYTHING, }; -BPF_CALL_3(bpf_dynptr_data, const struct bpf_dynptr_kern *, ptr, u32, offset, u32, len) +BPF_CALL_3(bpf_dynptr_data, const struct bpf_dynptr_kern *, ptr, u64, offset, u64, len) { enum bpf_dynptr_type type; int err; @@ -2680,12 +2680,12 @@ __bpf_kfunc struct task_struct *bpf_task_from_vpid(s32 vpid) * provided buffer, with its contents containing the data, if unable to obtain * direct pointer) */ -__bpf_kfunc void *bpf_dynptr_slice(const struct bpf_dynptr *p, u32 offset, - void *buffer__opt, u32 buffer__szk) +__bpf_kfunc void *bpf_dynptr_slice(const struct bpf_dynptr *p, u64 offset, + void *buffer__opt, u64 buffer__szk) { const struct bpf_dynptr_kern *ptr = (struct bpf_dynptr_kern *)p; enum bpf_dynptr_type type; - u32 len = buffer__szk; + u64 len = buffer__szk; int err; if (!ptr->data) @@ -2767,8 +2767,8 @@ __bpf_kfunc void *bpf_dynptr_slice(const struct bpf_dynptr *p, u32 offset, * provided buffer, with its contents containing the data, if unable to obtain * direct pointer) */ -__bpf_kfunc void *bpf_dynptr_slice_rdwr(const struct bpf_dynptr *p, u32 offset, - void *buffer__opt, u32 buffer__szk) +__bpf_kfunc void *bpf_dynptr_slice_rdwr(const struct bpf_dynptr *p, u64 offset, + void *buffer__opt, u64 buffer__szk) { const struct bpf_dynptr_kern *ptr = (struct bpf_dynptr_kern *)p; @@ -2800,10 +2800,10 @@ __bpf_kfunc void *bpf_dynptr_slice_rdwr(const struct bpf_dynptr *p, u32 offset, return bpf_dynptr_slice(p, offset, buffer__opt, buffer__szk); } -__bpf_kfunc int bpf_dynptr_adjust(const struct bpf_dynptr *p, u32 start, u32 end) +__bpf_kfunc int bpf_dynptr_adjust(const struct bpf_dynptr *p, u64 start, u64 end) { struct bpf_dynptr_kern *ptr = (struct bpf_dynptr_kern *)p; - u32 size; + u64 size; if (!ptr->data || start > end) return -EINVAL; @@ -2836,7 +2836,7 @@ __bpf_kfunc bool bpf_dynptr_is_rdonly(const struct bpf_dynptr *p) return __bpf_dynptr_is_rdonly(ptr); } -__bpf_kfunc __u32 bpf_dynptr_size(const struct bpf_dynptr *p) +__bpf_kfunc u64 bpf_dynptr_size(const struct bpf_dynptr *p) { struct bpf_dynptr_kern *ptr = (struct bpf_dynptr_kern *)p; @@ -2873,14 +2873,14 @@ __bpf_kfunc int bpf_dynptr_clone(const struct bpf_dynptr *p, * Copies data from source dynptr to destination dynptr. * Returns 0 on success; negative error, otherwise. */ -__bpf_kfunc int bpf_dynptr_copy(struct bpf_dynptr *dst_ptr, u32 dst_off, - struct bpf_dynptr *src_ptr, u32 src_off, u32 size) +__bpf_kfunc int bpf_dynptr_copy(struct bpf_dynptr *dst_ptr, u64 dst_off, + struct bpf_dynptr *src_ptr, u64 src_off, u64 size) { struct bpf_dynptr_kern *dst = (struct bpf_dynptr_kern *)dst_ptr; struct bpf_dynptr_kern *src = (struct bpf_dynptr_kern *)src_ptr; void *src_slice, *dst_slice; char buf[256]; - u32 off; + u64 off; src_slice = bpf_dynptr_slice(src_ptr, src_off, NULL, size); dst_slice = bpf_dynptr_slice_rdwr(dst_ptr, dst_off, NULL, size); @@ -2902,7 +2902,7 @@ __bpf_kfunc int bpf_dynptr_copy(struct bpf_dynptr *dst_ptr, u32 dst_off, off = 0; while (off < size) { - u32 chunk_sz = min_t(u32, sizeof(buf), size - off); + u64 chunk_sz = min_t(u64, sizeof(buf), size - off); int err; err = __bpf_dynptr_read(buf, chunk_sz, src, src_off + off, 0); @@ -2928,10 +2928,10 @@ __bpf_kfunc int bpf_dynptr_copy(struct bpf_dynptr *dst_ptr, u32 dst_off, * at @offset with the constant byte @val. * Returns 0 on success; negative error, otherwise. */ - __bpf_kfunc int bpf_dynptr_memset(struct bpf_dynptr *p, u32 offset, u32 size, u8 val) - { +__bpf_kfunc int bpf_dynptr_memset(struct bpf_dynptr *p, u64 offset, u64 size, u8 val) +{ struct bpf_dynptr_kern *ptr = (struct bpf_dynptr_kern *)p; - u32 chunk_sz, write_off; + u64 chunk_sz, write_off; char buf[256]; void* slice; int err; @@ -2950,11 +2950,11 @@ __bpf_kfunc int bpf_dynptr_copy(struct bpf_dynptr *dst_ptr, u32 dst_off, return err; /* Non-linear data under the dynptr, write from a local buffer */ - chunk_sz = min_t(u32, sizeof(buf), size); + chunk_sz = min_t(u64, sizeof(buf), size); memset(buf, val, chunk_sz); for (write_off = 0; write_off < size; write_off += chunk_sz) { - chunk_sz = min_t(u32, sizeof(buf), size - write_off); + chunk_sz = min_t(u64, sizeof(buf), size - write_off); err = __bpf_dynptr_write(ptr, offset + write_off, buf, chunk_sz, 0); if (err) return err; @@ -4469,7 +4469,7 @@ late_initcall(kfunc_init); /* Get a pointer to dynptr data up to len bytes for read only access. If * the dynptr doesn't have continuous data up to len bytes, return NULL. */ -const void *__bpf_dynptr_data(const struct bpf_dynptr_kern *ptr, u32 len) +const void *__bpf_dynptr_data(const struct bpf_dynptr_kern *ptr, u64 len) { const struct bpf_dynptr *p = (struct bpf_dynptr *)ptr; @@ -4480,7 +4480,7 @@ const void *__bpf_dynptr_data(const struct bpf_dynptr_kern *ptr, u32 len) * the dynptr doesn't have continuous data up to len bytes, or the dynptr * is read only, return NULL. */ -void *__bpf_dynptr_data_rw(const struct bpf_dynptr_kern *ptr, u32 len) +void *__bpf_dynptr_data_rw(const struct bpf_dynptr_kern *ptr, u64 len) { if (__bpf_dynptr_is_rdonly(ptr)) return NULL; diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c index 4f87c16d915a..a795f7afbf3d 100644 --- a/kernel/trace/bpf_trace.c +++ b/kernel/trace/bpf_trace.c @@ -3372,13 +3372,13 @@ typedef int (*copy_fn_t)(void *dst, const void *src, u32 size, struct task_struc * direct calls into all the specific callback implementations * (copy_user_data_sleepable, copy_user_data_nofault, and so on) */ -static __always_inline int __bpf_dynptr_copy_str(struct bpf_dynptr *dptr, u32 doff, u32 size, +static __always_inline int __bpf_dynptr_copy_str(struct bpf_dynptr *dptr, u64 doff, u64 size, const void *unsafe_src, copy_fn_t str_copy_fn, struct task_struct *tsk) { struct bpf_dynptr_kern *dst; - u32 chunk_sz, off; + u64 chunk_sz, off; void *dst_slice; int cnt, err; char buf[256]; @@ -3392,7 +3392,7 @@ static __always_inline int __bpf_dynptr_copy_str(struct bpf_dynptr *dptr, u32 do return -E2BIG; for (off = 0; off < size; off += chunk_sz - 1) { - chunk_sz = min_t(u32, sizeof(buf), size - off); + chunk_sz = min_t(u64, sizeof(buf), size - off); /* Expect str_copy_fn to return count of copied bytes, including * zero terminator. Next iteration increment off by chunk_sz - 1 to * overwrite NUL. @@ -3409,14 +3409,14 @@ static __always_inline int __bpf_dynptr_copy_str(struct bpf_dynptr *dptr, u32 do return off; } -static __always_inline int __bpf_dynptr_copy(const struct bpf_dynptr *dptr, u32 doff, - u32 size, const void *unsafe_src, +static __always_inline int __bpf_dynptr_copy(const struct bpf_dynptr *dptr, u64 doff, + u64 size, const void *unsafe_src, copy_fn_t copy_fn, struct task_struct *tsk) { struct bpf_dynptr_kern *dst; void *dst_slice; char buf[256]; - u32 off, chunk_sz; + u64 off, chunk_sz; int err; dst_slice = bpf_dynptr_slice_rdwr(dptr, doff, NULL, size); @@ -3428,7 +3428,7 @@ static __always_inline int __bpf_dynptr_copy(const struct bpf_dynptr *dptr, u32 return -E2BIG; for (off = 0; off < size; off += chunk_sz) { - chunk_sz = min_t(u32, sizeof(buf), size - off); + chunk_sz = min_t(u64, sizeof(buf), size - off); err = copy_fn(buf, unsafe_src + off, chunk_sz, tsk); if (err) return err; @@ -3514,58 +3514,58 @@ __bpf_kfunc int bpf_send_signal_task(struct task_struct *task, int sig, enum pid return bpf_send_signal_common(sig, type, task, value); } -__bpf_kfunc int bpf_probe_read_user_dynptr(struct bpf_dynptr *dptr, u32 off, - u32 size, const void __user *unsafe_ptr__ign) +__bpf_kfunc int bpf_probe_read_user_dynptr(struct bpf_dynptr *dptr, u64 off, + u64 size, const void __user *unsafe_ptr__ign) { return __bpf_dynptr_copy(dptr, off, size, (const void *)unsafe_ptr__ign, copy_user_data_nofault, NULL); } -__bpf_kfunc int bpf_probe_read_kernel_dynptr(struct bpf_dynptr *dptr, u32 off, - u32 size, const void *unsafe_ptr__ign) +__bpf_kfunc int bpf_probe_read_kernel_dynptr(struct bpf_dynptr *dptr, u64 off, + u64 size, const void *unsafe_ptr__ign) { return __bpf_dynptr_copy(dptr, off, size, unsafe_ptr__ign, copy_kernel_data_nofault, NULL); } -__bpf_kfunc int bpf_probe_read_user_str_dynptr(struct bpf_dynptr *dptr, u32 off, - u32 size, const void __user *unsafe_ptr__ign) +__bpf_kfunc int bpf_probe_read_user_str_dynptr(struct bpf_dynptr *dptr, u64 off, + u64 size, const void __user *unsafe_ptr__ign) { return __bpf_dynptr_copy_str(dptr, off, size, (const void *)unsafe_ptr__ign, copy_user_str_nofault, NULL); } -__bpf_kfunc int bpf_probe_read_kernel_str_dynptr(struct bpf_dynptr *dptr, u32 off, - u32 size, const void *unsafe_ptr__ign) +__bpf_kfunc int bpf_probe_read_kernel_str_dynptr(struct bpf_dynptr *dptr, u64 off, + u64 size, const void *unsafe_ptr__ign) { return __bpf_dynptr_copy_str(dptr, off, size, unsafe_ptr__ign, copy_kernel_str_nofault, NULL); } -__bpf_kfunc int bpf_copy_from_user_dynptr(struct bpf_dynptr *dptr, u32 off, - u32 size, const void __user *unsafe_ptr__ign) +__bpf_kfunc int bpf_copy_from_user_dynptr(struct bpf_dynptr *dptr, u64 off, + u64 size, const void __user *unsafe_ptr__ign) { return __bpf_dynptr_copy(dptr, off, size, (const void *)unsafe_ptr__ign, copy_user_data_sleepable, NULL); } -__bpf_kfunc int bpf_copy_from_user_str_dynptr(struct bpf_dynptr *dptr, u32 off, - u32 size, const void __user *unsafe_ptr__ign) +__bpf_kfunc int bpf_copy_from_user_str_dynptr(struct bpf_dynptr *dptr, u64 off, + u64 size, const void __user *unsafe_ptr__ign) { return __bpf_dynptr_copy_str(dptr, off, size, (const void *)unsafe_ptr__ign, copy_user_str_sleepable, NULL); } -__bpf_kfunc int bpf_copy_from_user_task_dynptr(struct bpf_dynptr *dptr, u32 off, - u32 size, const void __user *unsafe_ptr__ign, +__bpf_kfunc int bpf_copy_from_user_task_dynptr(struct bpf_dynptr *dptr, u64 off, + u64 size, const void __user *unsafe_ptr__ign, struct task_struct *tsk) { return __bpf_dynptr_copy(dptr, off, size, (const void *)unsafe_ptr__ign, copy_user_data_sleepable, tsk); } -__bpf_kfunc int bpf_copy_from_user_task_str_dynptr(struct bpf_dynptr *dptr, u32 off, - u32 size, const void __user *unsafe_ptr__ign, +__bpf_kfunc int bpf_copy_from_user_task_str_dynptr(struct bpf_dynptr *dptr, u64 off, + u64 size, const void __user *unsafe_ptr__ign, struct task_struct *tsk) { return __bpf_dynptr_copy_str(dptr, off, size, (const void *)unsafe_ptr__ign, diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h index 6829936d33f5..77edd0253989 100644 --- a/tools/include/uapi/linux/bpf.h +++ b/tools/include/uapi/linux/bpf.h @@ -5618,7 +5618,7 @@ union bpf_attr { * Return * *sk* if casting is valid, or **NULL** otherwise. * - * long bpf_dynptr_from_mem(void *data, u32 size, u64 flags, struct bpf_dynptr *ptr) + * long bpf_dynptr_from_mem(void *data, u64 size, u64 flags, struct bpf_dynptr *ptr) * Description * Get a dynptr to local memory *data*. * @@ -5661,7 +5661,7 @@ union bpf_attr { * Return * Nothing. Always succeeds. * - * long bpf_dynptr_read(void *dst, u32 len, const struct bpf_dynptr *src, u32 offset, u64 flags) + * long bpf_dynptr_read(void *dst, u64 len, const struct bpf_dynptr *src, u64 offset, u64 flags) * Description * Read *len* bytes from *src* into *dst*, starting from *offset* * into *src*. @@ -5671,7 +5671,7 @@ union bpf_attr { * of *src*'s data, -EINVAL if *src* is an invalid dynptr or if * *flags* is not 0. * - * long bpf_dynptr_write(const struct bpf_dynptr *dst, u32 offset, void *src, u32 len, u64 flags) + * long bpf_dynptr_write(const struct bpf_dynptr *dst, u64 offset, void *src, u64 len, u64 flags) * Description * Write *len* bytes from *src* into *dst*, starting from *offset* * into *dst*. @@ -5692,7 +5692,7 @@ union bpf_attr { * is a read-only dynptr or if *flags* is not correct. For skb-type dynptrs, * other errors correspond to errors returned by **bpf_skb_store_bytes**\ (). * - * void *bpf_dynptr_data(const struct bpf_dynptr *ptr, u32 offset, u32 len) + * void *bpf_dynptr_data(const struct bpf_dynptr *ptr, u64 offset, u64 len) * Description * Get a pointer to the underlying dynptr data. * diff --git a/tools/testing/selftests/bpf/bpf_kfuncs.h b/tools/testing/selftests/bpf/bpf_kfuncs.h index 794d44d19c88..e0189254bb6e 100644 --- a/tools/testing/selftests/bpf/bpf_kfuncs.h +++ b/tools/testing/selftests/bpf/bpf_kfuncs.h @@ -28,8 +28,8 @@ extern int bpf_dynptr_from_skb_meta(struct __sk_buff *skb, __u64 flags, * Either a direct pointer to the dynptr data or a pointer to the user-provided * buffer if unable to obtain a direct pointer */ -extern void *bpf_dynptr_slice(const struct bpf_dynptr *ptr, __u32 offset, - void *buffer, __u32 buffer__szk) __ksym __weak; +extern void *bpf_dynptr_slice(const struct bpf_dynptr *ptr, __u64 offset, + void *buffer, __u64 buffer__szk) __ksym __weak; /* Description * Obtain a read-write pointer to the dynptr's data @@ -37,13 +37,13 @@ extern void *bpf_dynptr_slice(const struct bpf_dynptr *ptr, __u32 offset, * Either a direct pointer to the dynptr data or a pointer to the user-provided * buffer if unable to obtain a direct pointer */ -extern void *bpf_dynptr_slice_rdwr(const struct bpf_dynptr *ptr, __u32 offset, - void *buffer, __u32 buffer__szk) __ksym __weak; +extern void *bpf_dynptr_slice_rdwr(const struct bpf_dynptr *ptr, __u64 offset, void *buffer, + __u64 buffer__szk) __ksym __weak; -extern int bpf_dynptr_adjust(const struct bpf_dynptr *ptr, __u32 start, __u32 end) __ksym __weak; +extern int bpf_dynptr_adjust(const struct bpf_dynptr *ptr, __u64 start, __u64 end) __ksym __weak; extern bool bpf_dynptr_is_null(const struct bpf_dynptr *ptr) __ksym __weak; extern bool bpf_dynptr_is_rdonly(const struct bpf_dynptr *ptr) __ksym __weak; -extern __u32 bpf_dynptr_size(const struct bpf_dynptr *ptr) __ksym __weak; +extern __u64 bpf_dynptr_size(const struct bpf_dynptr *ptr) __ksym __weak; extern int bpf_dynptr_clone(const struct bpf_dynptr *ptr, struct bpf_dynptr *clone__init) __ksym __weak; /* Description diff --git a/tools/testing/selftests/bpf/progs/dynptr_success.c b/tools/testing/selftests/bpf/progs/dynptr_success.c index 127dea342e5a..e0d672d93adf 100644 --- a/tools/testing/selftests/bpf/progs/dynptr_success.c +++ b/tools/testing/selftests/bpf/progs/dynptr_success.c @@ -914,8 +914,8 @@ void *user_ptr; char expected_str[384]; __u32 test_len[7] = {0/* placeholder */, 0, 1, 2, 255, 256, 257}; -typedef int (*bpf_read_dynptr_fn_t)(struct bpf_dynptr *dptr, u32 off, - u32 size, const void *unsafe_ptr); +typedef int (*bpf_read_dynptr_fn_t)(struct bpf_dynptr *dptr, u64 off, + u64 size, const void *unsafe_ptr); /* Returns the offset just before the end of the maximum sized xdp fragment. * Any write larger than 32 bytes will be split between 2 fragments. @@ -1106,16 +1106,16 @@ int test_copy_from_user_str_dynptr(void *ctx) return 0; } -static int bpf_copy_data_from_user_task(struct bpf_dynptr *dptr, u32 off, - u32 size, const void *unsafe_ptr) +static int bpf_copy_data_from_user_task(struct bpf_dynptr *dptr, u64 off, + u64 size, const void *unsafe_ptr) { struct task_struct *task = bpf_get_current_task_btf(); return bpf_copy_from_user_task_dynptr(dptr, off, size, unsafe_ptr, task); } -static int bpf_copy_data_from_user_task_str(struct bpf_dynptr *dptr, u32 off, - u32 size, const void *unsafe_ptr) +static int bpf_copy_data_from_user_task_str(struct bpf_dynptr *dptr, u64 off, + u64 size, const void *unsafe_ptr) { struct task_struct *task = bpf_get_current_task_btf(); -- cgit v1.2.3 From 82cb5be6ad64198a3a028aeb49dcc7f6224d558a Mon Sep 17 00:00:00 2001 From: Wilfred Mallawa Date: Wed, 22 Oct 2025 10:19:36 +1000 Subject: net/tls: support setting the maximum payload size During a handshake, an endpoint may specify a maximum record size limit. Currently, the kernel defaults to TLS_MAX_PAYLOAD_SIZE (16KB) for the maximum record size. Meaning that, the outgoing records from the kernel can exceed a lower size negotiated during the handshake. In such a case, the TLS endpoint must send a fatal "record_overflow" alert [1], and thus the record is discarded. Upcoming Western Digital NVMe-TCP hardware controllers implement TLS support. For these devices, supporting TLS record size negotiation is necessary because the maximum TLS record size supported by the controller is less than the default 16KB currently used by the kernel. Currently, there is no way to inform the kernel of such a limit. This patch adds support to a new setsockopt() option `TLS_TX_MAX_PAYLOAD_LEN` that allows for setting the maximum plaintext fragment size. Once set, outgoing records are no larger than the size specified. This option can be used to specify the record size limit. [1] https://www.rfc-editor.org/rfc/rfc8449 Signed-off-by: Wilfred Mallawa Reviewed-by: Sabrina Dubroca Link: https://patch.msgid.link/20251022001937.20155-1-wilfred.opensource@gmail.com Signed-off-by: Jakub Kicinski --- Documentation/networking/tls.rst | 20 +++++++++++++ include/net/tls.h | 3 ++ include/uapi/linux/tls.h | 2 ++ net/tls/tls_device.c | 2 +- net/tls/tls_main.c | 64 ++++++++++++++++++++++++++++++++++++++++ net/tls/tls_sw.c | 2 +- 6 files changed, 91 insertions(+), 2 deletions(-) (limited to 'include/uapi/linux') diff --git a/Documentation/networking/tls.rst b/Documentation/networking/tls.rst index 36cc7afc2527..980c442d7161 100644 --- a/Documentation/networking/tls.rst +++ b/Documentation/networking/tls.rst @@ -280,6 +280,26 @@ If the record decrypted turns out to had been padded or is not a data record it will be decrypted again into a kernel buffer without zero copy. Such events are counted in the ``TlsDecryptRetry`` statistic. +TLS_TX_MAX_PAYLOAD_LEN +~~~~~~~~~~~~~~~~~~~~~~ + +Specifies the maximum size of the plaintext payload for transmitted TLS records. + +When this option is set, the kernel enforces the specified limit on all outgoing +TLS records. No plaintext fragment will exceed this size. This option can be used +to implement the TLS Record Size Limit extension [1]. + +* For TLS 1.2, the value corresponds directly to the record size limit. +* For TLS 1.3, the value should be set to record_size_limit - 1, since + the record size limit includes one additional byte for the ContentType + field. + +The valid range for this option is 64 to 16384 bytes for TLS 1.2, and 63 to +16384 bytes for TLS 1.3. The lower minimum for TLS 1.3 accounts for the +extra byte used by the ContentType field. + +[1] https://datatracker.ietf.org/doc/html/rfc8449 + Statistics ========== diff --git a/include/net/tls.h b/include/net/tls.h index 857340338b69..f2af113728aa 100644 --- a/include/net/tls.h +++ b/include/net/tls.h @@ -53,6 +53,8 @@ struct tls_rec; /* Maximum data size carried in a TLS record */ #define TLS_MAX_PAYLOAD_SIZE ((size_t)1 << 14) +/* Minimum record size limit as per RFC8449 */ +#define TLS_MIN_RECORD_SIZE_LIM ((size_t)1 << 6) #define TLS_HEADER_SIZE 5 #define TLS_NONCE_OFFSET TLS_HEADER_SIZE @@ -226,6 +228,7 @@ struct tls_context { u8 rx_conf:3; u8 zerocopy_sendfile:1; u8 rx_no_pad:1; + u16 tx_max_payload_len; int (*push_pending_record)(struct sock *sk, int flags); void (*sk_write_space)(struct sock *sk); diff --git a/include/uapi/linux/tls.h b/include/uapi/linux/tls.h index b66a800389cc..b8b9c42f848c 100644 --- a/include/uapi/linux/tls.h +++ b/include/uapi/linux/tls.h @@ -41,6 +41,7 @@ #define TLS_RX 2 /* Set receive parameters */ #define TLS_TX_ZEROCOPY_RO 3 /* TX zerocopy (only sendfile now) */ #define TLS_RX_EXPECT_NO_PAD 4 /* Attempt opportunistic zero-copy */ +#define TLS_TX_MAX_PAYLOAD_LEN 5 /* Maximum plaintext size */ /* Supported versions */ #define TLS_VERSION_MINOR(ver) ((ver) & 0xFF) @@ -194,6 +195,7 @@ enum { TLS_INFO_RXCONF, TLS_INFO_ZC_RO_TX, TLS_INFO_RX_NO_PAD, + TLS_INFO_TX_MAX_PAYLOAD_LEN, __TLS_INFO_MAX, }; #define TLS_INFO_MAX (__TLS_INFO_MAX - 1) diff --git a/net/tls/tls_device.c b/net/tls/tls_device.c index caa2b5d24622..4d29b390aed9 100644 --- a/net/tls/tls_device.c +++ b/net/tls/tls_device.c @@ -462,7 +462,7 @@ static int tls_push_data(struct sock *sk, /* TLS_HEADER_SIZE is not counted as part of the TLS record, and * we need to leave room for an authentication tag. */ - max_open_record_len = TLS_MAX_PAYLOAD_SIZE + + max_open_record_len = tls_ctx->tx_max_payload_len + prot->prepend_size; do { rc = tls_do_allocation(sk, ctx, pfrag, prot->prepend_size); diff --git a/net/tls/tls_main.c b/net/tls/tls_main.c index 39a2ab47fe72..56ce0bc8317b 100644 --- a/net/tls/tls_main.c +++ b/net/tls/tls_main.c @@ -541,6 +541,28 @@ static int do_tls_getsockopt_no_pad(struct sock *sk, char __user *optval, return 0; } +static int do_tls_getsockopt_tx_payload_len(struct sock *sk, char __user *optval, + int __user *optlen) +{ + struct tls_context *ctx = tls_get_ctx(sk); + u16 payload_len = ctx->tx_max_payload_len; + int len; + + if (get_user(len, optlen)) + return -EFAULT; + + if (len < sizeof(payload_len)) + return -EINVAL; + + if (put_user(sizeof(payload_len), optlen)) + return -EFAULT; + + if (copy_to_user(optval, &payload_len, sizeof(payload_len))) + return -EFAULT; + + return 0; +} + static int do_tls_getsockopt(struct sock *sk, int optname, char __user *optval, int __user *optlen) { @@ -560,6 +582,9 @@ static int do_tls_getsockopt(struct sock *sk, int optname, case TLS_RX_EXPECT_NO_PAD: rc = do_tls_getsockopt_no_pad(sk, optval, optlen); break; + case TLS_TX_MAX_PAYLOAD_LEN: + rc = do_tls_getsockopt_tx_payload_len(sk, optval, optlen); + break; default: rc = -ENOPROTOOPT; break; @@ -809,6 +834,32 @@ static int do_tls_setsockopt_no_pad(struct sock *sk, sockptr_t optval, return rc; } +static int do_tls_setsockopt_tx_payload_len(struct sock *sk, sockptr_t optval, + unsigned int optlen) +{ + struct tls_context *ctx = tls_get_ctx(sk); + struct tls_sw_context_tx *sw_ctx = tls_sw_ctx_tx(ctx); + u16 value; + bool tls_13 = ctx->prot_info.version == TLS_1_3_VERSION; + + if (sw_ctx && sw_ctx->open_rec) + return -EBUSY; + + if (sockptr_is_null(optval) || optlen != sizeof(value)) + return -EINVAL; + + if (copy_from_sockptr(&value, optval, sizeof(value))) + return -EFAULT; + + if (value < TLS_MIN_RECORD_SIZE_LIM - (tls_13 ? 1 : 0) || + value > TLS_MAX_PAYLOAD_SIZE) + return -EINVAL; + + ctx->tx_max_payload_len = value; + + return 0; +} + static int do_tls_setsockopt(struct sock *sk, int optname, sockptr_t optval, unsigned int optlen) { @@ -830,6 +881,11 @@ static int do_tls_setsockopt(struct sock *sk, int optname, sockptr_t optval, case TLS_RX_EXPECT_NO_PAD: rc = do_tls_setsockopt_no_pad(sk, optval, optlen); break; + case TLS_TX_MAX_PAYLOAD_LEN: + lock_sock(sk); + rc = do_tls_setsockopt_tx_payload_len(sk, optval, optlen); + release_sock(sk); + break; default: rc = -ENOPROTOOPT; break; @@ -1019,6 +1075,7 @@ static int tls_init(struct sock *sk) ctx->tx_conf = TLS_BASE; ctx->rx_conf = TLS_BASE; + ctx->tx_max_payload_len = TLS_MAX_PAYLOAD_SIZE; update_sk_prot(sk, ctx); out: write_unlock_bh(&sk->sk_callback_lock); @@ -1108,6 +1165,12 @@ static int tls_get_info(struct sock *sk, struct sk_buff *skb, bool net_admin) goto nla_failure; } + err = nla_put_u16(skb, TLS_INFO_TX_MAX_PAYLOAD_LEN, + ctx->tx_max_payload_len); + + if (err) + goto nla_failure; + rcu_read_unlock(); nla_nest_end(skb, start); return 0; @@ -1129,6 +1192,7 @@ static size_t tls_get_info_size(const struct sock *sk, bool net_admin) nla_total_size(sizeof(u16)) + /* TLS_INFO_TXCONF */ nla_total_size(0) + /* TLS_INFO_ZC_RO_TX */ nla_total_size(0) + /* TLS_INFO_RX_NO_PAD */ + nla_total_size(sizeof(u16)) + /* TLS_INFO_TX_MAX_PAYLOAD_LEN */ 0; return size; diff --git a/net/tls/tls_sw.c b/net/tls/tls_sw.c index d17135369980..9937d4c810f2 100644 --- a/net/tls/tls_sw.c +++ b/net/tls/tls_sw.c @@ -1079,7 +1079,7 @@ static int tls_sw_sendmsg_locked(struct sock *sk, struct msghdr *msg, orig_size = msg_pl->sg.size; full_record = false; try_to_copy = msg_data_left(msg); - record_room = TLS_MAX_PAYLOAD_SIZE - msg_pl->sg.size; + record_room = tls_ctx->tx_max_payload_len - msg_pl->sg.size; if (try_to_copy >= record_room) { try_to_copy = record_room; full_record = true; -- cgit v1.2.3 From feeaf1346f80ffb181b6f9b739628103aa73b067 Mon Sep 17 00:00:00 2001 From: Xu Kuohai Date: Sat, 18 Oct 2025 11:57:36 +0800 Subject: bpf: Add overwrite mode for BPF ring buffer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the BPF ring buffer is full, a new event cannot be recorded until one or more old events are consumed to make enough space for it. In cases such as fault diagnostics, where recent events are more useful than older ones, this mechanism may lead to critical events being lost. So add overwrite mode for BPF ring buffer to address it. In this mode, the new event overwrites the oldest event when the buffer is full. The basic idea is as follows: 1. producer_pos tracks the next position to record new event. When there is enough free space, producer_pos is simply advanced by producer to make space for the new event. 2. To avoid waiting for consumer when the buffer is full, a new variable, overwrite_pos, is introduced for producer. It points to the oldest event committed in the buffer. It is advanced by producer to discard one or more oldest events to make space for the new event when the buffer is full. 3. pending_pos tracks the oldest event to be committed. pending_pos is never passed by producer_pos, so multiple producers never write to the same position at the same time. The following example diagrams show how it works in a 4096-byte ring buffer. 1. At first, {producer,overwrite,pending,consumer}_pos are all set to 0. 0 512 1024 1536 2048 2560 3072 3584 4096 +-----------------------------------------------------------------------+ | | | | | | +-----------------------------------------------------------------------+ ^ | | producer_pos = 0 overwrite_pos = 0 pending_pos = 0 consumer_pos = 0 2. Now reserve a 512-byte event A. There is enough free space, so A is allocated at offset 0. And producer_pos is advanced to 512, the end of A. Since A is not submitted, the BUSY bit is set. 0 512 1024 1536 2048 2560 3072 3584 4096 +-----------------------------------------------------------------------+ | | | | A | | | [BUSY] | | +-----------------------------------------------------------------------+ ^ ^ | | | | | producer_pos = 512 | overwrite_pos = 0 pending_pos = 0 consumer_pos = 0 3. Reserve event B, size 1024. B is allocated at offset 512 with BUSY bit set, and producer_pos is advanced to the end of B. 0 512 1024 1536 2048 2560 3072 3584 4096 +-----------------------------------------------------------------------+ | | | | | A | B | | | [BUSY] | [BUSY] | | +-----------------------------------------------------------------------+ ^ ^ | | | | | producer_pos = 1536 | overwrite_pos = 0 pending_pos = 0 consumer_pos = 0 4. Reserve event C, size 2048. C is allocated at offset 1536, and producer_pos is advanced to 3584. 0 512 1024 1536 2048 2560 3072 3584 4096 +-----------------------------------------------------------------------+ | | | | | | A | B | C | | | [BUSY] | [BUSY] | [BUSY] | | +-----------------------------------------------------------------------+ ^ ^ | | | | | producer_pos = 3584 | overwrite_pos = 0 pending_pos = 0 consumer_pos = 0 5. Submit event A. The BUSY bit of A is cleared. B becomes the oldest event to be committed, so pending_pos is advanced to 512, the start of B. 0 512 1024 1536 2048 2560 3072 3584 4096 +-----------------------------------------------------------------------+ | | | | | | A | B | C | | | | [BUSY] | [BUSY] | | +-----------------------------------------------------------------------+ ^ ^ ^ | | | | | | | pending_pos = 512 producer_pos = 3584 | overwrite_pos = 0 consumer_pos = 0 6. Submit event B. The BUSY bit of B is cleared, and pending_pos is advanced to the start of C, which is now the oldest event to be committed. 0 512 1024 1536 2048 2560 3072 3584 4096 +-----------------------------------------------------------------------+ | | | | | | A | B | C | | | | | [BUSY] | | +-----------------------------------------------------------------------+ ^ ^ ^ | | | | | | | pending_pos = 1536 producer_pos = 3584 | overwrite_pos = 0 consumer_pos = 0 7. Reserve event D, size 1536 (3 * 512). There are 2048 bytes not being written between producer_pos (currently 3584) and pending_pos, so D is allocated at offset 3584, and producer_pos is advanced by 1536 (from 3584 to 5120). Since event D will overwrite all bytes of event A and the first 512 bytes of event B, overwrite_pos is advanced to the start of event C, the oldest event that is not overwritten. 0 512 1024 1536 2048 2560 3072 3584 4096 +-----------------------------------------------------------------------+ | | | | | | D End | | C | D Begin| | [BUSY] | | [BUSY] | [BUSY] | +-----------------------------------------------------------------------+ ^ ^ ^ | | | | | pending_pos = 1536 | | overwrite_pos = 1536 | | | producer_pos=5120 | consumer_pos = 0 8. Reserve event E, size 1024. Although there are 512 bytes not being written between producer_pos and pending_pos, E cannot be reserved, as it would overwrite the first 512 bytes of event C, which is still being written. 9. Submit event C and D. pending_pos is advanced to the end of D. 0 512 1024 1536 2048 2560 3072 3584 4096 +-----------------------------------------------------------------------+ | | | | | | D End | | C | D Begin| | | | | | +-----------------------------------------------------------------------+ ^ ^ ^ | | | | | overwrite_pos = 1536 | | | producer_pos=5120 | pending_pos=5120 | consumer_pos = 0 The performance data for overwrite mode will be provided in a follow-up patch that adds overwrite-mode benchmarks. A sample of performance data for non-overwrite mode, collected on an x86_64 CPU and an arm64 CPU, before and after this patch, is shown below. As we can see, no obvious performance regression occurs. - x86_64 (AMD EPYC 9654) Before: Ringbuf, multi-producer contention ================================== rb-libbpf nr_prod 1 11.623 ± 0.027M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 2 15.812 ± 0.014M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 3 7.871 ± 0.003M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 4 6.703 ± 0.001M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 8 2.896 ± 0.002M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 12 2.054 ± 0.002M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 16 1.864 ± 0.002M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 20 1.580 ± 0.002M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 24 1.484 ± 0.002M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 28 1.369 ± 0.002M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 32 1.316 ± 0.001M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 36 1.272 ± 0.002M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 40 1.239 ± 0.001M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 44 1.226 ± 0.002M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 48 1.213 ± 0.001M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 52 1.193 ± 0.001M/s (drops 0.000 ± 0.000M/s) After: Ringbuf, multi-producer contention ================================== rb-libbpf nr_prod 1 11.845 ± 0.036M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 2 15.889 ± 0.006M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 3 8.155 ± 0.002M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 4 6.708 ± 0.001M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 8 2.918 ± 0.001M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 12 2.065 ± 0.002M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 16 1.870 ± 0.002M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 20 1.582 ± 0.002M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 24 1.482 ± 0.001M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 28 1.372 ± 0.002M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 32 1.323 ± 0.002M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 36 1.264 ± 0.001M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 40 1.236 ± 0.002M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 44 1.209 ± 0.002M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 48 1.189 ± 0.001M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 52 1.165 ± 0.002M/s (drops 0.000 ± 0.000M/s) - arm64 (HiSilicon Kunpeng 920) Before: Ringbuf, multi-producer contention ================================== rb-libbpf nr_prod 1 11.310 ± 0.623M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 2 9.947 ± 0.004M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 3 6.634 ± 0.011M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 4 4.502 ± 0.003M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 8 3.888 ± 0.003M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 12 3.372 ± 0.005M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 16 3.189 ± 0.010M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 20 2.998 ± 0.006M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 24 3.086 ± 0.018M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 28 2.845 ± 0.004M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 32 2.815 ± 0.008M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 36 2.771 ± 0.009M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 40 2.814 ± 0.011M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 44 2.752 ± 0.006M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 48 2.695 ± 0.006M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 52 2.710 ± 0.006M/s (drops 0.000 ± 0.000M/s) After: Ringbuf, multi-producer contention ================================== rb-libbpf nr_prod 1 11.283 ± 0.550M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 2 9.993 ± 0.003M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 3 6.898 ± 0.006M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 4 5.257 ± 0.001M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 8 3.830 ± 0.005M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 12 3.528 ± 0.013M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 16 3.265 ± 0.018M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 20 2.990 ± 0.007M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 24 2.929 ± 0.014M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 28 2.898 ± 0.010M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 32 2.818 ± 0.006M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 36 2.789 ± 0.012M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 40 2.770 ± 0.006M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 44 2.651 ± 0.007M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 48 2.669 ± 0.005M/s (drops 0.000 ± 0.000M/s) rb-libbpf nr_prod 52 2.695 ± 0.009M/s (drops 0.000 ± 0.000M/s) Signed-off-by: Xu Kuohai Signed-off-by: Andrii Nakryiko Link: https://lore.kernel.org/bpf/20251018035738.4039621-2-xukuohai@huaweicloud.com --- include/uapi/linux/bpf.h | 4 ++ kernel/bpf/ringbuf.c | 114 ++++++++++++++++++++++++++++++++++------- tools/include/uapi/linux/bpf.h | 4 ++ 3 files changed, 103 insertions(+), 19 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index 77edd0253989..1d73f165394d 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -1430,6 +1430,9 @@ enum { /* Do not translate kernel bpf_arena pointers to user pointers */ BPF_F_NO_USER_CONV = (1U << 18), + +/* Enable BPF ringbuf overwrite mode */ + BPF_F_RB_OVERWRITE = (1U << 19), }; /* Flags for BPF_PROG_QUERY. */ @@ -6231,6 +6234,7 @@ enum { BPF_RB_RING_SIZE = 1, BPF_RB_CONS_POS = 2, BPF_RB_PROD_POS = 3, + BPF_RB_OVERWRITE_POS = 4, }; /* BPF ring buffer constants */ diff --git a/kernel/bpf/ringbuf.c b/kernel/bpf/ringbuf.c index 719d73299397..cbfa109e907e 100644 --- a/kernel/bpf/ringbuf.c +++ b/kernel/bpf/ringbuf.c @@ -13,7 +13,7 @@ #include #include -#define RINGBUF_CREATE_FLAG_MASK (BPF_F_NUMA_NODE) +#define RINGBUF_CREATE_FLAG_MASK (BPF_F_NUMA_NODE | BPF_F_RB_OVERWRITE) /* non-mmap()'able part of bpf_ringbuf (everything up to consumer page) */ #define RINGBUF_PGOFF \ @@ -30,6 +30,7 @@ struct bpf_ringbuf { u64 mask; struct page **pages; int nr_pages; + bool overwrite_mode; rqspinlock_t spinlock ____cacheline_aligned_in_smp; /* For user-space producer ring buffers, an atomic_t busy bit is used * to synchronize access to the ring buffers in the kernel, rather than @@ -73,6 +74,7 @@ struct bpf_ringbuf { unsigned long consumer_pos __aligned(PAGE_SIZE); unsigned long producer_pos __aligned(PAGE_SIZE); unsigned long pending_pos; + unsigned long overwrite_pos; /* position after the last overwritten record */ char data[] __aligned(PAGE_SIZE); }; @@ -166,7 +168,7 @@ static void bpf_ringbuf_notify(struct irq_work *work) * considering that the maximum value of data_sz is (4GB - 1), there * will be no overflow, so just note the size limit in the comments. */ -static struct bpf_ringbuf *bpf_ringbuf_alloc(size_t data_sz, int numa_node) +static struct bpf_ringbuf *bpf_ringbuf_alloc(size_t data_sz, int numa_node, bool overwrite_mode) { struct bpf_ringbuf *rb; @@ -183,17 +185,25 @@ static struct bpf_ringbuf *bpf_ringbuf_alloc(size_t data_sz, int numa_node) rb->consumer_pos = 0; rb->producer_pos = 0; rb->pending_pos = 0; + rb->overwrite_mode = overwrite_mode; return rb; } static struct bpf_map *ringbuf_map_alloc(union bpf_attr *attr) { + bool overwrite_mode = false; struct bpf_ringbuf_map *rb_map; if (attr->map_flags & ~RINGBUF_CREATE_FLAG_MASK) return ERR_PTR(-EINVAL); + if (attr->map_flags & BPF_F_RB_OVERWRITE) { + if (attr->map_type != BPF_MAP_TYPE_RINGBUF) + return ERR_PTR(-EINVAL); + overwrite_mode = true; + } + if (attr->key_size || attr->value_size || !is_power_of_2(attr->max_entries) || !PAGE_ALIGNED(attr->max_entries)) @@ -205,7 +215,7 @@ static struct bpf_map *ringbuf_map_alloc(union bpf_attr *attr) bpf_map_init_from_attr(&rb_map->map, attr); - rb_map->rb = bpf_ringbuf_alloc(attr->max_entries, rb_map->map.numa_node); + rb_map->rb = bpf_ringbuf_alloc(attr->max_entries, rb_map->map.numa_node, overwrite_mode); if (!rb_map->rb) { bpf_map_area_free(rb_map); return ERR_PTR(-ENOMEM); @@ -293,13 +303,26 @@ static int ringbuf_map_mmap_user(struct bpf_map *map, struct vm_area_struct *vma return remap_vmalloc_range(vma, rb_map->rb, vma->vm_pgoff + RINGBUF_PGOFF); } +/* + * Return an estimate of the available data in the ring buffer. + * Note: the returned value can exceed the actual ring buffer size because the + * function is not synchronized with the producer. The producer acquires the + * ring buffer's spinlock, but this function does not. + */ static unsigned long ringbuf_avail_data_sz(struct bpf_ringbuf *rb) { - unsigned long cons_pos, prod_pos; + unsigned long cons_pos, prod_pos, over_pos; cons_pos = smp_load_acquire(&rb->consumer_pos); - prod_pos = smp_load_acquire(&rb->producer_pos); - return prod_pos - cons_pos; + + if (unlikely(rb->overwrite_mode)) { + over_pos = smp_load_acquire(&rb->overwrite_pos); + prod_pos = smp_load_acquire(&rb->producer_pos); + return prod_pos - max(cons_pos, over_pos); + } else { + prod_pos = smp_load_acquire(&rb->producer_pos); + return prod_pos - cons_pos; + } } static u32 ringbuf_total_data_sz(const struct bpf_ringbuf *rb) @@ -402,11 +425,43 @@ bpf_ringbuf_restore_from_rec(struct bpf_ringbuf_hdr *hdr) return (void*)((addr & PAGE_MASK) - off); } +static bool bpf_ringbuf_has_space(const struct bpf_ringbuf *rb, + unsigned long new_prod_pos, + unsigned long cons_pos, + unsigned long pend_pos) +{ + /* + * No space if oldest not yet committed record until the newest + * record span more than (ringbuf_size - 1). + */ + if (new_prod_pos - pend_pos > rb->mask) + return false; + + /* Ok, we have space in overwrite mode */ + if (unlikely(rb->overwrite_mode)) + return true; + + /* + * No space if producer position advances more than (ringbuf_size - 1) + * ahead of consumer position when not in overwrite mode. + */ + if (new_prod_pos - cons_pos > rb->mask) + return false; + + return true; +} + +static u32 bpf_ringbuf_round_up_hdr_len(u32 hdr_len) +{ + hdr_len &= ~BPF_RINGBUF_DISCARD_BIT; + return round_up(hdr_len + BPF_RINGBUF_HDR_SZ, 8); +} + static void *__bpf_ringbuf_reserve(struct bpf_ringbuf *rb, u64 size) { - unsigned long cons_pos, prod_pos, new_prod_pos, pend_pos, flags; + unsigned long cons_pos, prod_pos, new_prod_pos, pend_pos, over_pos, flags; struct bpf_ringbuf_hdr *hdr; - u32 len, pg_off, tmp_size, hdr_len; + u32 len, pg_off, hdr_len; if (unlikely(size > RINGBUF_MAX_RECORD_SZ)) return NULL; @@ -429,24 +484,43 @@ static void *__bpf_ringbuf_reserve(struct bpf_ringbuf *rb, u64 size) hdr_len = READ_ONCE(hdr->len); if (hdr_len & BPF_RINGBUF_BUSY_BIT) break; - tmp_size = hdr_len & ~BPF_RINGBUF_DISCARD_BIT; - tmp_size = round_up(tmp_size + BPF_RINGBUF_HDR_SZ, 8); - pend_pos += tmp_size; + pend_pos += bpf_ringbuf_round_up_hdr_len(hdr_len); } rb->pending_pos = pend_pos; - /* check for out of ringbuf space: - * - by ensuring producer position doesn't advance more than - * (ringbuf_size - 1) ahead - * - by ensuring oldest not yet committed record until newest - * record does not span more than (ringbuf_size - 1) - */ - if (new_prod_pos - cons_pos > rb->mask || - new_prod_pos - pend_pos > rb->mask) { + if (!bpf_ringbuf_has_space(rb, new_prod_pos, cons_pos, pend_pos)) { raw_res_spin_unlock_irqrestore(&rb->spinlock, flags); return NULL; } + /* + * In overwrite mode, advance overwrite_pos when the ring buffer is full. + * The key points are to stay on record boundaries and consume enough records + * to fit the new one. + */ + if (unlikely(rb->overwrite_mode)) { + over_pos = rb->overwrite_pos; + while (new_prod_pos - over_pos > rb->mask) { + hdr = (void *)rb->data + (over_pos & rb->mask); + hdr_len = READ_ONCE(hdr->len); + /* + * The bpf_ringbuf_has_space() check above ensures we won’t + * step over a record currently being worked on by another + * producer. + */ + over_pos += bpf_ringbuf_round_up_hdr_len(hdr_len); + } + /* + * smp_store_release(&rb->producer_pos, new_prod_pos) at + * the end of the function ensures that when consumer sees + * the updated rb->producer_pos, it always sees the updated + * rb->overwrite_pos, so when consumer reads overwrite_pos + * after smp_load_acquire(r->producer_pos), the overwrite_pos + * will always be valid. + */ + WRITE_ONCE(rb->overwrite_pos, over_pos); + } + hdr = (void *)rb->data + (prod_pos & rb->mask); pg_off = bpf_ringbuf_rec_pg_off(rb, hdr); hdr->len = size | BPF_RINGBUF_BUSY_BIT; @@ -576,6 +650,8 @@ BPF_CALL_2(bpf_ringbuf_query, struct bpf_map *, map, u64, flags) return smp_load_acquire(&rb->consumer_pos); case BPF_RB_PROD_POS: return smp_load_acquire(&rb->producer_pos); + case BPF_RB_OVERWRITE_POS: + return smp_load_acquire(&rb->overwrite_pos); default: return 0; } diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h index 77edd0253989..1d73f165394d 100644 --- a/tools/include/uapi/linux/bpf.h +++ b/tools/include/uapi/linux/bpf.h @@ -1430,6 +1430,9 @@ enum { /* Do not translate kernel bpf_arena pointers to user pointers */ BPF_F_NO_USER_CONV = (1U << 18), + +/* Enable BPF ringbuf overwrite mode */ + BPF_F_RB_OVERWRITE = (1U << 19), }; /* Flags for BPF_PROG_QUERY. */ @@ -6231,6 +6234,7 @@ enum { BPF_RB_RING_SIZE = 1, BPF_RB_CONS_POS = 2, BPF_RB_PROD_POS = 3, + BPF_RB_OVERWRITE_POS = 4, }; /* BPF ring buffer constants */ -- cgit v1.2.3 From f74ee32963f1b74865fe679e2475450434fea51c Mon Sep 17 00:00:00 2001 From: Qinxin Xia Date: Tue, 28 Oct 2025 20:09:00 +0800 Subject: tools/dma: move dma_map_benchmark from selftests to tools/dma dma_map_benchmark is a standalone developer tool rather than an automated selftest. It has no pass/fail criteria, expects manual invocation, and is built as a normal userspace binary. Move it to tools/dma/ and add a minimal Makefile. Suggested-by: Marek Szyprowski Suggested-by: Barry Song Signed-off-by: Qinxin Xia Acked-by: Barry Song Signed-off-by: Marek Szyprowski Link: https://lore.kernel.org/r/20251028120900.2265511-3-xiaqinxin@huawei.com --- include/linux/map_benchmark.h | 32 ------ include/uapi/linux/map_benchmark.h | 35 +++++++ kernel/dma/map_benchmark.c | 2 +- tools/Makefile | 13 +-- tools/dma/.gitignore | 3 + tools/dma/Makefile | 55 ++++++++++ tools/dma/config | 1 + tools/dma/dma_map_benchmark.c | 127 +++++++++++++++++++++++ tools/testing/selftests/dma/Makefile | 7 -- tools/testing/selftests/dma/config | 1 - tools/testing/selftests/dma/dma_map_benchmark.c | 128 ------------------------ 11 files changed, 229 insertions(+), 175 deletions(-) delete mode 100644 include/linux/map_benchmark.h create mode 100644 include/uapi/linux/map_benchmark.h create mode 100644 tools/dma/.gitignore create mode 100644 tools/dma/Makefile create mode 100644 tools/dma/config create mode 100644 tools/dma/dma_map_benchmark.c delete mode 100644 tools/testing/selftests/dma/Makefile delete mode 100644 tools/testing/selftests/dma/config delete mode 100644 tools/testing/selftests/dma/dma_map_benchmark.c (limited to 'include/uapi/linux') diff --git a/include/linux/map_benchmark.h b/include/linux/map_benchmark.h deleted file mode 100644 index 48e2ff95332f..000000000000 --- a/include/linux/map_benchmark.h +++ /dev/null @@ -1,32 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Copyright (C) 2022 HiSilicon Limited. - */ - -#ifndef _KERNEL_DMA_BENCHMARK_H -#define _KERNEL_DMA_BENCHMARK_H - -#define DMA_MAP_BENCHMARK _IOWR('d', 1, struct map_benchmark) -#define DMA_MAP_MAX_THREADS 1024 -#define DMA_MAP_MAX_SECONDS 300 -#define DMA_MAP_MAX_TRANS_DELAY (10 * NSEC_PER_MSEC) - -#define DMA_MAP_BIDIRECTIONAL 0 -#define DMA_MAP_TO_DEVICE 1 -#define DMA_MAP_FROM_DEVICE 2 - -struct map_benchmark { - __u64 avg_map_100ns; /* average map latency in 100ns */ - __u64 map_stddev; /* standard deviation of map latency */ - __u64 avg_unmap_100ns; /* as above */ - __u64 unmap_stddev; - __u32 threads; /* how many threads will do map/unmap in parallel */ - __u32 seconds; /* how long the test will last */ - __s32 node; /* which numa node this benchmark will run on */ - __u32 dma_bits; /* DMA addressing capability */ - __u32 dma_dir; /* DMA data direction */ - __u32 dma_trans_ns; /* time for DMA transmission in ns */ - __u32 granule; /* how many PAGE_SIZE will do map/unmap once a time */ - __u8 expansion[76]; /* For future use */ -}; -#endif /* _KERNEL_DMA_BENCHMARK_H */ diff --git a/include/uapi/linux/map_benchmark.h b/include/uapi/linux/map_benchmark.h new file mode 100644 index 000000000000..c2d91088a40d --- /dev/null +++ b/include/uapi/linux/map_benchmark.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ +/* + * Copyright (C) 2022-2025 HiSilicon Limited. + */ + +#ifndef _UAPI_DMA_BENCHMARK_H +#define _UAPI_DMA_BENCHMARK_H + +#include + +#define DMA_MAP_BENCHMARK _IOWR('d', 1, struct map_benchmark) +#define DMA_MAP_MAX_THREADS 1024 +#define DMA_MAP_MAX_SECONDS 300 +#define DMA_MAP_MAX_TRANS_DELAY (10 * NSEC_PER_MSEC) + +#define DMA_MAP_BIDIRECTIONAL 0 +#define DMA_MAP_TO_DEVICE 1 +#define DMA_MAP_FROM_DEVICE 2 + +struct map_benchmark { + __u64 avg_map_100ns; /* average map latency in 100ns */ + __u64 map_stddev; /* standard deviation of map latency */ + __u64 avg_unmap_100ns; /* as above */ + __u64 unmap_stddev; + __u32 threads; /* how many threads will do map/unmap in parallel */ + __u32 seconds; /* how long the test will last */ + __s32 node; /* which numa node this benchmark will run on */ + __u32 dma_bits; /* DMA addressing capability */ + __u32 dma_dir; /* DMA data direction */ + __u32 dma_trans_ns; /* time for DMA transmission in ns */ + __u32 granule; /* how many PAGE_SIZE will do map/unmap once a time */ + __u8 expansion[76]; /* For future use */ +}; + +#endif /* _UAPI_DMA_BENCHMARK_H */ diff --git a/kernel/dma/map_benchmark.c b/kernel/dma/map_benchmark.c index cc19a3efea89..794041a39e65 100644 --- a/kernel/dma/map_benchmark.c +++ b/kernel/dma/map_benchmark.c @@ -11,13 +11,13 @@ #include #include #include -#include #include #include #include #include #include #include +#include struct map_benchmark_data { struct map_benchmark bparam; diff --git a/tools/Makefile b/tools/Makefile index c31cbbd12c45..cb40961a740f 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -14,6 +14,7 @@ help: @echo ' counter - counter tools' @echo ' cpupower - a tool for all things x86 CPU power' @echo ' debugging - tools for debugging' + @echo ' dma - tools for DMA mapping' @echo ' firewire - the userspace part of nosy, an IEEE-1394 traffic sniffer' @echo ' firmware - Firmware tools' @echo ' freefall - laptop accelerometer program for disk protection' @@ -69,7 +70,7 @@ acpi: FORCE cpupower: FORCE $(call descend,power/$@) -counter firewire hv guest bootconfig spi usb virtio mm bpf iio gpio objtool leds wmi firmware debugging tracing: FORCE +counter dma firewire hv guest bootconfig spi usb virtio mm bpf iio gpio objtool leds wmi firmware debugging tracing: FORCE $(call descend,$@) bpf/%: FORCE @@ -122,7 +123,7 @@ kvm_stat: FORCE ynl: FORCE $(call descend,net/ynl) -all: acpi counter cpupower gpio hv firewire \ +all: acpi counter cpupower dma gpio hv firewire \ perf selftests bootconfig spi turbostat usb \ virtio mm bpf x86_energy_perf_policy \ tmon freefall iio objtool kvm_stat wmi \ @@ -134,7 +135,7 @@ acpi_install: cpupower_install: $(call descend,power/$(@:_install=),install) -counter_install firewire_install gpio_install hv_install iio_install perf_install bootconfig_install spi_install usb_install virtio_install mm_install bpf_install objtool_install wmi_install debugging_install tracing_install: +counter_install dma_install firewire_install gpio_install hv_install iio_install perf_install bootconfig_install spi_install usb_install virtio_install mm_install bpf_install objtool_install wmi_install debugging_install tracing_install: $(call descend,$(@:_install=),install) selftests_install: @@ -164,7 +165,7 @@ kvm_stat_install: ynl_install: $(call descend,net/$(@:_install=),install) -install: acpi_install counter_install cpupower_install gpio_install \ +install: acpi_install counter_install cpupower_install dma_install gpio_install \ hv_install firewire_install iio_install \ perf_install selftests_install turbostat_install usb_install \ virtio_install mm_install bpf_install x86_energy_perf_policy_install \ @@ -178,7 +179,7 @@ acpi_clean: cpupower_clean: $(call descend,power/cpupower,clean) -counter_clean hv_clean firewire_clean bootconfig_clean spi_clean usb_clean virtio_clean mm_clean wmi_clean bpf_clean iio_clean gpio_clean objtool_clean leds_clean firmware_clean debugging_clean tracing_clean: +counter_clean dma_clean hv_clean firewire_clean bootconfig_clean spi_clean usb_clean virtio_clean mm_clean wmi_clean bpf_clean iio_clean gpio_clean objtool_clean leds_clean firmware_clean debugging_clean tracing_clean: $(call descend,$(@:_clean=),clean) libapi_clean: @@ -224,7 +225,7 @@ build_clean: ynl_clean: $(call descend,net/$(@:_clean=),clean) -clean: acpi_clean counter_clean cpupower_clean hv_clean firewire_clean \ +clean: acpi_clean counter_clean cpupower_clean dma_clean hv_clean firewire_clean \ perf_clean selftests_clean turbostat_clean bootconfig_clean spi_clean usb_clean virtio_clean \ mm_clean bpf_clean iio_clean x86_energy_perf_policy_clean tmon_clean \ freefall_clean build_clean libbpf_clean libsubcmd_clean \ diff --git a/tools/dma/.gitignore b/tools/dma/.gitignore new file mode 100644 index 000000000000..94b68cf4147b --- /dev/null +++ b/tools/dma/.gitignore @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only +dma_map_benchmark +include/linux/map_benchmark.h diff --git a/tools/dma/Makefile b/tools/dma/Makefile new file mode 100644 index 000000000000..e4abf37bf020 --- /dev/null +++ b/tools/dma/Makefile @@ -0,0 +1,55 @@ +# SPDX-License-Identifier: GPL-2.0 +include ../scripts/Makefile.include + +bindir ?= /usr/bin + +# This will work when dma is built in tools env. where srctree +# isn't set and when invoked from selftests build, where srctree +# is set to ".". building_out_of_srctree is undefined for in srctree +# builds +ifndef building_out_of_srctree +srctree := $(patsubst %/,%,$(dir $(CURDIR))) +srctree := $(patsubst %/,%,$(dir $(srctree))) +endif + +# Do not use make's built-in rules +# (this improves performance and avoids hard-to-debug behaviour); +MAKEFLAGS += -r + +override CFLAGS += -O2 -Wall -g -D_GNU_SOURCE -I$(OUTPUT)include + +ALL_TARGETS := dma_map_benchmark +ALL_PROGRAMS := $(patsubst %,$(OUTPUT)%,$(ALL_TARGETS)) + +all: $(ALL_PROGRAMS) + +export srctree OUTPUT CC LD CFLAGS +include $(srctree)/tools/build/Makefile.include + +# +# We need the following to be outside of kernel tree +# +$(OUTPUT)include/linux/map_benchmark.h: ../../include/uapi/linux/map_benchmark.h + mkdir -p $(OUTPUT)include/linux 2>&1 || true + ln -sf $(CURDIR)/../../include/uapi/linux/map_benchmark.h $@ + +prepare: $(OUTPUT)include/linux/map_benchmark.h + +FORCE: + +DMA_MAP_BENCHMARK = dma_map_benchmark +$(DMA_MAP_BENCHMARK): prepare FORCE + $(CC) $(CFLAGS) $(DMA_MAP_BENCHMARK).c -o $(DMA_MAP_BENCHMARK) + +clean: + rm -f $(ALL_PROGRAMS) + rm -rf $(OUTPUT)include + find $(or $(OUTPUT),.) -name '*.o' -delete -o -name '\.*.d' -delete -o -name '\.*.cmd' -delete + +install: $(ALL_PROGRAMS) + install -d -m 755 $(DESTDIR)$(bindir); \ + for program in $(ALL_PROGRAMS); do \ + install $$program $(DESTDIR)$(bindir); \ + done + +.PHONY: all install clean prepare FORCE diff --git a/tools/dma/config b/tools/dma/config new file mode 100644 index 000000000000..6102ee3c43cd --- /dev/null +++ b/tools/dma/config @@ -0,0 +1 @@ +CONFIG_DMA_MAP_BENCHMARK=y diff --git a/tools/dma/dma_map_benchmark.c b/tools/dma/dma_map_benchmark.c new file mode 100644 index 000000000000..5474a450863c --- /dev/null +++ b/tools/dma/dma_map_benchmark.c @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2020 HiSilicon Limited. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define NSEC_PER_MSEC 1000000L + +static char *directions[] = { + "BIDIRECTIONAL", + "TO_DEVICE", + "FROM_DEVICE", +}; + +int main(int argc, char **argv) +{ + struct map_benchmark map; + int fd, opt; + /* default single thread, run 20 seconds on NUMA_NO_NODE */ + int threads = 1, seconds = 20, node = -1; + /* default dma mask 32bit, bidirectional DMA */ + int bits = 32, xdelay = 0, dir = DMA_MAP_BIDIRECTIONAL; + /* default granule 1 PAGESIZE */ + int granule = 1; + + int cmd = DMA_MAP_BENCHMARK; + + while ((opt = getopt(argc, argv, "t:s:n:b:d:x:g:")) != -1) { + switch (opt) { + case 't': + threads = atoi(optarg); + break; + case 's': + seconds = atoi(optarg); + break; + case 'n': + node = atoi(optarg); + break; + case 'b': + bits = atoi(optarg); + break; + case 'd': + dir = atoi(optarg); + break; + case 'x': + xdelay = atoi(optarg); + break; + case 'g': + granule = atoi(optarg); + break; + default: + return -1; + } + } + + if (threads <= 0 || threads > DMA_MAP_MAX_THREADS) { + fprintf(stderr, "invalid number of threads, must be in 1-%d\n", + DMA_MAP_MAX_THREADS); + exit(1); + } + + if (seconds <= 0 || seconds > DMA_MAP_MAX_SECONDS) { + fprintf(stderr, "invalid number of seconds, must be in 1-%d\n", + DMA_MAP_MAX_SECONDS); + exit(1); + } + + if (xdelay < 0 || xdelay > DMA_MAP_MAX_TRANS_DELAY) { + fprintf(stderr, "invalid transmit delay, must be in 0-%ld\n", + DMA_MAP_MAX_TRANS_DELAY); + exit(1); + } + + /* suppose the mininum DMA zone is 1MB in the world */ + if (bits < 20 || bits > 64) { + fprintf(stderr, "invalid dma mask bit, must be in 20-64\n"); + exit(1); + } + + if (dir != DMA_MAP_BIDIRECTIONAL && dir != DMA_MAP_TO_DEVICE && + dir != DMA_MAP_FROM_DEVICE) { + fprintf(stderr, "invalid dma direction\n"); + exit(1); + } + + if (granule < 1 || granule > 1024) { + fprintf(stderr, "invalid granule size\n"); + exit(1); + } + + fd = open("/sys/kernel/debug/dma_map_benchmark", O_RDWR); + if (fd == -1) { + perror("open"); + exit(1); + } + + memset(&map, 0, sizeof(map)); + map.seconds = seconds; + map.threads = threads; + map.node = node; + map.dma_bits = bits; + map.dma_dir = dir; + map.dma_trans_ns = xdelay; + map.granule = granule; + + if (ioctl(fd, cmd, &map)) { + perror("ioctl"); + exit(1); + } + + printf("dma mapping benchmark: threads:%d seconds:%d node:%d dir:%s granule: %d\n", + threads, seconds, node, dir[directions], granule); + printf("average map latency(us):%.1f standard deviation:%.1f\n", + map.avg_map_100ns/10.0, map.map_stddev/10.0); + printf("average unmap latency(us):%.1f standard deviation:%.1f\n", + map.avg_unmap_100ns/10.0, map.unmap_stddev/10.0); + + return 0; +} diff --git a/tools/testing/selftests/dma/Makefile b/tools/testing/selftests/dma/Makefile deleted file mode 100644 index cd8c5ece1cba..000000000000 --- a/tools/testing/selftests/dma/Makefile +++ /dev/null @@ -1,7 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -CFLAGS += -I../../../../usr/include/ -CFLAGS += -I../../../../include/ - -TEST_GEN_PROGS := dma_map_benchmark - -include ../lib.mk diff --git a/tools/testing/selftests/dma/config b/tools/testing/selftests/dma/config deleted file mode 100644 index 6102ee3c43cd..000000000000 --- a/tools/testing/selftests/dma/config +++ /dev/null @@ -1 +0,0 @@ -CONFIG_DMA_MAP_BENCHMARK=y diff --git a/tools/testing/selftests/dma/dma_map_benchmark.c b/tools/testing/selftests/dma/dma_map_benchmark.c deleted file mode 100644 index b12f1f9babf8..000000000000 --- a/tools/testing/selftests/dma/dma_map_benchmark.c +++ /dev/null @@ -1,128 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (C) 2020 HiSilicon Limited. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define NSEC_PER_MSEC 1000000L - -static char *directions[] = { - "BIDIRECTIONAL", - "TO_DEVICE", - "FROM_DEVICE", -}; - -int main(int argc, char **argv) -{ - struct map_benchmark map; - int fd, opt; - /* default single thread, run 20 seconds on NUMA_NO_NODE */ - int threads = 1, seconds = 20, node = -1; - /* default dma mask 32bit, bidirectional DMA */ - int bits = 32, xdelay = 0, dir = DMA_MAP_BIDIRECTIONAL; - /* default granule 1 PAGESIZE */ - int granule = 1; - - int cmd = DMA_MAP_BENCHMARK; - - while ((opt = getopt(argc, argv, "t:s:n:b:d:x:g:")) != -1) { - switch (opt) { - case 't': - threads = atoi(optarg); - break; - case 's': - seconds = atoi(optarg); - break; - case 'n': - node = atoi(optarg); - break; - case 'b': - bits = atoi(optarg); - break; - case 'd': - dir = atoi(optarg); - break; - case 'x': - xdelay = atoi(optarg); - break; - case 'g': - granule = atoi(optarg); - break; - default: - return -1; - } - } - - if (threads <= 0 || threads > DMA_MAP_MAX_THREADS) { - fprintf(stderr, "invalid number of threads, must be in 1-%d\n", - DMA_MAP_MAX_THREADS); - exit(1); - } - - if (seconds <= 0 || seconds > DMA_MAP_MAX_SECONDS) { - fprintf(stderr, "invalid number of seconds, must be in 1-%d\n", - DMA_MAP_MAX_SECONDS); - exit(1); - } - - if (xdelay < 0 || xdelay > DMA_MAP_MAX_TRANS_DELAY) { - fprintf(stderr, "invalid transmit delay, must be in 0-%ld\n", - DMA_MAP_MAX_TRANS_DELAY); - exit(1); - } - - /* suppose the mininum DMA zone is 1MB in the world */ - if (bits < 20 || bits > 64) { - fprintf(stderr, "invalid dma mask bit, must be in 20-64\n"); - exit(1); - } - - if (dir != DMA_MAP_BIDIRECTIONAL && dir != DMA_MAP_TO_DEVICE && - dir != DMA_MAP_FROM_DEVICE) { - fprintf(stderr, "invalid dma direction\n"); - exit(1); - } - - if (granule < 1 || granule > 1024) { - fprintf(stderr, "invalid granule size\n"); - exit(1); - } - - fd = open("/sys/kernel/debug/dma_map_benchmark", O_RDWR); - if (fd == -1) { - perror("open"); - exit(1); - } - - memset(&map, 0, sizeof(map)); - map.seconds = seconds; - map.threads = threads; - map.node = node; - map.dma_bits = bits; - map.dma_dir = dir; - map.dma_trans_ns = xdelay; - map.granule = granule; - - if (ioctl(fd, cmd, &map)) { - perror("ioctl"); - exit(1); - } - - printf("dma mapping benchmark: threads:%d seconds:%d node:%d dir:%s granule: %d\n", - threads, seconds, node, dir[directions], granule); - printf("average map latency(us):%.1f standard deviation:%.1f\n", - map.avg_map_100ns/10.0, map.map_stddev/10.0); - printf("average unmap latency(us):%.1f standard deviation:%.1f\n", - map.avg_unmap_100ns/10.0, map.unmap_stddev/10.0); - - return 0; -} -- cgit v1.2.3 From c69993ecdd4dfde2b7da08b022052a33b203da07 Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Thu, 23 Oct 2025 15:17:05 +0200 Subject: perf: Support deferred user unwind Add support for deferred userspace unwind to perf. Where perf currently relies on in-place stack unwinding; from NMI context and all that. This moves the userspace part of the unwind to right before the return-to-userspace. This has two distinct benefits, the biggest is that it moves the unwind to a faultable context. It becomes possible to fault in debug info (.eh_frame, SFrame etc.) that might not otherwise be readily available. And secondly, it de-duplicates the user callchain where multiple samples happen during the same kernel entry. To facilitate this the perf interface is extended with a new record type: PERF_RECORD_CALLCHAIN_DEFERRED and two new attribute flags: perf_event_attr::defer_callchain - to request the user unwind be deferred perf_event_attr::defer_output - to request PERF_RECORD_CALLCHAIN_DEFERRED records The existing PERF_RECORD_SAMPLE callchain section gets a new context type: PERF_CONTEXT_USER_DEFERRED After which will come a single entry, denoting the 'cookie' of the deferred callchain that should be attached here, matching the 'cookie' field of the above mentioned PERF_RECORD_CALLCHAIN_DEFERRED. The 'defer_callchain' flag is expected on all events with PERF_SAMPLE_CALLCHAIN. The 'defer_output' flag is expect on the event responsible for collecting side-band events (like mmap, comm etc.). Setting 'defer_output' on multiple events will get you duplicated PERF_RECORD_CALLCHAIN_DEFERRED records. Based on earlier patches by Josh and Steven. Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20251023150002.GR4067720@noisy.programming.kicks-ass.net --- include/linux/perf_event.h | 2 +- include/linux/unwind_deferred.h | 12 ------ include/linux/unwind_deferred_types.h | 13 ++++++ include/uapi/linux/perf_event.h | 21 +++++++++- kernel/bpf/stackmap.c | 4 +- kernel/events/callchain.c | 14 ++++++- kernel/events/core.c | 78 ++++++++++++++++++++++++++++++++++- tools/include/uapi/linux/perf_event.h | 21 +++++++++- 8 files changed, 145 insertions(+), 20 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h index fd1d91017b99..9870d768db4c 100644 --- a/include/linux/perf_event.h +++ b/include/linux/perf_event.h @@ -1720,7 +1720,7 @@ extern void perf_callchain_user(struct perf_callchain_entry_ctx *entry, struct p extern void perf_callchain_kernel(struct perf_callchain_entry_ctx *entry, struct pt_regs *regs); extern struct perf_callchain_entry * get_perf_callchain(struct pt_regs *regs, bool kernel, bool user, - u32 max_stack, bool crosstask, bool add_mark); + u32 max_stack, bool crosstask, bool add_mark, u64 defer_cookie); extern int get_callchain_buffers(int max_stack); extern void put_callchain_buffers(void); extern struct perf_callchain_entry *get_callchain_entry(int *rctx); diff --git a/include/linux/unwind_deferred.h b/include/linux/unwind_deferred.h index f4743c8cff4c..bc7ae7d21900 100644 --- a/include/linux/unwind_deferred.h +++ b/include/linux/unwind_deferred.h @@ -6,18 +6,6 @@ #include #include -struct unwind_work; - -typedef void (*unwind_callback_t)(struct unwind_work *work, - struct unwind_stacktrace *trace, - u64 cookie); - -struct unwind_work { - struct list_head list; - unwind_callback_t func; - int bit; -}; - #ifdef CONFIG_UNWIND_USER enum { diff --git a/include/linux/unwind_deferred_types.h b/include/linux/unwind_deferred_types.h index 0a4c8ddbbc57..18fa3932f61c 100644 --- a/include/linux/unwind_deferred_types.h +++ b/include/linux/unwind_deferred_types.h @@ -39,4 +39,17 @@ struct unwind_task_info { union unwind_task_id id; }; +struct unwind_work; +struct unwind_stacktrace; + +typedef void (*unwind_callback_t)(struct unwind_work *work, + struct unwind_stacktrace *trace, + u64 cookie); + +struct unwind_work { + struct list_head list; + unwind_callback_t func; + int bit; +}; + #endif /* _LINUX_UNWIND_USER_DEFERRED_TYPES_H */ diff --git a/include/uapi/linux/perf_event.h b/include/uapi/linux/perf_event.h index 78a362b80027..d292f96bc06f 100644 --- a/include/uapi/linux/perf_event.h +++ b/include/uapi/linux/perf_event.h @@ -463,7 +463,9 @@ struct perf_event_attr { inherit_thread : 1, /* children only inherit if cloned with CLONE_THREAD */ remove_on_exec : 1, /* event is removed from task on exec */ sigtrap : 1, /* send synchronous SIGTRAP on event */ - __reserved_1 : 26; + defer_callchain: 1, /* request PERF_RECORD_CALLCHAIN_DEFERRED records */ + defer_output : 1, /* output PERF_RECORD_CALLCHAIN_DEFERRED records */ + __reserved_1 : 24; union { __u32 wakeup_events; /* wake up every n events */ @@ -1239,6 +1241,22 @@ enum perf_event_type { */ PERF_RECORD_AUX_OUTPUT_HW_ID = 21, + /* + * This user callchain capture was deferred until shortly before + * returning to user space. Previous samples would have kernel + * callchains only and they need to be stitched with this to make full + * callchains. + * + * struct { + * struct perf_event_header header; + * u64 cookie; + * u64 nr; + * u64 ips[nr]; + * struct sample_id sample_id; + * }; + */ + PERF_RECORD_CALLCHAIN_DEFERRED = 22, + PERF_RECORD_MAX, /* non-ABI */ }; @@ -1269,6 +1287,7 @@ enum perf_callchain_context { PERF_CONTEXT_HV = (__u64)-32, PERF_CONTEXT_KERNEL = (__u64)-128, PERF_CONTEXT_USER = (__u64)-512, + PERF_CONTEXT_USER_DEFERRED = (__u64)-640, PERF_CONTEXT_GUEST = (__u64)-2048, PERF_CONTEXT_GUEST_KERNEL = (__u64)-2176, diff --git a/kernel/bpf/stackmap.c b/kernel/bpf/stackmap.c index 4d53cdd1374c..8f1dacaf01fe 100644 --- a/kernel/bpf/stackmap.c +++ b/kernel/bpf/stackmap.c @@ -315,7 +315,7 @@ BPF_CALL_3(bpf_get_stackid, struct pt_regs *, regs, struct bpf_map *, map, max_depth = sysctl_perf_event_max_stack; trace = get_perf_callchain(regs, kernel, user, max_depth, - false, false); + false, false, 0); if (unlikely(!trace)) /* couldn't fetch the stack trace */ @@ -452,7 +452,7 @@ static long __bpf_get_stack(struct pt_regs *regs, struct task_struct *task, trace = get_callchain_entry_for_task(task, max_depth); else trace = get_perf_callchain(regs, kernel, user, max_depth, - crosstask, false); + crosstask, false, 0); if (unlikely(!trace) || trace->nr < skip) { if (may_fault) diff --git a/kernel/events/callchain.c b/kernel/events/callchain.c index 808c0d7a31fa..b9c7e00725d6 100644 --- a/kernel/events/callchain.c +++ b/kernel/events/callchain.c @@ -218,7 +218,7 @@ static void fixup_uretprobe_trampoline_entries(struct perf_callchain_entry *entr struct perf_callchain_entry * get_perf_callchain(struct pt_regs *regs, bool kernel, bool user, - u32 max_stack, bool crosstask, bool add_mark) + u32 max_stack, bool crosstask, bool add_mark, u64 defer_cookie) { struct perf_callchain_entry *entry; struct perf_callchain_entry_ctx ctx; @@ -251,6 +251,18 @@ get_perf_callchain(struct pt_regs *regs, bool kernel, bool user, regs = task_pt_regs(current); } + if (defer_cookie) { + /* + * Foretell the coming of PERF_RECORD_CALLCHAIN_DEFERRED + * which can be stitched to this one, and add + * the cookie after it (it will be cut off when the + * user stack is copied to the callchain). + */ + perf_callchain_store_context(&ctx, PERF_CONTEXT_USER_DEFERRED); + perf_callchain_store_context(&ctx, defer_cookie); + goto exit_put; + } + if (add_mark) perf_callchain_store_context(&ctx, PERF_CONTEXT_USER); diff --git a/kernel/events/core.c b/kernel/events/core.c index 7541f6f85fcb..f6a08c73f783 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -56,6 +56,7 @@ #include #include #include +#include #include "internal.h" @@ -8200,6 +8201,8 @@ static u64 perf_get_page_size(unsigned long addr) static struct perf_callchain_entry __empty_callchain = { .nr = 0, }; +static struct unwind_work perf_unwind_work; + struct perf_callchain_entry * perf_callchain(struct perf_event *event, struct pt_regs *regs) { @@ -8208,8 +8211,11 @@ perf_callchain(struct perf_event *event, struct pt_regs *regs) !(current->flags & (PF_KTHREAD | PF_USER_WORKER)); /* Disallow cross-task user callchains. */ bool crosstask = event->ctx->task && event->ctx->task != current; + bool defer_user = IS_ENABLED(CONFIG_UNWIND_USER) && user && + event->attr.defer_callchain; const u32 max_stack = event->attr.sample_max_stack; struct perf_callchain_entry *callchain; + u64 defer_cookie; if (!current->mm) user = false; @@ -8217,8 +8223,13 @@ perf_callchain(struct perf_event *event, struct pt_regs *regs) if (!kernel && !user) return &__empty_callchain; - callchain = get_perf_callchain(regs, kernel, user, - max_stack, crosstask, true); + if (!(user && defer_user && !crosstask && + unwind_deferred_request(&perf_unwind_work, &defer_cookie) >= 0)) + defer_cookie = 0; + + callchain = get_perf_callchain(regs, kernel, user, max_stack, + crosstask, true, defer_cookie); + return callchain ?: &__empty_callchain; } @@ -10003,6 +10014,66 @@ void perf_event_bpf_event(struct bpf_prog *prog, perf_iterate_sb(perf_event_bpf_output, &bpf_event, NULL); } +struct perf_callchain_deferred_event { + struct unwind_stacktrace *trace; + struct { + struct perf_event_header header; + u64 cookie; + u64 nr; + u64 ips[]; + } event; +}; + +static void perf_callchain_deferred_output(struct perf_event *event, void *data) +{ + struct perf_callchain_deferred_event *deferred_event = data; + struct perf_output_handle handle; + struct perf_sample_data sample; + int ret, size = deferred_event->event.header.size; + + if (!event->attr.defer_output) + return; + + /* XXX do we really need sample_id_all for this ??? */ + perf_event_header__init_id(&deferred_event->event.header, &sample, event); + + ret = perf_output_begin(&handle, &sample, event, + deferred_event->event.header.size); + if (ret) + goto out; + + perf_output_put(&handle, deferred_event->event); + for (int i = 0; i < deferred_event->trace->nr; i++) { + u64 entry = deferred_event->trace->entries[i]; + perf_output_put(&handle, entry); + } + perf_event__output_id_sample(event, &handle, &sample); + + perf_output_end(&handle); +out: + deferred_event->event.header.size = size; +} + +static void perf_unwind_deferred_callback(struct unwind_work *work, + struct unwind_stacktrace *trace, u64 cookie) +{ + struct perf_callchain_deferred_event deferred_event = { + .trace = trace, + .event = { + .header = { + .type = PERF_RECORD_CALLCHAIN_DEFERRED, + .misc = PERF_RECORD_MISC_USER, + .size = sizeof(deferred_event.event) + + (trace->nr * sizeof(u64)), + }, + .cookie = cookie, + .nr = trace->nr, + }, + }; + + perf_iterate_sb(perf_callchain_deferred_output, &deferred_event, NULL); +} + struct perf_text_poke_event { const void *old_bytes; const void *new_bytes; @@ -14799,6 +14870,9 @@ void __init perf_event_init(void) idr_init(&pmu_idr); + unwind_deferred_init(&perf_unwind_work, + perf_unwind_deferred_callback); + perf_event_init_all_cpus(); init_srcu_struct(&pmus_srcu); perf_pmu_register(&perf_swevent, "software", PERF_TYPE_SOFTWARE); diff --git a/tools/include/uapi/linux/perf_event.h b/tools/include/uapi/linux/perf_event.h index 78a362b80027..d292f96bc06f 100644 --- a/tools/include/uapi/linux/perf_event.h +++ b/tools/include/uapi/linux/perf_event.h @@ -463,7 +463,9 @@ struct perf_event_attr { inherit_thread : 1, /* children only inherit if cloned with CLONE_THREAD */ remove_on_exec : 1, /* event is removed from task on exec */ sigtrap : 1, /* send synchronous SIGTRAP on event */ - __reserved_1 : 26; + defer_callchain: 1, /* request PERF_RECORD_CALLCHAIN_DEFERRED records */ + defer_output : 1, /* output PERF_RECORD_CALLCHAIN_DEFERRED records */ + __reserved_1 : 24; union { __u32 wakeup_events; /* wake up every n events */ @@ -1239,6 +1241,22 @@ enum perf_event_type { */ PERF_RECORD_AUX_OUTPUT_HW_ID = 21, + /* + * This user callchain capture was deferred until shortly before + * returning to user space. Previous samples would have kernel + * callchains only and they need to be stitched with this to make full + * callchains. + * + * struct { + * struct perf_event_header header; + * u64 cookie; + * u64 nr; + * u64 ips[nr]; + * struct sample_id sample_id; + * }; + */ + PERF_RECORD_CALLCHAIN_DEFERRED = 22, + PERF_RECORD_MAX, /* non-ABI */ }; @@ -1269,6 +1287,7 @@ enum perf_callchain_context { PERF_CONTEXT_HV = (__u64)-32, PERF_CONTEXT_KERNEL = (__u64)-128, PERF_CONTEXT_USER = (__u64)-512, + PERF_CONTEXT_USER_DEFERRED = (__u64)-640, PERF_CONTEXT_GUEST = (__u64)-2048, PERF_CONTEXT_GUEST_KERNEL = (__u64)-2176, -- cgit v1.2.3 From 4061c43a99772c66c378cfacaa71550ab3b35909 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 28 Oct 2025 09:45:48 +0100 Subject: pidfs: add missing PIDFD_INFO_SIZE_VER1 We grew struct pidfd_info not too long ago. Link: https://patch.msgid.link/20251028-work-coredump-signal-v1-3-ca449b7b7aa0@kernel.org Fixes: 1d8db6fd698d ("pidfs, coredump: add PIDFD_INFO_COREDUMP") Reviewed-by: Alexander Mikhalitsyn Reviewed-by: Oleg Nesterov Signed-off-by: Christian Brauner --- include/uapi/linux/pidfd.h | 1 + 1 file changed, 1 insertion(+) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/pidfd.h b/include/uapi/linux/pidfd.h index 957db425d459..6ccbabd9a68d 100644 --- a/include/uapi/linux/pidfd.h +++ b/include/uapi/linux/pidfd.h @@ -28,6 +28,7 @@ #define PIDFD_INFO_COREDUMP (1UL << 4) /* Only returned if requested. */ #define PIDFD_INFO_SIZE_VER0 64 /* sizeof first published struct */ +#define PIDFD_INFO_SIZE_VER1 72 /* sizeof second published struct */ /* * Values for @coredump_mask in pidfd_info. -- cgit v1.2.3 From dfd78546c95330db2252e0d7e937a15ab5eddb4e Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 28 Oct 2025 09:45:50 +0100 Subject: pidfd: add a new supported_mask field Some of the future fields in struct pidfd_info can be optional. If the kernel has nothing to emit in that field, then it doesn't set the flag in the reply. This presents a problem: There is currently no way to know what mask flags the kernel supports since one can't always count on them being in the reply. Add a new PIDFD_INFO_SUPPORTED_MASK flag and field that the kernel can set in the reply. Userspace can use this to determine if the fields it requires from the kernel are supported. This also gives us a way to deprecate fields in the future, if that should become necessary. Link: https://patch.msgid.link/20251028-work-coredump-signal-v1-5-ca449b7b7aa0@kernel.org Reviewed-by: Alexander Mikhalitsyn Reviewed-by: Oleg Nesterov Signed-off-by: Christian Brauner --- fs/pidfs.c | 17 ++++++++++++++++- include/uapi/linux/pidfd.h | 3 +++ 2 files changed, 19 insertions(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/fs/pidfs.c b/fs/pidfs.c index 7e4d90cc74ff..204ebd32791a 100644 --- a/fs/pidfs.c +++ b/fs/pidfs.c @@ -293,6 +293,14 @@ static __u32 pidfs_coredump_mask(unsigned long mm_flags) return 0; } +/* This must be updated whenever a new flag is added */ +#define PIDFD_INFO_SUPPORTED (PIDFD_INFO_PID | \ + PIDFD_INFO_CREDS | \ + PIDFD_INFO_CGROUPID | \ + PIDFD_INFO_EXIT | \ + PIDFD_INFO_COREDUMP | \ + PIDFD_INFO_SUPPORTED_MASK) + static long pidfd_info(struct file *file, unsigned int cmd, unsigned long arg) { struct pidfd_info __user *uinfo = (struct pidfd_info __user *)arg; @@ -306,7 +314,7 @@ static long pidfd_info(struct file *file, unsigned int cmd, unsigned long arg) const struct cred *c; __u64 mask; - BUILD_BUG_ON(sizeof(struct pidfd_info) != PIDFD_INFO_SIZE_VER1); + BUILD_BUG_ON(sizeof(struct pidfd_info) != PIDFD_INFO_SIZE_VER2); if (!uinfo) return -EINVAL; @@ -412,6 +420,13 @@ static long pidfd_info(struct file *file, unsigned int cmd, unsigned long arg) return -ESRCH; copy_out: + if (mask & PIDFD_INFO_SUPPORTED_MASK) { + kinfo.mask |= PIDFD_INFO_SUPPORTED_MASK; + kinfo.supported_mask = PIDFD_INFO_SUPPORTED; + } + + /* Are there bits in the return mask not present in PIDFD_INFO_SUPPORTED? */ + WARN_ON_ONCE(~PIDFD_INFO_SUPPORTED & kinfo.mask); /* * If userspace and the kernel have the same struct size it can just * be copied. If userspace provides an older struct, only the bits that diff --git a/include/uapi/linux/pidfd.h b/include/uapi/linux/pidfd.h index 6ccbabd9a68d..e05caa0e00fe 100644 --- a/include/uapi/linux/pidfd.h +++ b/include/uapi/linux/pidfd.h @@ -26,9 +26,11 @@ #define PIDFD_INFO_CGROUPID (1UL << 2) /* Always returned if available, even if not requested */ #define PIDFD_INFO_EXIT (1UL << 3) /* Only returned if requested. */ #define PIDFD_INFO_COREDUMP (1UL << 4) /* Only returned if requested. */ +#define PIDFD_INFO_SUPPORTED_MASK (1UL << 5) /* Want/got supported mask flags */ #define PIDFD_INFO_SIZE_VER0 64 /* sizeof first published struct */ #define PIDFD_INFO_SIZE_VER1 72 /* sizeof second published struct */ +#define PIDFD_INFO_SIZE_VER2 80 /* sizeof third published struct */ /* * Values for @coredump_mask in pidfd_info. @@ -94,6 +96,7 @@ struct pidfd_info { __s32 exit_code; __u32 coredump_mask; __u32 __spare1; + __u64 supported_mask; /* Mask flags that this kernel supports */ }; #define PIDFS_IOCTL_MAGIC 0xFF -- cgit v1.2.3 From 036375522be8425874e9e0f907c7127e315c7a52 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 28 Oct 2025 09:45:53 +0100 Subject: pidfs: expose coredump signal Userspace needs access to the signal that caused the coredump before the coredumping process has been reaped. Expose it as part of the coredump information in struct pidfd_info. After the process has been reaped that info is also available as part of PIDFD_INFO_EXIT's exit_code field. Link: https://patch.msgid.link/20251028-work-coredump-signal-v1-8-ca449b7b7aa0@kernel.org Reviewed-by: Alexander Mikhalitsyn Reviewed-by: Oleg Nesterov Signed-off-by: Christian Brauner --- fs/pidfs.c | 30 +++++++++++++++++++----------- include/uapi/linux/pidfd.h | 7 +++++-- 2 files changed, 24 insertions(+), 13 deletions(-) (limited to 'include/uapi/linux') diff --git a/fs/pidfs.c b/fs/pidfs.c index a3b80be3b98b..354ceb2126e7 100644 --- a/fs/pidfs.c +++ b/fs/pidfs.c @@ -41,6 +41,7 @@ void pidfs_get_root(struct path *path) enum pidfs_attr_mask_bits { PIDFS_ATTR_BIT_EXIT = 0, + PIDFS_ATTR_BIT_COREDUMP = 1, }; struct pidfs_attr { @@ -51,6 +52,7 @@ struct pidfs_attr { __s32 exit_code; }; __u32 coredump_mask; + __u32 coredump_signal; }; static struct rb_root pidfs_ino_tree = RB_ROOT; @@ -297,7 +299,8 @@ static __u32 pidfs_coredump_mask(unsigned long mm_flags) PIDFD_INFO_CGROUPID | \ PIDFD_INFO_EXIT | \ PIDFD_INFO_COREDUMP | \ - PIDFD_INFO_SUPPORTED_MASK) + PIDFD_INFO_SUPPORTED_MASK | \ + PIDFD_INFO_COREDUMP_SIGNAL) static long pidfd_info(struct file *file, unsigned int cmd, unsigned long arg) { @@ -342,9 +345,12 @@ static long pidfd_info(struct file *file, unsigned int cmd, unsigned long arg) } if (mask & PIDFD_INFO_COREDUMP) { - kinfo.coredump_mask = READ_ONCE(attr->coredump_mask); - if (kinfo.coredump_mask) - kinfo.mask |= PIDFD_INFO_COREDUMP; + if (test_bit(PIDFS_ATTR_BIT_COREDUMP, &attr->attr_mask)) { + smp_rmb(); + kinfo.mask |= PIDFD_INFO_COREDUMP | PIDFD_INFO_COREDUMP_SIGNAL; + kinfo.coredump_mask = attr->coredump_mask; + kinfo.coredump_signal = attr->coredump_signal; + } } task = get_pid_task(pid, PIDTYPE_PID); @@ -370,6 +376,7 @@ static long pidfd_info(struct file *file, unsigned int cmd, unsigned long arg) kinfo.coredump_mask = pidfs_coredump_mask(flags); kinfo.mask |= PIDFD_INFO_COREDUMP; + /* No coredump actually took place, so no coredump signal. */ } } @@ -666,20 +673,21 @@ void pidfs_coredump(const struct coredump_params *cprm) { struct pid *pid = cprm->pid; struct pidfs_attr *attr; - __u32 coredump_mask = 0; attr = READ_ONCE(pid->attr); VFS_WARN_ON_ONCE(!attr); VFS_WARN_ON_ONCE(attr == PIDFS_PID_DEAD); - /* Note how we were coredumped. */ - coredump_mask = pidfs_coredump_mask(cprm->mm_flags); - /* Note that we actually did coredump. */ - coredump_mask |= PIDFD_COREDUMPED; + /* Note how we were coredumped and that we coredumped. */ + attr->coredump_mask = pidfs_coredump_mask(cprm->mm_flags) | + PIDFD_COREDUMPED; /* If coredumping is set to skip we should never end up here. */ - VFS_WARN_ON_ONCE(coredump_mask & PIDFD_COREDUMP_SKIP); - smp_store_release(&attr->coredump_mask, coredump_mask); + VFS_WARN_ON_ONCE(attr->coredump_mask & PIDFD_COREDUMP_SKIP); + /* Expose the signal number that caused the coredump. */ + attr->coredump_signal = cprm->siginfo->si_signo; + smp_wmb(); + set_bit(PIDFS_ATTR_BIT_COREDUMP, &attr->attr_mask); } #endif diff --git a/include/uapi/linux/pidfd.h b/include/uapi/linux/pidfd.h index e05caa0e00fe..ea9a6811fc76 100644 --- a/include/uapi/linux/pidfd.h +++ b/include/uapi/linux/pidfd.h @@ -27,6 +27,7 @@ #define PIDFD_INFO_EXIT (1UL << 3) /* Only returned if requested. */ #define PIDFD_INFO_COREDUMP (1UL << 4) /* Only returned if requested. */ #define PIDFD_INFO_SUPPORTED_MASK (1UL << 5) /* Want/got supported mask flags */ +#define PIDFD_INFO_COREDUMP_SIGNAL (1UL << 6) /* Always returned if PIDFD_INFO_COREDUMP is requested. */ #define PIDFD_INFO_SIZE_VER0 64 /* sizeof first published struct */ #define PIDFD_INFO_SIZE_VER1 72 /* sizeof second published struct */ @@ -94,8 +95,10 @@ struct pidfd_info { __u32 fsuid; __u32 fsgid; __s32 exit_code; - __u32 coredump_mask; - __u32 __spare1; + struct /* coredump info */ { + __u32 coredump_mask; + __u32 coredump_signal; + }; __u64 supported_mask; /* Mask flags that this kernel supports */ }; -- cgit v1.2.3 From 30176bf7c871681df506f3165ffe76ec462db991 Mon Sep 17 00:00:00 2001 From: Ivan Vecera Date: Wed, 29 Oct 2025 16:32:06 +0100 Subject: dpll: add phase-adjust-gran pin attribute Phase-adjust values are currently limited by a min-max range. Some hardware requires, for certain pin types, that values be multiples of a specific granularity, as in the zl3073x driver. Add a `phase-adjust-gran` pin attribute and an appropriate field in dpll_pin_properties. If set by the driver, use its value to validate user-provided phase-adjust values. Reviewed-by: Michal Schmidt Reviewed-by: Petr Oros Tested-by: Prathosh Satish Signed-off-by: Ivan Vecera Reviewed-by: Jiri Pirko Reviewed-by: Arkadiusz Kubalewski Link: https://patch.msgid.link/20251029153207.178448-2-ivecera@redhat.com Signed-off-by: Jakub Kicinski --- Documentation/driver-api/dpll.rst | 36 +++++++++++++++++++---------------- Documentation/netlink/specs/dpll.yaml | 7 +++++++ drivers/dpll/dpll_netlink.c | 12 +++++++++++- include/linux/dpll.h | 1 + include/uapi/linux/dpll.h | 1 + 5 files changed, 40 insertions(+), 17 deletions(-) (limited to 'include/uapi/linux') diff --git a/Documentation/driver-api/dpll.rst b/Documentation/driver-api/dpll.rst index be1fc643b645..83118c728ed9 100644 --- a/Documentation/driver-api/dpll.rst +++ b/Documentation/driver-api/dpll.rst @@ -198,26 +198,28 @@ be requested with the same attribute with ``DPLL_CMD_DEVICE_SET`` command. ================================== ====================================== Device may also provide ability to adjust a signal phase on a pin. -If pin phase adjustment is supported, minimal and maximal values that pin -handle shall be provide to the user on ``DPLL_CMD_PIN_GET`` respond -with ``DPLL_A_PIN_PHASE_ADJUST_MIN`` and ``DPLL_A_PIN_PHASE_ADJUST_MAX`` +If pin phase adjustment is supported, minimal and maximal values and +granularity that pin handle shall be provided to the user on +``DPLL_CMD_PIN_GET`` respond with ``DPLL_A_PIN_PHASE_ADJUST_MIN``, +``DPLL_A_PIN_PHASE_ADJUST_MAX`` and ``DPLL_A_PIN_PHASE_ADJUST_GRAN`` attributes. Configured phase adjust value is provided with ``DPLL_A_PIN_PHASE_ADJUST`` attribute of a pin, and value change can be requested with the same attribute with ``DPLL_CMD_PIN_SET`` command. - =============================== ====================================== - ``DPLL_A_PIN_ID`` configured pin id - ``DPLL_A_PIN_PHASE_ADJUST_MIN`` attr minimum value of phase adjustment - ``DPLL_A_PIN_PHASE_ADJUST_MAX`` attr maximum value of phase adjustment - ``DPLL_A_PIN_PHASE_ADJUST`` attr configured value of phase - adjustment on parent dpll device - ``DPLL_A_PIN_PARENT_DEVICE`` nested attribute for requesting - configuration on given parent dpll - device - ``DPLL_A_PIN_PARENT_ID`` parent dpll device id - ``DPLL_A_PIN_PHASE_OFFSET`` attr measured phase difference - between a pin and parent dpll device - =============================== ====================================== + ================================ ========================================== + ``DPLL_A_PIN_ID`` configured pin id + ``DPLL_A_PIN_PHASE_ADJUST_GRAN`` attr granularity of phase adjustment value + ``DPLL_A_PIN_PHASE_ADJUST_MIN`` attr minimum value of phase adjustment + ``DPLL_A_PIN_PHASE_ADJUST_MAX`` attr maximum value of phase adjustment + ``DPLL_A_PIN_PHASE_ADJUST`` attr configured value of phase + adjustment on parent dpll device + ``DPLL_A_PIN_PARENT_DEVICE`` nested attribute for requesting + configuration on given parent dpll + device + ``DPLL_A_PIN_PARENT_ID`` parent dpll device id + ``DPLL_A_PIN_PHASE_OFFSET`` attr measured phase difference + between a pin and parent dpll device + ================================ ========================================== All phase related values are provided in pico seconds, which represents time difference between signals phase. The negative value means that @@ -384,6 +386,8 @@ according to attribute purpose. frequencies ``DPLL_A_PIN_ANY_FREQUENCY_MIN`` attr minimum value of frequency ``DPLL_A_PIN_ANY_FREQUENCY_MAX`` attr maximum value of frequency + ``DPLL_A_PIN_PHASE_ADJUST_GRAN`` attr granularity of phase + adjustment value ``DPLL_A_PIN_PHASE_ADJUST_MIN`` attr minimum value of phase adjustment ``DPLL_A_PIN_PHASE_ADJUST_MAX`` attr maximum value of phase diff --git a/Documentation/netlink/specs/dpll.yaml b/Documentation/netlink/specs/dpll.yaml index 80728f6f9bc8..78d0724d7e12 100644 --- a/Documentation/netlink/specs/dpll.yaml +++ b/Documentation/netlink/specs/dpll.yaml @@ -440,6 +440,12 @@ attribute-sets: doc: | Capable pin provides list of pins that can be bound to create a reference-sync pin pair. + - + name: phase-adjust-gran + type: u32 + doc: | + Granularity of phase adjustment, in picoseconds. The value of + phase adjustment must be a multiple of this granularity. - name: pin-parent-device @@ -616,6 +622,7 @@ operations: - capabilities - parent-device - parent-pin + - phase-adjust-gran - phase-adjust-min - phase-adjust-max - phase-adjust diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c index a4153bcb6dcf..64944f601ee5 100644 --- a/drivers/dpll/dpll_netlink.c +++ b/drivers/dpll/dpll_netlink.c @@ -637,6 +637,10 @@ dpll_cmd_pin_get_one(struct sk_buff *msg, struct dpll_pin *pin, ret = dpll_msg_add_pin_freq(msg, pin, ref, extack); if (ret) return ret; + if (prop->phase_gran && + nla_put_u32(msg, DPLL_A_PIN_PHASE_ADJUST_GRAN, + prop->phase_gran)) + return -EMSGSIZE; if (nla_put_s32(msg, DPLL_A_PIN_PHASE_ADJUST_MIN, prop->phase_range.min)) return -EMSGSIZE; @@ -1261,7 +1265,13 @@ dpll_pin_phase_adj_set(struct dpll_pin *pin, struct nlattr *phase_adj_attr, if (phase_adj > pin->prop.phase_range.max || phase_adj < pin->prop.phase_range.min) { NL_SET_ERR_MSG_ATTR(extack, phase_adj_attr, - "phase adjust value not supported"); + "phase adjust value of out range"); + return -EINVAL; + } + if (pin->prop.phase_gran && phase_adj % (s32)pin->prop.phase_gran) { + NL_SET_ERR_MSG_ATTR_FMT(extack, phase_adj_attr, + "phase adjust value not multiple of %u", + pin->prop.phase_gran); return -EINVAL; } diff --git a/include/linux/dpll.h b/include/linux/dpll.h index 25be745bf41f..562f520b23c2 100644 --- a/include/linux/dpll.h +++ b/include/linux/dpll.h @@ -163,6 +163,7 @@ struct dpll_pin_properties { u32 freq_supported_num; struct dpll_pin_frequency *freq_supported; struct dpll_pin_phase_adjust_range phase_range; + u32 phase_gran; }; #if IS_ENABLED(CONFIG_DPLL) diff --git a/include/uapi/linux/dpll.h b/include/uapi/linux/dpll.h index ab1725a954d7..69d35570ac4f 100644 --- a/include/uapi/linux/dpll.h +++ b/include/uapi/linux/dpll.h @@ -251,6 +251,7 @@ enum dpll_a_pin { DPLL_A_PIN_ESYNC_FREQUENCY_SUPPORTED, DPLL_A_PIN_ESYNC_PULSE, DPLL_A_PIN_REFERENCE_SYNC, + DPLL_A_PIN_PHASE_ADJUST_GRAN, __DPLL_A_PIN_MAX, DPLL_A_PIN_MAX = (__DPLL_A_PIN_MAX - 1) -- cgit v1.2.3 From bc49af56eea866c34d21bf582f65b02fc8c06ec3 Mon Sep 17 00:00:00 2001 From: Chaitanya Kulkarni Date: Tue, 28 Oct 2025 20:34:23 -0700 Subject: blktrace: add support for REQ_OP_WRITE_ZEROES tracing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, REQ_OP_WRITE_ZEROES operations are not handled in the blktrace infrastructure, resulting in incorrect or missing operation labels in ftrace blktrace output. This manifests as write-zeroes operations appearing with incorrect labels like "N" instead of a proper "WZ" designation. This patch adds complete support for REQ_OP_WRITE_ZEROES across the blktrace infrastructure: Add BLK_TC_WRITE_ZEROES trace category in blktrace_api.h and update BLK_TC_END_V2 marker accordingly Map REQ_OP_WRITE_ZEROES to BLK_TC_WRITE_ZEROES in __blk_add_trace() to ensure proper trace event categorization Update fill_rwbs() to generate "WZ" label for write-zeroes operations in ftrace output, making them easily identifiable Add "write-zeroes" string mapping in act_to_str array for debugfs filter interface Update blk_fill_rwbs() to handle REQ_OP_WRITE_ZEROES for block layer event tracing With this fix, write-zeroes operations are now correctly traced and displayed. =========================================================== BEFORE THIS PATCH =========================================================== blkdiscard -z -o 0 -l 40960 /dev/nvme0n1 blkdiscard-3809 [030] ..... 1212.253701: block_bio_queue: 259,0 NS 0 + 80 [blkdiscard] blkdiscard-3809 [030] ..... 1212.253703: block_getrq: 259,0 NS 0 + 80 [blkdiscard] blkdiscard-3809 [030] ..... 1212.253704: block_io_start: 259,0 NS 40960 () 0 + 80 be,0,4 [blkdiscard] blkdiscard-3809 [030] ..... 1212.253704: block_plug: [blkdiscard] blkdiscard-3809 [030] ..... 1212.253706: block_unplug: [blkdiscard] 1 blkdiscard-3809 [030] ..... 1212.253706: block_rq_insert: 259,0 NS 40960 () 0 + 80 be,0,4 [blkdiscard] kworker/30:1H-566 [030] ..... 1212.253726: block_rq_issue: 259,0 NS 40960 () 0 + 80 be,0,4 [kworker/30:1H] -0 [030] d.h1. 1212.253957: block_rq_complete: 259,0 NS () 0 + 80 be,0,4 [0] -0 [030] dNh1. 1212.253960: block_io_done: 259,0 NS 0 () 0 + 0 none,0,0 [swapper/30] Trace Event Breakdown: Event | Device | Op | Sector | Sectors | Byte Size | Calculation block_bio_queue | 259,0 | NS | 0 | 80 | - | 80 × 512 = 40,960 block_getrq | 259,0 | NS | 0 | 80 | - | 80 × 512 = 40,960 block_io_start | 259,0 | NS | 0 | 80 | 40960 | Direct from trace block_rq_insert | 259,0 | NS | 0 | 80 | 40960 | Direct from trace block_rq_issue | 259,0 | NS | 0 | 80 | 40960 | Direct from trace block_rq_complete | 259,0 | NS | 0 | 80 | - | 80 × 512 = 40,960 block_io_done | 259,0 | NS | 0 | 0 | 0 | Completion (no data) Total Bytes Transferred: Sectors: 80 Bytes: 80 × 512 = 40,960 bytes =========================================================== AFTER THIS PATCH =========================================================== blkdiscard -z -o 0 -l 40960 /dev/nvme0n1 blkdiscard-2477 [020] ..... 960.989131: block_bio_queue: 259,0 WZS 0 + 80 [blkdiscard] blkdiscard-2477 [020] ..... 960.989134: block_getrq: 259,0 WZS 0 + 80 [blkdiscard] blkdiscard-2477 [020] ..... 960.989135: block_io_start: 259,0 WZS 40960 () 0 + 80 be,0,4 [blkdiscard] blkdiscard-2477 [020] ..... 960.989138: block_plug: [blkdiscard] blkdiscard-2477 [020] ..... 960.989140: block_unplug: [blkdiscard] 1 blkdiscard-2477 [020] ..... 960.989141: block_rq_insert: 259,0 WZS 40960 () 0 + 80 be,0,4 [blkdiscard] kworker/20:1H-736 [020] ..... 960.989166: block_rq_issue: 259,0 WZS 40960 () 0 + 80 be,0,4 [kworker/20:1H] -0 [020] d.h1. 960.989476: block_rq_complete: 259,0 WZS () 0 + 80 be,0,4 [0] -0 [020] dNh1. 960.989482: block_io_done: 259,0 WZS 0 () 0 + 0 none,0,0 [swapper/20] Trace Event Breakdown: Event | Device | Op | Sector | Sectors | Byte Size | Calculation block_bio_queue | 259,0 | WZS | 0 | 80 | - | 80 × 512 = 40,960 block_getrq | 259,0 | WZS | 0 | 80 | - | 80 × 512 = 40,960 block_io_start | 259,0 | WZS | 0 | 80 | 40960 | Direct from trace block_rq_insert | 259,0 | WZS | 0 | 80 | 40960 | Direct from trace block_rq_issue | 259,0 | WZS | 0 | 80 | 40960 | Direct from trace block_rq_complete | 259,0 | WZS | 0 | 80 | - | 80 × 512 = 40,960 block_io_done | 259,0 | WZS | 0 | 0 | 0 | Completion (no data) Total Bytes Transferred: Sectors: 80 Bytes: 80 × 512 = 40,960 bytes Tested with ftrace blktrace on NVMe devices using blkdiscard with the -z (write-zeroes) flag. Signed-off-by: Chaitanya Kulkarni Reviewed-by: Johannes Thumshirn Signed-off-by: Jens Axboe --- include/uapi/linux/blktrace_api.h | 4 +++- kernel/trace/blktrace.c | 13 ++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/blktrace_api.h b/include/uapi/linux/blktrace_api.h index 30f3d2589365..7c092d9f3aa4 100644 --- a/include/uapi/linux/blktrace_api.h +++ b/include/uapi/linux/blktrace_api.h @@ -35,7 +35,9 @@ enum blktrace_cat { BLK_TC_ZONE_OPEN = 1ull << 20, /* zone open */ BLK_TC_ZONE_CLOSE = 1ull << 21, /* zone close */ - BLK_TC_END_V2 = 1ull << 21, + BLK_TC_WRITE_ZEROES = 1ull << 22, /* write-zeroes */ + + BLK_TC_END_V2 = 1ull << 22, }; #define BLK_TC_SHIFT (16) diff --git a/kernel/trace/blktrace.c b/kernel/trace/blktrace.c index e4f26ddb7ee2..af8cbc8e1a7c 100644 --- a/kernel/trace/blktrace.c +++ b/kernel/trace/blktrace.c @@ -360,6 +360,9 @@ static void __blk_add_trace(struct blk_trace *bt, sector_t sector, int bytes, case REQ_OP_ZONE_CLOSE: what |= BLK_TC_ACT(BLK_TC_ZONE_CLOSE); break; + case REQ_OP_WRITE_ZEROES: + what |= BLK_TC_ACT(BLK_TC_WRITE_ZEROES); + break; default: break; } @@ -1408,7 +1411,10 @@ static void fill_rwbs(char *rwbs, const struct blk_io_trace2 *t) if (tc & BLK_TC_DISCARD) rwbs[i++] = 'D'; - else if (tc & BLK_TC_WRITE) + else if (tc & BLK_TC_WRITE_ZEROES) { + rwbs[i++] = 'W'; + rwbs[i++] = 'Z'; + } else if (tc & BLK_TC_WRITE) rwbs[i++] = 'W'; else if (t->bytes) rwbs[i++] = 'R'; @@ -1951,6 +1957,7 @@ static const struct { { BLK_TC_DISCARD, "discard" }, { BLK_TC_DRV_DATA, "drv_data" }, { BLK_TC_FUA, "fua" }, + { BLK_TC_WRITE_ZEROES, "write-zeroes" }, }; static int blk_trace_str2mask(const char *str) @@ -2164,6 +2171,10 @@ void blk_fill_rwbs(char *rwbs, blk_opf_t opf) rwbs[i++] = 'Z'; rwbs[i++] = 'C'; break; + case REQ_OP_WRITE_ZEROES: + rwbs[i++] = 'W'; + rwbs[i++] = 'Z'; + break; default: rwbs[i++] = 'N'; } -- cgit v1.2.3 From 3760342fd6312416491d536144e39297fa5b1950 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 29 Oct 2025 13:20:28 +0100 Subject: nstree: assign fixed ids to the initial namespaces The initial set of namespace comes with fixed inode numbers making it easy for userspace to identify them solely based on that information. This has long preceeded anything here. Similarly, let's assign fixed namespace ids for the initial namespaces. Kill the cookie and use a sequentially increasing number. This has the nice side-effect that the owning user namespace will always have a namespace id that is smaller than any of it's descendant namespaces. Link: https://patch.msgid.link/20251029-work-namespace-nstree-listns-v4-15-2e6f823ebdc0@kernel.org Signed-off-by: Christian Brauner --- fs/namespace.c | 2 +- include/linux/ns_common.h | 13 ++++++++++++- include/linux/nstree.h | 15 +++++++++++---- include/uapi/linux/nsfs.h | 14 ++++++++++++++ kernel/nstree.c | 13 ++++++++----- net/core/net_namespace.c | 2 +- 6 files changed, 47 insertions(+), 12 deletions(-) (limited to 'include/uapi/linux') diff --git a/fs/namespace.c b/fs/namespace.c index 7b78dd48b6c3..eded33eeb647 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -4094,7 +4094,7 @@ static struct mnt_namespace *alloc_mnt_ns(struct user_namespace *user_ns, bool a return ERR_PTR(ret); } if (!anon) - ns_tree_gen_id(&new_ns->ns); + ns_tree_gen_id(new_ns); refcount_set(&new_ns->passive, 1); new_ns->mounts = RB_ROOT; init_waitqueue_head(&new_ns->poll); diff --git a/include/linux/ns_common.h b/include/linux/ns_common.h index 7a3c71b3a76f..009a6dea724f 100644 --- a/include/linux/ns_common.h +++ b/include/linux/ns_common.h @@ -173,6 +173,17 @@ static __always_inline bool is_initial_namespace(struct ns_common *ns) struct user_namespace *: &init_user_ns, \ struct uts_namespace *: &init_uts_ns) +#define ns_init_id(__ns) \ + _Generic((__ns), \ + struct cgroup_namespace *: CGROUP_NS_INIT_ID, \ + struct ipc_namespace *: IPC_NS_INIT_ID, \ + struct mnt_namespace *: MNT_NS_INIT_ID, \ + struct net *: NET_NS_INIT_ID, \ + struct pid_namespace *: PID_NS_INIT_ID, \ + struct time_namespace *: TIME_NS_INIT_ID, \ + struct user_namespace *: USER_NS_INIT_ID, \ + struct uts_namespace *: UTS_NS_INIT_ID) + #define to_ns_operations(__ns) \ _Generic((__ns), \ struct cgroup_namespace *: (IS_ENABLED(CONFIG_CGROUPS) ? &cgroupns_operations : NULL), \ @@ -198,7 +209,7 @@ static __always_inline bool is_initial_namespace(struct ns_common *ns) #define NS_COMMON_INIT(nsname, refs) \ { \ .ns_type = ns_common_type(&nsname), \ - .ns_id = 0, \ + .ns_id = ns_init_id(&nsname), \ .inum = ns_init_inum(&nsname), \ .ops = to_ns_operations(&nsname), \ .stashed = NULL, \ diff --git a/include/linux/nstree.h b/include/linux/nstree.h index 43aa262c0ea1..38674c6fa4f7 100644 --- a/include/linux/nstree.h +++ b/include/linux/nstree.h @@ -9,6 +9,7 @@ #include #include #include +#include extern struct ns_tree cgroup_ns_tree; extern struct ns_tree ipc_ns_tree; @@ -30,7 +31,11 @@ extern struct ns_tree uts_ns_tree; struct user_namespace *: &(user_ns_tree), \ struct uts_namespace *: &(uts_ns_tree)) -u64 ns_tree_gen_id(struct ns_common *ns); +#define ns_tree_gen_id(__ns) \ + __ns_tree_gen_id(to_ns_common(__ns), \ + (((__ns) == ns_init_ns(__ns)) ? ns_init_id(__ns) : 0)) + +u64 __ns_tree_gen_id(struct ns_common *ns, u64 id); void __ns_tree_add_raw(struct ns_common *ns, struct ns_tree *ns_tree); void __ns_tree_remove(struct ns_common *ns, struct ns_tree *ns_tree); struct ns_common *ns_tree_lookup_rcu(u64 ns_id, int ns_type); @@ -38,9 +43,9 @@ struct ns_common *__ns_tree_adjoined_rcu(struct ns_common *ns, struct ns_tree *ns_tree, bool previous); -static inline void __ns_tree_add(struct ns_common *ns, struct ns_tree *ns_tree) +static inline void __ns_tree_add(struct ns_common *ns, struct ns_tree *ns_tree, u64 id) { - ns_tree_gen_id(ns); + __ns_tree_gen_id(ns, id); __ns_tree_add_raw(ns, ns_tree); } @@ -60,7 +65,9 @@ static inline void __ns_tree_add(struct ns_common *ns, struct ns_tree *ns_tree) * This function assigns a new id to the namespace and adds it to the * appropriate namespace tree and list. */ -#define ns_tree_add(__ns) __ns_tree_add(to_ns_common(__ns), to_ns_tree(__ns)) +#define ns_tree_add(__ns) \ + __ns_tree_add(to_ns_common(__ns), to_ns_tree(__ns), \ + (((__ns) == ns_init_ns(__ns)) ? ns_init_id(__ns) : 0)) /** * ns_tree_remove - Remove a namespace from a namespace tree diff --git a/include/uapi/linux/nsfs.h b/include/uapi/linux/nsfs.h index e098759ec917..f8bc2aad74d6 100644 --- a/include/uapi/linux/nsfs.h +++ b/include/uapi/linux/nsfs.h @@ -67,4 +67,18 @@ struct nsfs_file_handle { #define NSFS_FILE_HANDLE_SIZE_VER0 16 /* sizeof first published struct */ #define NSFS_FILE_HANDLE_SIZE_LATEST sizeof(struct nsfs_file_handle) /* sizeof latest published struct */ +enum init_ns_id { + IPC_NS_INIT_ID = 1ULL, + UTS_NS_INIT_ID = 2ULL, + USER_NS_INIT_ID = 3ULL, + PID_NS_INIT_ID = 4ULL, + CGROUP_NS_INIT_ID = 5ULL, + TIME_NS_INIT_ID = 6ULL, + NET_NS_INIT_ID = 7ULL, + MNT_NS_INIT_ID = 8ULL, +#ifdef __KERNEL__ + NS_LAST_INIT_ID = MNT_NS_INIT_ID, +#endif +}; + #endif /* __LINUX_NSFS_H */ diff --git a/kernel/nstree.c b/kernel/nstree.c index bbb34b46b01b..cf102c5bb849 100644 --- a/kernel/nstree.c +++ b/kernel/nstree.c @@ -69,8 +69,6 @@ struct ns_tree time_ns_tree = { .type = CLONE_NEWTIME, }; -DEFINE_COOKIE(namespace_cookie); - static inline struct ns_common *node_to_ns(const struct rb_node *node) { if (!node) @@ -285,15 +283,20 @@ struct ns_common *__ns_tree_adjoined_rcu(struct ns_common *ns, /** * ns_tree_gen_id - generate a new namespace id * @ns: namespace to generate id for + * @id: if non-zero, this is the initial namespace and this is a fixed id * * Generates a new namespace id and assigns it to the namespace. All * namespaces types share the same id space and thus can be compared * directly. IOW, when two ids of two namespace are equal, they are * identical. */ -u64 ns_tree_gen_id(struct ns_common *ns) +u64 __ns_tree_gen_id(struct ns_common *ns, u64 id) { - guard(preempt)(); - ns->ns_id = gen_cookie_next(&namespace_cookie); + static atomic64_t namespace_cookie = ATOMIC64_INIT(NS_LAST_INIT_ID + 1); + + if (id) + ns->ns_id = id; + else + ns->ns_id = atomic64_inc_return(&namespace_cookie); return ns->ns_id; } diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c index b0e0f22d7b21..83cbec4afcb3 100644 --- a/net/core/net_namespace.c +++ b/net/core/net_namespace.c @@ -439,7 +439,7 @@ static __net_init int setup_net(struct net *net) LIST_HEAD(net_exit_list); int error = 0; - net->net_cookie = ns_tree_gen_id(&net->ns); + net->net_cookie = ns_tree_gen_id(net); list_for_each_entry(ops, &pernet_list, list) { error = ops_init(ops, net); -- cgit v1.2.3 From 76b6f5dfb3fda76fce1f9990d6fa58adc711122b Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Wed, 29 Oct 2025 13:20:32 +0100 Subject: nstree: add listns() Add a new listns() system call that allows userspace to iterate through namespaces in the system. This provides a programmatic interface to discover and inspect namespaces, enhancing existing namespace apis. Currently, there is no direct way for userspace to enumerate namespaces in the system. Applications must resort to scanning /proc//ns/ across all processes, which is: 1. Inefficient - requires iterating over all processes 2. Incomplete - misses inactive namespaces that aren't attached to any running process but are kept alive by file descriptors, bind mounts, or parent namespace references 3. Permission-heavy - requires access to /proc for many processes 4. No ordering or ownership. 5. No filtering per namespace type: Must always iterate and check all namespaces. The list goes on. The listns() system call solves these problems by providing direct kernel-level enumeration of namespaces. It is similar to listmount() but obviously tailored to namespaces. /* * @req: Pointer to struct ns_id_req specifying search parameters * @ns_ids: User buffer to receive namespace IDs * @nr_ns_ids: Size of ns_ids buffer (maximum number of IDs to return) * @flags: Reserved for future use (must be 0) */ ssize_t listns(const struct ns_id_req *req, u64 *ns_ids, size_t nr_ns_ids, unsigned int flags); Returns: - On success: Number of namespace IDs written to ns_ids - On error: Negative error code /* * @size: Structure size * @ns_id: Starting point for iteration; use 0 for first call, then * use the last returned ID for subsequent calls to paginate * @ns_type: Bitmask of namespace types to include (from enum ns_type): * 0: Return all namespace types * MNT_NS: Mount namespaces * NET_NS: Network namespaces * USER_NS: User namespaces * etc. Can be OR'd together * @user_ns_id: Filter results to namespaces owned by this user namespace: * 0: Return all namespaces (subject to permission checks) * LISTNS_CURRENT_USER: Namespaces owned by caller's user namespace * Other value: Namespaces owned by the specified user namespace ID */ struct ns_id_req { __u32 size; /* sizeof(struct ns_id_req) */ __u32 spare; /* Reserved, must be 0 */ __u64 ns_id; /* Last seen namespace ID (for pagination) */ __u32 ns_type; /* Filter by namespace type(s) */ __u32 spare2; /* Reserved, must be 0 */ __u64 user_ns_id; /* Filter by owning user namespace */ }; Example 1: List all namespaces void list_all_namespaces(void) { struct ns_id_req req = { .size = sizeof(req), .ns_id = 0, /* Start from beginning */ .ns_type = 0, /* All types */ .user_ns_id = 0, /* All user namespaces */ }; uint64_t ids[100]; ssize_t ret; printf("All namespaces in the system:\n"); do { ret = listns(&req, ids, 100, 0); if (ret < 0) { perror("listns"); break; } for (ssize_t i = 0; i < ret; i++) printf(" Namespace ID: %llu\n", (unsigned long long)ids[i]); /* Continue from last seen ID */ if (ret > 0) req.ns_id = ids[ret - 1]; } while (ret == 100); /* Buffer was full, more may exist */ } Example 2: List network namespaces only void list_network_namespaces(void) { struct ns_id_req req = { .size = sizeof(req), .ns_id = 0, .ns_type = NET_NS, /* Only network namespaces */ .user_ns_id = 0, }; uint64_t ids[100]; ssize_t ret; ret = listns(&req, ids, 100, 0); if (ret < 0) { perror("listns"); return; } printf("Network namespaces: %zd found\n", ret); for (ssize_t i = 0; i < ret; i++) printf(" netns ID: %llu\n", (unsigned long long)ids[i]); } Example 3: List namespaces owned by current user namespace void list_owned_namespaces(void) { struct ns_id_req req = { .size = sizeof(req), .ns_id = 0, .ns_type = 0, /* All types */ .user_ns_id = LISTNS_CURRENT_USER, /* Current userns */ }; uint64_t ids[100]; ssize_t ret; ret = listns(&req, ids, 100, 0); if (ret < 0) { perror("listns"); return; } printf("Namespaces owned by my user namespace: %zd\n", ret); for (ssize_t i = 0; i < ret; i++) printf(" ns ID: %llu\n", (unsigned long long)ids[i]); } Example 4: List multiple namespace types void list_network_and_mount_namespaces(void) { struct ns_id_req req = { .size = sizeof(req), .ns_id = 0, .ns_type = NET_NS | MNT_NS, /* Network and mount */ .user_ns_id = 0, }; uint64_t ids[100]; ssize_t ret; ret = listns(&req, ids, 100, 0); printf("Network and mount namespaces: %zd found\n", ret); } Example 5: Pagination through large namespace sets void list_all_with_pagination(void) { struct ns_id_req req = { .size = sizeof(req), .ns_id = 0, .ns_type = 0, .user_ns_id = 0, }; uint64_t ids[50]; size_t total = 0; ssize_t ret; printf("Enumerating all namespaces with pagination:\n"); while (1) { ret = listns(&req, ids, 50, 0); if (ret < 0) { perror("listns"); break; } if (ret == 0) break; /* No more namespaces */ total += ret; printf(" Batch: %zd namespaces\n", ret); /* Last ID in this batch becomes start of next batch */ req.ns_id = ids[ret - 1]; if (ret < 50) break; /* Partial batch = end of results */ } printf("Total: %zu namespaces\n", total); } Permission Model listns() respects namespace isolation and capabilities: (1) Global listing (user_ns_id = 0): - Requires CAP_SYS_ADMIN in the namespace's owning user namespace - OR the namespace must be in the caller's namespace context (e.g., a namespace the caller is currently using) - User namespaces additionally allow listing if the caller has CAP_SYS_ADMIN in that user namespace itself (2) Owner-filtered listing (user_ns_id != 0): - Requires CAP_SYS_ADMIN in the specified owner user namespace - OR the namespace must be in the caller's namespace context - This allows unprivileged processes to enumerate namespaces they own (3) Visibility: - Only "active" namespaces are listed - A namespace is active if it has a non-zero __ns_ref_active count - This includes namespaces used by running processes, held by open file descriptors, or kept active by bind mounts - Inactive namespaces (kept alive only by internal kernel references) are not visible via listns() Link: https://patch.msgid.link/20251029-work-namespace-nstree-listns-v4-19-2e6f823ebdc0@kernel.org Signed-off-by: Christian Brauner --- fs/nsfs.c | 39 ++++ include/linux/ns_common.h | 2 + include/linux/syscalls.h | 4 + include/linux/user_namespace.h | 4 +- include/uapi/linux/nsfs.h | 44 +++++ kernel/nscommon.c | 2 +- kernel/nstree.c | 397 +++++++++++++++++++++++++++++++++++++++++ 7 files changed, 489 insertions(+), 3 deletions(-) (limited to 'include/uapi/linux') diff --git a/fs/nsfs.c b/fs/nsfs.c index 4a95a0a38f86..ba6c8975c82e 100644 --- a/fs/nsfs.c +++ b/fs/nsfs.c @@ -471,6 +471,45 @@ static int nsfs_encode_fh(struct inode *inode, u32 *fh, int *max_len, return FILEID_NSFS; } +bool is_current_namespace(struct ns_common *ns) +{ + switch (ns->ns_type) { +#ifdef CONFIG_CGROUPS + case CLONE_NEWCGROUP: + return current_in_namespace(to_cg_ns(ns)); +#endif +#ifdef CONFIG_IPC_NS + case CLONE_NEWIPC: + return current_in_namespace(to_ipc_ns(ns)); +#endif + case CLONE_NEWNS: + return current_in_namespace(to_mnt_ns(ns)); +#ifdef CONFIG_NET_NS + case CLONE_NEWNET: + return current_in_namespace(to_net_ns(ns)); +#endif +#ifdef CONFIG_PID_NS + case CLONE_NEWPID: + return current_in_namespace(to_pid_ns(ns)); +#endif +#ifdef CONFIG_TIME_NS + case CLONE_NEWTIME: + return current_in_namespace(to_time_ns(ns)); +#endif +#ifdef CONFIG_USER_NS + case CLONE_NEWUSER: + return current_in_namespace(to_user_ns(ns)); +#endif +#ifdef CONFIG_UTS_NS + case CLONE_NEWUTS: + return current_in_namespace(to_uts_ns(ns)); +#endif + default: + VFS_WARN_ON_ONCE(true); + return false; + } +} + static struct dentry *nsfs_fh_to_dentry(struct super_block *sb, struct fid *fh, int fh_len, int fh_type) { diff --git a/include/linux/ns_common.h b/include/linux/ns_common.h index 3f05dd7d40c7..bd4492ef6ffc 100644 --- a/include/linux/ns_common.h +++ b/include/linux/ns_common.h @@ -129,8 +129,10 @@ struct ns_common { }; }; +bool is_current_namespace(struct ns_common *ns); int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_operations *ops, int inum); void __ns_common_free(struct ns_common *ns); +struct ns_common *__must_check ns_owner(struct ns_common *ns); static __always_inline bool is_initial_namespace(struct ns_common *ns) { diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h index 66c06fcdfe19..cf84d98964b2 100644 --- a/include/linux/syscalls.h +++ b/include/linux/syscalls.h @@ -77,6 +77,7 @@ struct cachestat_range; struct cachestat; struct statmount; struct mnt_id_req; +struct ns_id_req; struct xattr_args; struct file_attr; @@ -437,6 +438,9 @@ asmlinkage long sys_statmount(const struct mnt_id_req __user *req, asmlinkage long sys_listmount(const struct mnt_id_req __user *req, u64 __user *mnt_ids, size_t nr_mnt_ids, unsigned int flags); +asmlinkage long sys_listns(const struct ns_id_req __user *req, + u64 __user *ns_ids, size_t nr_ns_ids, + unsigned int flags); asmlinkage long sys_truncate(const char __user *path, long length); asmlinkage long sys_ftruncate(unsigned int fd, off_t length); #if BITS_PER_LONG == 32 diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h index 9a9aebbf96b9..9c3be157397e 100644 --- a/include/linux/user_namespace.h +++ b/include/linux/user_namespace.h @@ -166,13 +166,13 @@ static inline void set_userns_rlimit_max(struct user_namespace *ns, ns->rlimit_max[type] = max <= LONG_MAX ? max : LONG_MAX; } -#ifdef CONFIG_USER_NS - static inline struct user_namespace *to_user_ns(struct ns_common *ns) { return container_of(ns, struct user_namespace, ns); } +#ifdef CONFIG_USER_NS + static inline struct user_namespace *get_user_ns(struct user_namespace *ns) { if (ns) diff --git a/include/uapi/linux/nsfs.h b/include/uapi/linux/nsfs.h index f8bc2aad74d6..a25e38d1c874 100644 --- a/include/uapi/linux/nsfs.h +++ b/include/uapi/linux/nsfs.h @@ -81,4 +81,48 @@ enum init_ns_id { #endif }; +enum ns_type { + TIME_NS = (1ULL << 7), /* CLONE_NEWTIME */ + MNT_NS = (1ULL << 17), /* CLONE_NEWNS */ + CGROUP_NS = (1ULL << 25), /* CLONE_NEWCGROUP */ + UTS_NS = (1ULL << 26), /* CLONE_NEWUTS */ + IPC_NS = (1ULL << 27), /* CLONE_NEWIPC */ + USER_NS = (1ULL << 28), /* CLONE_NEWUSER */ + PID_NS = (1ULL << 29), /* CLONE_NEWPID */ + NET_NS = (1ULL << 30), /* CLONE_NEWNET */ +}; + +/** + * struct ns_id_req - namespace ID request structure + * @size: size of this structure + * @spare: reserved for future use + * @filter: filter mask + * @ns_id: last namespace id + * @user_ns_id: owning user namespace ID + * + * Structure for passing namespace ID and miscellaneous parameters to + * statns(2) and listns(2). + * + * For statns(2) @param represents the request mask. + * For listns(2) @param represents the last listed mount id (or zero). + */ +struct ns_id_req { + __u32 size; + __u32 spare; + __u64 ns_id; + struct /* listns */ { + __u32 ns_type; + __u32 spare2; + __u64 user_ns_id; + }; +}; + +/* + * Special @user_ns_id value that can be passed to listns() + */ +#define LISTNS_CURRENT_USER 0xffffffffffffffff /* Caller's userns */ + +/* List of all ns_id_req versions. */ +#define NS_ID_REQ_SIZE_VER0 32 /* sizeof first published struct */ + #endif /* __LINUX_NSFS_H */ diff --git a/kernel/nscommon.c b/kernel/nscommon.c index 4cbe1ecc8df0..6fe1c747fa46 100644 --- a/kernel/nscommon.c +++ b/kernel/nscommon.c @@ -98,7 +98,7 @@ void __ns_common_free(struct ns_common *ns) proc_free_inum(ns->inum); } -static struct ns_common *ns_owner(struct ns_common *ns) +struct ns_common *__must_check ns_owner(struct ns_common *ns) { struct user_namespace *owner; diff --git a/kernel/nstree.c b/kernel/nstree.c index dcad6a308547..4a8838683b6b 100644 --- a/kernel/nstree.c +++ b/kernel/nstree.c @@ -5,6 +5,7 @@ #include #include #include +#include #include static __cacheline_aligned_in_smp DEFINE_SEQLOCK(ns_tree_lock); @@ -359,3 +360,399 @@ u64 __ns_tree_gen_id(struct ns_common *ns, u64 id) ns->ns_id = atomic64_inc_return(&namespace_cookie); return ns->ns_id; } + +struct klistns { + u64 __user *uns_ids; + u32 nr_ns_ids; + u64 last_ns_id; + u64 user_ns_id; + u32 ns_type; + struct user_namespace *user_ns; + bool userns_capable; + struct ns_common *first_ns; +}; + +static void __free_klistns_free(const struct klistns *kls) +{ + if (kls->user_ns_id != LISTNS_CURRENT_USER) + put_user_ns(kls->user_ns); + if (kls->first_ns && kls->first_ns->ops) + kls->first_ns->ops->put(kls->first_ns); +} + +#define NS_ALL (PID_NS | USER_NS | MNT_NS | UTS_NS | IPC_NS | NET_NS | CGROUP_NS | TIME_NS) + +static int copy_ns_id_req(const struct ns_id_req __user *req, + struct ns_id_req *kreq) +{ + int ret; + size_t usize; + + BUILD_BUG_ON(sizeof(struct ns_id_req) != NS_ID_REQ_SIZE_VER0); + + ret = get_user(usize, &req->size); + if (ret) + return -EFAULT; + if (unlikely(usize > PAGE_SIZE)) + return -E2BIG; + if (unlikely(usize < NS_ID_REQ_SIZE_VER0)) + return -EINVAL; + memset(kreq, 0, sizeof(*kreq)); + ret = copy_struct_from_user(kreq, sizeof(*kreq), req, usize); + if (ret) + return ret; + if (kreq->spare != 0) + return -EINVAL; + if (kreq->ns_type & ~NS_ALL) + return -EOPNOTSUPP; + return 0; +} + +static inline int prepare_klistns(struct klistns *kls, struct ns_id_req *kreq, + u64 __user *ns_ids, size_t nr_ns_ids) +{ + kls->last_ns_id = kreq->ns_id; + kls->user_ns_id = kreq->user_ns_id; + kls->nr_ns_ids = nr_ns_ids; + kls->ns_type = kreq->ns_type; + kls->uns_ids = ns_ids; + return 0; +} + +/* + * Lookup a namespace owned by owner with id >= ns_id. + * Returns the namespace with the smallest id that is >= ns_id. + */ +static struct ns_common *lookup_ns_owner_at(u64 ns_id, struct ns_common *owner) +{ + struct ns_common *ret = NULL; + struct rb_node *node; + + VFS_WARN_ON_ONCE(owner->ns_type != CLONE_NEWUSER); + + read_seqlock_excl(&ns_tree_lock); + node = owner->ns_owner_tree.rb_node; + + while (node) { + struct ns_common *ns; + + ns = node_to_ns_owner(node); + if (ns_id <= ns->ns_id) { + ret = ns; + if (ns_id == ns->ns_id) + break; + node = node->rb_left; + } else { + node = node->rb_right; + } + } + + if (ret) + ret = ns_get_unless_inactive(ret); + read_sequnlock_excl(&ns_tree_lock); + return ret; +} + +static struct ns_common *lookup_ns_id(u64 mnt_ns_id, int ns_type) +{ + struct ns_common *ns; + + guard(rcu)(); + ns = ns_tree_lookup_rcu(mnt_ns_id, ns_type); + if (!ns) + return NULL; + + if (!ns_get_unless_inactive(ns)) + return NULL; + + return ns; +} + +static inline bool __must_check ns_requested(const struct klistns *kls, + const struct ns_common *ns) +{ + return !kls->ns_type || (kls->ns_type & ns->ns_type); +} + +static inline bool __must_check may_list_ns(const struct klistns *kls, + struct ns_common *ns) +{ + if (kls->user_ns) { + if (kls->userns_capable) + return true; + } else { + struct ns_common *owner; + struct user_namespace *user_ns; + + owner = ns_owner(ns); + if (owner) + user_ns = to_user_ns(owner); + else + user_ns = &init_user_ns; + if (ns_capable_noaudit(user_ns, CAP_SYS_ADMIN)) + return true; + } + + if (is_current_namespace(ns)) + return true; + + if (ns->ns_type != CLONE_NEWUSER) + return false; + + if (ns_capable_noaudit(to_user_ns(ns), CAP_SYS_ADMIN)) + return true; + + return false; +} + +static void __ns_put(struct ns_common *ns) +{ + if (ns->ops) + ns->ops->put(ns); +} + +DEFINE_FREE(ns_put, struct ns_common *, if (!IS_ERR_OR_NULL(_T)) __ns_put(_T)) + +static inline struct ns_common *__must_check legitimize_ns(const struct klistns *kls, + struct ns_common *candidate) +{ + struct ns_common *ns __free(ns_put) = NULL; + + if (!ns_requested(kls, candidate)) + return NULL; + + ns = ns_get_unless_inactive(candidate); + if (!ns) + return NULL; + + if (!may_list_ns(kls, ns)) + return NULL; + + return no_free_ptr(ns); +} + +static ssize_t do_listns_userns(struct klistns *kls) +{ + u64 __user *ns_ids = kls->uns_ids; + size_t nr_ns_ids = kls->nr_ns_ids; + struct ns_common *ns = NULL, *first_ns = NULL; + const struct list_head *head; + ssize_t ret; + + VFS_WARN_ON_ONCE(!kls->user_ns_id); + + if (kls->user_ns_id == LISTNS_CURRENT_USER) + ns = to_ns_common(current_user_ns()); + else if (kls->user_ns_id) + ns = lookup_ns_id(kls->user_ns_id, CLONE_NEWUSER); + if (!ns) + return -EINVAL; + kls->user_ns = to_user_ns(ns); + + /* + * Use the rbtree to find the first namespace we care about and + * then use it's list entry to iterate from there. + */ + if (kls->last_ns_id) { + kls->first_ns = lookup_ns_owner_at(kls->last_ns_id + 1, ns); + if (!kls->first_ns) + return -ENOENT; + first_ns = kls->first_ns; + } + + ret = 0; + head = &to_ns_common(kls->user_ns)->ns_owner; + kls->userns_capable = ns_capable_noaudit(kls->user_ns, CAP_SYS_ADMIN); + + rcu_read_lock(); + + if (!first_ns) + first_ns = list_entry_rcu(head->next, typeof(*ns), ns_owner_entry); + for (ns = first_ns; &ns->ns_owner_entry != head && nr_ns_ids; + ns = list_entry_rcu(ns->ns_owner_entry.next, typeof(*ns), ns_owner_entry)) { + struct ns_common *valid __free(ns_put); + + valid = legitimize_ns(kls, ns); + if (!valid) + continue; + + rcu_read_unlock(); + + if (put_user(valid->ns_id, ns_ids + ret)) + return -EINVAL; + nr_ns_ids--; + ret++; + + rcu_read_lock(); + } + + rcu_read_unlock(); + return ret; +} + +/* + * Lookup a namespace with id >= ns_id in either the unified tree or a type-specific tree. + * Returns the namespace with the smallest id that is >= ns_id. + */ +static struct ns_common *lookup_ns_id_at(u64 ns_id, int ns_type) +{ + struct ns_common *ret = NULL; + struct ns_tree *ns_tree = NULL; + struct rb_node *node; + + if (ns_type) { + ns_tree = ns_tree_from_type(ns_type); + if (!ns_tree) + return NULL; + } + + read_seqlock_excl(&ns_tree_lock); + if (ns_tree) + node = ns_tree->ns_tree.rb_node; + else + node = ns_unified_tree.rb_node; + + while (node) { + struct ns_common *ns; + + if (ns_type) + ns = node_to_ns(node); + else + ns = node_to_ns_unified(node); + + if (ns_id <= ns->ns_id) { + if (ns_type) + ret = node_to_ns(node); + else + ret = node_to_ns_unified(node); + if (ns_id == ns->ns_id) + break; + node = node->rb_left; + } else { + node = node->rb_right; + } + } + + if (ret) + ret = ns_get_unless_inactive(ret); + read_sequnlock_excl(&ns_tree_lock); + return ret; +} + +static inline struct ns_common *first_ns_common(const struct list_head *head, + struct ns_tree *ns_tree) +{ + if (ns_tree) + return list_entry_rcu(head->next, struct ns_common, ns_list_node); + return list_entry_rcu(head->next, struct ns_common, ns_unified_list_node); +} + +static inline struct ns_common *next_ns_common(struct ns_common *ns, + struct ns_tree *ns_tree) +{ + if (ns_tree) + return list_entry_rcu(ns->ns_list_node.next, struct ns_common, ns_list_node); + return list_entry_rcu(ns->ns_unified_list_node.next, struct ns_common, ns_unified_list_node); +} + +static inline bool ns_common_is_head(struct ns_common *ns, + const struct list_head *head, + struct ns_tree *ns_tree) +{ + if (ns_tree) + return &ns->ns_list_node == head; + return &ns->ns_unified_list_node == head; +} + +static ssize_t do_listns(struct klistns *kls) +{ + u64 __user *ns_ids = kls->uns_ids; + size_t nr_ns_ids = kls->nr_ns_ids; + struct ns_common *ns, *first_ns = NULL; + struct ns_tree *ns_tree = NULL; + const struct list_head *head; + u32 ns_type; + ssize_t ret; + + if (hweight32(kls->ns_type) == 1) + ns_type = kls->ns_type; + else + ns_type = 0; + + if (ns_type) { + ns_tree = ns_tree_from_type(ns_type); + if (!ns_tree) + return -EINVAL; + } + + if (kls->last_ns_id) { + kls->first_ns = lookup_ns_id_at(kls->last_ns_id + 1, ns_type); + if (!kls->first_ns) + return -ENOENT; + first_ns = kls->first_ns; + } + + ret = 0; + if (ns_tree) + head = &ns_tree->ns_list; + else + head = &ns_unified_list; + + rcu_read_lock(); + + if (!first_ns) + first_ns = first_ns_common(head, ns_tree); + + for (ns = first_ns; !ns_common_is_head(ns, head, ns_tree) && nr_ns_ids; + ns = next_ns_common(ns, ns_tree)) { + struct ns_common *valid __free(ns_put); + + valid = legitimize_ns(kls, ns); + if (!valid) + continue; + + rcu_read_unlock(); + + if (put_user(valid->ns_id, ns_ids + ret)) + return -EINVAL; + + nr_ns_ids--; + ret++; + + rcu_read_lock(); + } + + rcu_read_unlock(); + return ret; +} + +SYSCALL_DEFINE4(listns, const struct ns_id_req __user *, req, + u64 __user *, ns_ids, size_t, nr_ns_ids, unsigned int, flags) +{ + struct klistns klns __free(klistns_free) = {}; + const size_t maxcount = 1000000; + struct ns_id_req kreq; + ssize_t ret; + + if (flags) + return -EINVAL; + + if (unlikely(nr_ns_ids > maxcount)) + return -EOVERFLOW; + + if (!access_ok(ns_ids, nr_ns_ids * sizeof(*ns_ids))) + return -EFAULT; + + ret = copy_ns_id_req(req, &kreq); + if (ret) + return ret; + + ret = prepare_klistns(&klns, &kreq, ns_ids, nr_ns_ids); + if (ret) + return ret; + + if (kreq.user_ns_id) + return do_listns_userns(&klns); + + return do_listns(&klns); +} -- cgit v1.2.3 From c18d4b190a46651726c9a952667c74d2deb33c28 Mon Sep 17 00:00:00 2001 From: Samiullah Khawaja Date: Tue, 28 Oct 2025 20:30:05 +0000 Subject: net: Extend NAPI threaded polling to allow kthread based busy polling Add a new state NAPI_STATE_THREADED_BUSY_POLL to the NAPI state enum to enable and disable threaded busy polling. When threaded busy polling is enabled for a NAPI, enable NAPI_STATE_THREADED also. When the threaded NAPI is scheduled, set NAPI_STATE_IN_BUSY_POLL to signal napi_complete_done not to rearm interrupts. Whenever NAPI_STATE_THREADED_BUSY_POLL is unset, the NAPI_STATE_IN_BUSY_POLL will be unset, napi_complete_done unsets the NAPI_STATE_SCHED_THREADED bit also, which in turn will make the kthread go to sleep. Signed-off-by: Samiullah Khawaja Reviewed-by: Willem de Bruijn Acked-by: Martin Karsten Tested-by: Martin Karsten Link: https://patch.msgid.link/20251028203007.575686-2-skhawaja@google.com Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/netdev.yaml | 5 +-- Documentation/networking/napi.rst | 50 +++++++++++++++++++++++++++- include/linux/netdevice.h | 4 ++- include/uapi/linux/netdev.h | 1 + net/core/dev.c | 58 +++++++++++++++++++++++++++------ net/core/dev.h | 3 ++ net/core/netdev-genl-gen.c | 2 +- tools/include/uapi/linux/netdev.h | 1 + 8 files changed, 109 insertions(+), 15 deletions(-) (limited to 'include/uapi/linux') diff --git a/Documentation/netlink/specs/netdev.yaml b/Documentation/netlink/specs/netdev.yaml index e00d3fa1c152..10c412b7433f 100644 --- a/Documentation/netlink/specs/netdev.yaml +++ b/Documentation/netlink/specs/netdev.yaml @@ -88,7 +88,7 @@ definitions: - name: napi-threaded type: enum - entries: [disabled, enabled] + entries: [disabled, enabled, busy-poll] attribute-sets: - @@ -291,7 +291,8 @@ attribute-sets: name: threaded doc: Whether the NAPI is configured to operate in threaded polling mode. If this is set to enabled then the NAPI context operates - in threaded polling mode. + in threaded polling mode. If this is set to busy-poll, then the + threaded polling mode also busy polls. type: u32 enum: napi-threaded - diff --git a/Documentation/networking/napi.rst b/Documentation/networking/napi.rst index 7dd60366f4ff..4e008efebb35 100644 --- a/Documentation/networking/napi.rst +++ b/Documentation/networking/napi.rst @@ -263,7 +263,9 @@ are not well known). Busy polling is enabled by either setting ``SO_BUSY_POLL`` on selected sockets or using the global ``net.core.busy_poll`` and ``net.core.busy_read`` sysctls. An io_uring API for NAPI busy polling -also exists. +also exists. Threaded polling of NAPI also has a mode to busy poll for +packets (:ref:`threaded busy polling`) using the NAPI +processing kthread. epoll-based busy polling ------------------------ @@ -426,6 +428,52 @@ Therefore, setting ``gro_flush_timeout`` and ``napi_defer_hard_irqs`` is the recommended usage, because otherwise setting ``irq-suspend-timeout`` might not have any discernible effect. +.. _threaded_busy_poll: + +Threaded NAPI busy polling +-------------------------- + +Threaded NAPI busy polling extends threaded NAPI and adds support to do +continuous busy polling of the NAPI. This can be useful for forwarding or +AF_XDP applications. + +Threaded NAPI busy polling can be enabled on per NIC queue basis using Netlink. + +For example, using the following script: + +.. code-block:: bash + + $ ynl --family netdev --do napi-set \ + --json='{"id": 66, "threaded": "busy-poll"}' + +The kernel will create a kthread that busy polls on this NAPI. + +The user may elect to set the CPU affinity of this kthread to an unused CPU +core to improve how often the NAPI is polled at the expense of wasted CPU +cycles. Note that this will keep the CPU core busy with 100% usage. + +Once threaded busy polling is enabled for a NAPI, PID of the kthread can be +retrieved using Netlink so the affinity of the kthread can be set up. + +For example, the following script can be used to fetch the PID: + +.. code-block:: bash + + $ ynl --family netdev --do napi-get --json='{"id": 66}' + +This will output something like following, the pid `258` is the PID of the +kthread that is polling this NAPI. + +.. code-block:: bash + + $ {'defer-hard-irqs': 0, + 'gro-flush-timeout': 0, + 'id': 66, + 'ifindex': 2, + 'irq-suspend-timeout': 0, + 'pid': 258, + 'threaded': 'busy-poll'} + .. _threaded: Threaded NAPI diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 9c1e5042c5e7..e808071dbb7d 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -423,11 +423,12 @@ enum { NAPI_STATE_NPSVC, /* Netpoll - don't dequeue from poll_list */ NAPI_STATE_LISTED, /* NAPI added to system lists */ NAPI_STATE_NO_BUSY_POLL, /* Do not add in napi_hash, no busy polling */ - NAPI_STATE_IN_BUSY_POLL, /* sk_busy_loop() owns this NAPI */ + NAPI_STATE_IN_BUSY_POLL, /* Do not rearm NAPI interrupt */ NAPI_STATE_PREFER_BUSY_POLL, /* prefer busy-polling over softirq processing*/ NAPI_STATE_THREADED, /* The poll is performed inside its own thread*/ NAPI_STATE_SCHED_THREADED, /* Napi is currently scheduled in threaded mode */ NAPI_STATE_HAS_NOTIFIER, /* Napi has an IRQ notifier */ + NAPI_STATE_THREADED_BUSY_POLL, /* The threaded NAPI poller will busy poll */ }; enum { @@ -442,6 +443,7 @@ enum { NAPIF_STATE_THREADED = BIT(NAPI_STATE_THREADED), NAPIF_STATE_SCHED_THREADED = BIT(NAPI_STATE_SCHED_THREADED), NAPIF_STATE_HAS_NOTIFIER = BIT(NAPI_STATE_HAS_NOTIFIER), + NAPIF_STATE_THREADED_BUSY_POLL = BIT(NAPI_STATE_THREADED_BUSY_POLL), }; enum gro_result { diff --git a/include/uapi/linux/netdev.h b/include/uapi/linux/netdev.h index 48eb49aa03d4..048c8de1a130 100644 --- a/include/uapi/linux/netdev.h +++ b/include/uapi/linux/netdev.h @@ -80,6 +80,7 @@ enum netdev_qstats_scope { enum netdev_napi_threaded { NETDEV_NAPI_THREADED_DISABLED, NETDEV_NAPI_THREADED_ENABLED, + NETDEV_NAPI_THREADED_BUSY_POLL, }; enum { diff --git a/net/core/dev.c b/net/core/dev.c index dccc1176f3c6..2c1de5fb97d9 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -7089,7 +7089,8 @@ static void napi_stop_kthread(struct napi_struct *napi) */ if ((val & NAPIF_STATE_SCHED_THREADED) || !(val & NAPIF_STATE_SCHED)) { - new = val & (~NAPIF_STATE_THREADED); + new = val & (~(NAPIF_STATE_THREADED | + NAPIF_STATE_THREADED_BUSY_POLL)); } else { msleep(20); continue; @@ -7113,6 +7114,16 @@ static void napi_stop_kthread(struct napi_struct *napi) napi->thread = NULL; } +static void napi_set_threaded_state(struct napi_struct *napi, + enum netdev_napi_threaded threaded_mode) +{ + bool threaded = threaded_mode != NETDEV_NAPI_THREADED_DISABLED; + bool busy_poll = threaded_mode == NETDEV_NAPI_THREADED_BUSY_POLL; + + assign_bit(NAPI_STATE_THREADED, &napi->state, threaded); + assign_bit(NAPI_STATE_THREADED_BUSY_POLL, &napi->state, busy_poll); +} + int napi_set_threaded(struct napi_struct *napi, enum netdev_napi_threaded threaded) { @@ -7139,7 +7150,7 @@ int napi_set_threaded(struct napi_struct *napi, } else { /* Make sure kthread is created before THREADED bit is set. */ smp_mb__before_atomic(); - assign_bit(NAPI_STATE_THREADED, &napi->state, threaded); + napi_set_threaded_state(napi, threaded); } return 0; @@ -7531,7 +7542,9 @@ void napi_disable_locked(struct napi_struct *n) } new = val | NAPIF_STATE_SCHED | NAPIF_STATE_NPSVC; - new &= ~(NAPIF_STATE_THREADED | NAPIF_STATE_PREFER_BUSY_POLL); + new &= ~(NAPIF_STATE_THREADED | + NAPIF_STATE_THREADED_BUSY_POLL | + NAPIF_STATE_PREFER_BUSY_POLL); } while (!try_cmpxchg(&n->state, &val, new)); hrtimer_cancel(&n->timer); @@ -7743,7 +7756,7 @@ static int napi_thread_wait(struct napi_struct *napi) return -1; } -static void napi_threaded_poll_loop(struct napi_struct *napi) +static void napi_threaded_poll_loop(struct napi_struct *napi, bool busy_poll) { struct bpf_net_context __bpf_net_ctx, *bpf_net_ctx; struct softnet_data *sd; @@ -7772,22 +7785,47 @@ static void napi_threaded_poll_loop(struct napi_struct *napi) } skb_defer_free_flush(); bpf_net_ctx_clear(bpf_net_ctx); + + /* When busy poll is enabled, the old packets are not flushed in + * napi_complete_done. So flush them here. + */ + if (busy_poll) + gro_flush_normal(&napi->gro, HZ >= 1000); local_bh_enable(); + /* Call cond_resched here to avoid watchdog warnings. */ + if (repoll || busy_poll) { + rcu_softirq_qs_periodic(last_qs); + cond_resched(); + } + if (!repoll) break; - - rcu_softirq_qs_periodic(last_qs); - cond_resched(); } } static int napi_threaded_poll(void *data) { struct napi_struct *napi = data; + bool want_busy_poll; + bool in_busy_poll; + unsigned long val; + + while (!napi_thread_wait(napi)) { + val = READ_ONCE(napi->state); + + want_busy_poll = val & NAPIF_STATE_THREADED_BUSY_POLL; + in_busy_poll = val & NAPIF_STATE_IN_BUSY_POLL; - while (!napi_thread_wait(napi)) - napi_threaded_poll_loop(napi); + if (unlikely(val & NAPIF_STATE_DISABLE)) + want_busy_poll = false; + + if (want_busy_poll != in_busy_poll) + assign_bit(NAPI_STATE_IN_BUSY_POLL, &napi->state, + want_busy_poll); + + napi_threaded_poll_loop(napi, want_busy_poll); + } return 0; } @@ -13097,7 +13135,7 @@ static void run_backlog_napi(unsigned int cpu) { struct softnet_data *sd = per_cpu_ptr(&softnet_data, cpu); - napi_threaded_poll_loop(&sd->backlog); + napi_threaded_poll_loop(&sd->backlog, false); } static void backlog_napi_setup(unsigned int cpu) diff --git a/net/core/dev.h b/net/core/dev.h index 900880e8b5b4..4d872a79bafb 100644 --- a/net/core/dev.h +++ b/net/core/dev.h @@ -317,6 +317,9 @@ static inline void napi_set_irq_suspend_timeout(struct napi_struct *n, static inline enum netdev_napi_threaded napi_get_threaded(struct napi_struct *n) { + if (test_bit(NAPI_STATE_THREADED_BUSY_POLL, &n->state)) + return NETDEV_NAPI_THREADED_BUSY_POLL; + if (test_bit(NAPI_STATE_THREADED, &n->state)) return NETDEV_NAPI_THREADED_ENABLED; diff --git a/net/core/netdev-genl-gen.c b/net/core/netdev-genl-gen.c index e9a2a6f26cb7..ff20435c45d2 100644 --- a/net/core/netdev-genl-gen.c +++ b/net/core/netdev-genl-gen.c @@ -97,7 +97,7 @@ static const struct nla_policy netdev_napi_set_nl_policy[NETDEV_A_NAPI_THREADED [NETDEV_A_NAPI_DEFER_HARD_IRQS] = NLA_POLICY_FULL_RANGE(NLA_U32, &netdev_a_napi_defer_hard_irqs_range), [NETDEV_A_NAPI_GRO_FLUSH_TIMEOUT] = { .type = NLA_UINT, }, [NETDEV_A_NAPI_IRQ_SUSPEND_TIMEOUT] = { .type = NLA_UINT, }, - [NETDEV_A_NAPI_THREADED] = NLA_POLICY_MAX(NLA_U32, 1), + [NETDEV_A_NAPI_THREADED] = NLA_POLICY_MAX(NLA_U32, 2), }; /* NETDEV_CMD_BIND_TX - do */ diff --git a/tools/include/uapi/linux/netdev.h b/tools/include/uapi/linux/netdev.h index 48eb49aa03d4..048c8de1a130 100644 --- a/tools/include/uapi/linux/netdev.h +++ b/tools/include/uapi/linux/netdev.h @@ -80,6 +80,7 @@ enum netdev_qstats_scope { enum netdev_napi_threaded { NETDEV_NAPI_THREADED_DISABLED, NETDEV_NAPI_THREADED_ENABLED, + NETDEV_NAPI_THREADED_BUSY_POLL, }; enum { -- cgit v1.2.3 From e6e93fb01302e9b7a15d17f3b8a00eff8a601654 Mon Sep 17 00:00:00 2001 From: Oleksij Rempel Date: Mon, 27 Oct 2025 13:27:59 +0100 Subject: ethtool: netlink: add ETHTOOL_MSG_MSE_GET and wire up PHY MSE access Introduce the userspace entry point for PHY MSE diagnostics via ethtool netlink. This exposes the core API added previously and returns both capability information and one or more snapshots. Userspace sends ETHTOOL_MSG_MSE_GET. The reply carries: - ETHTOOL_A_MSE_CAPABILITIES: scale limits and timing information - ETHTOOL_A_MSE_CHANNEL_* nests: one or more snapshots (per-channel if available, otherwise WORST, otherwise LINK) Link down returns -ENETDOWN. Changes: - YAML: add attribute sets (mse, mse-capabilities, mse-snapshot) and the mse-get operation - UAPI (generated): add ETHTOOL_A_MSE_* enums and message IDs, ETHTOOL_MSG_MSE_GET/REPLY - ethtool core: add net/ethtool/mse.c implementing the request, register genl op, and hook into ethnl dispatch - docs: document MSE_GET in ethtool-netlink.rst The include/uapi/linux/ethtool_netlink_generated.h is generated from Documentation/netlink/specs/ethtool.yaml. Signed-off-by: Oleksij Rempel Link: https://patch.msgid.link/20251027122801.982364-3-o.rempel@pengutronix.de Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/ethtool.yaml | 86 +++++++ Documentation/networking/ethtool-netlink.rst | 64 +++++ include/uapi/linux/ethtool_netlink_generated.h | 35 +++ net/ethtool/Makefile | 2 +- net/ethtool/mse.c | 329 +++++++++++++++++++++++++ net/ethtool/netlink.c | 10 + net/ethtool/netlink.h | 2 + 7 files changed, 527 insertions(+), 1 deletion(-) create mode 100644 net/ethtool/mse.c (limited to 'include/uapi/linux') diff --git a/Documentation/netlink/specs/ethtool.yaml b/Documentation/netlink/specs/ethtool.yaml index 6a0fb1974513..05d2b6508b59 100644 --- a/Documentation/netlink/specs/ethtool.yaml +++ b/Documentation/netlink/specs/ethtool.yaml @@ -1823,6 +1823,73 @@ attribute-sets: type: uint enum: pse-event doc: List of events reported by the PSE controller + - + name: mse-capabilities + doc: MSE capabilities attribute set + attr-cnt-name: --ethtool-a-mse-capabilities-cnt + attributes: + - + name: max-average-mse + type: uint + - + name: max-peak-mse + type: uint + - + name: refresh-rate-ps + type: uint + - + name: num-symbols + type: uint + - + name: mse-snapshot + doc: MSE snapshot attribute set + attr-cnt-name: --ethtool-a-mse-snapshot-cnt + attributes: + - + name: average-mse + type: uint + - + name: peak-mse + type: uint + - + name: worst-peak-mse + type: uint + - + name: mse + attr-cnt-name: --ethtool-a-mse-cnt + attributes: + - + name: header + type: nest + nested-attributes: header + - + name: capabilities + type: nest + nested-attributes: mse-capabilities + - + name: channel-a + type: nest + nested-attributes: mse-snapshot + - + name: channel-b + type: nest + nested-attributes: mse-snapshot + - + name: channel-c + type: nest + nested-attributes: mse-snapshot + - + name: channel-d + type: nest + nested-attributes: mse-snapshot + - + name: worst-channel + type: nest + nested-attributes: mse-snapshot + - + name: link + type: nest + nested-attributes: mse-snapshot operations: enum-model: directional @@ -2756,6 +2823,25 @@ operations: attributes: - header - context + - + name: mse-get + doc: Get PHY MSE measurement data and capabilities. + attribute-set: mse + do: &mse-get-op + request: + attributes: + - header + reply: + attributes: + - header + - capabilities + - channel-a + - channel-b + - channel-c + - channel-d + - worst-channel + - link + dump: *mse-get-op mcast-groups: list: diff --git a/Documentation/networking/ethtool-netlink.rst b/Documentation/networking/ethtool-netlink.rst index b270886c5f5d..af56c304cef4 100644 --- a/Documentation/networking/ethtool-netlink.rst +++ b/Documentation/networking/ethtool-netlink.rst @@ -242,6 +242,7 @@ Userspace to kernel: ``ETHTOOL_MSG_RSS_SET`` set RSS settings ``ETHTOOL_MSG_RSS_CREATE_ACT`` create an additional RSS context ``ETHTOOL_MSG_RSS_DELETE_ACT`` delete an additional RSS context + ``ETHTOOL_MSG_MSE_GET`` get MSE diagnostic data ===================================== ================================= Kernel to userspace: @@ -299,6 +300,7 @@ Kernel to userspace: ``ETHTOOL_MSG_RSS_CREATE_ACT_REPLY`` create an additional RSS context ``ETHTOOL_MSG_RSS_CREATE_NTF`` additional RSS context created ``ETHTOOL_MSG_RSS_DELETE_NTF`` additional RSS context deleted + ``ETHTOOL_MSG_MSE_GET_REPLY`` MSE diagnostic data ======================================== ================================= ``GET`` requests are sent by userspace applications to retrieve device @@ -2458,6 +2460,68 @@ Kernel response contents: For a description of each attribute, see ``TSCONFIG_GET``. +MSE_GET +======= + +Retrieves detailed Mean Square Error (MSE) diagnostic information from the PHY. + +Request Contents: + + ==================================== ====== ============================ + ``ETHTOOL_A_MSE_HEADER`` nested request header + ==================================== ====== ============================ + +Kernel Response Contents: + + ==================================== ====== ================================ + ``ETHTOOL_A_MSE_HEADER`` nested reply header + ``ETHTOOL_A_MSE_CAPABILITIES`` nested capability/scale info for MSE + measurements + ``ETHTOOL_A_MSE_CHANNEL_A`` nested snapshot for Channel A + ``ETHTOOL_A_MSE_CHANNEL_B`` nested snapshot for Channel B + ``ETHTOOL_A_MSE_CHANNEL_C`` nested snapshot for Channel C + ``ETHTOOL_A_MSE_CHANNEL_D`` nested snapshot for Channel D + ``ETHTOOL_A_MSE_WORST_CHANNEL`` nested snapshot for worst channel + ``ETHTOOL_A_MSE_LINK`` nested snapshot for link-wide aggregate + ==================================== ====== ================================ + +MSE Capabilities +---------------- + +This nested attribute reports the capability / scaling properties used to +interpret snapshot values. + + ============================================== ====== ========================= + ``ETHTOOL_A_MSE_CAPABILITIES_MAX_AVERAGE_MSE`` uint max avg_mse scale + ``ETHTOOL_A_MSE_CAPABILITIES_MAX_PEAK_MSE`` uint max peak_mse scale + ``ETHTOOL_A_MSE_CAPABILITIES_REFRESH_RATE_PS`` uint sample rate (picoseconds) + ``ETHTOOL_A_MSE_CAPABILITIES_NUM_SYMBOLS`` uint symbols per HW sample + ============================================== ====== ========================= + +The max-average/peak fields are included only if the corresponding metric +is supported by the PHY. Their absence indicates that the metric is not +available. + +See ``struct phy_mse_capability`` kernel documentation in +``include/linux/phy.h``. + +MSE Snapshot +------------ + +Each per-channel nest contains an atomic snapshot of MSE values for that +selector (channel A/B/C/D, worst channel, or link). + + ========================================== ====== =================== + ``ETHTOOL_A_MSE_SNAPSHOT_AVERAGE_MSE`` uint average MSE value + ``ETHTOOL_A_MSE_SNAPSHOT_PEAK_MSE`` uint current peak MSE + ``ETHTOOL_A_MSE_SNAPSHOT_WORST_PEAK_MSE`` uint worst-case peak MSE + ========================================== ====== =================== + +Within each channel nest, only the metrics supported by the PHY will be present. + +See ``struct phy_mse_snapshot`` kernel documentation in +``include/linux/phy.h``. + Request translation =================== diff --git a/include/uapi/linux/ethtool_netlink_generated.h b/include/uapi/linux/ethtool_netlink_generated.h index 0e8ac0d974e2..b71b175df46d 100644 --- a/include/uapi/linux/ethtool_netlink_generated.h +++ b/include/uapi/linux/ethtool_netlink_generated.h @@ -803,6 +803,39 @@ enum { ETHTOOL_A_PSE_NTF_MAX = (__ETHTOOL_A_PSE_NTF_CNT - 1) }; +enum { + ETHTOOL_A_MSE_CAPABILITIES_MAX_AVERAGE_MSE = 1, + ETHTOOL_A_MSE_CAPABILITIES_MAX_PEAK_MSE, + ETHTOOL_A_MSE_CAPABILITIES_REFRESH_RATE_PS, + ETHTOOL_A_MSE_CAPABILITIES_NUM_SYMBOLS, + + __ETHTOOL_A_MSE_CAPABILITIES_CNT, + ETHTOOL_A_MSE_CAPABILITIES_MAX = (__ETHTOOL_A_MSE_CAPABILITIES_CNT - 1) +}; + +enum { + ETHTOOL_A_MSE_SNAPSHOT_AVERAGE_MSE = 1, + ETHTOOL_A_MSE_SNAPSHOT_PEAK_MSE, + ETHTOOL_A_MSE_SNAPSHOT_WORST_PEAK_MSE, + + __ETHTOOL_A_MSE_SNAPSHOT_CNT, + ETHTOOL_A_MSE_SNAPSHOT_MAX = (__ETHTOOL_A_MSE_SNAPSHOT_CNT - 1) +}; + +enum { + ETHTOOL_A_MSE_HEADER = 1, + ETHTOOL_A_MSE_CAPABILITIES, + ETHTOOL_A_MSE_CHANNEL_A, + ETHTOOL_A_MSE_CHANNEL_B, + ETHTOOL_A_MSE_CHANNEL_C, + ETHTOOL_A_MSE_CHANNEL_D, + ETHTOOL_A_MSE_WORST_CHANNEL, + ETHTOOL_A_MSE_LINK, + + __ETHTOOL_A_MSE_CNT, + ETHTOOL_A_MSE_MAX = (__ETHTOOL_A_MSE_CNT - 1) +}; + enum { ETHTOOL_MSG_USER_NONE = 0, ETHTOOL_MSG_STRSET_GET = 1, @@ -855,6 +888,7 @@ enum { ETHTOOL_MSG_RSS_SET, ETHTOOL_MSG_RSS_CREATE_ACT, ETHTOOL_MSG_RSS_DELETE_ACT, + ETHTOOL_MSG_MSE_GET, __ETHTOOL_MSG_USER_CNT, ETHTOOL_MSG_USER_MAX = (__ETHTOOL_MSG_USER_CNT - 1) @@ -915,6 +949,7 @@ enum { ETHTOOL_MSG_RSS_CREATE_ACT_REPLY, ETHTOOL_MSG_RSS_CREATE_NTF, ETHTOOL_MSG_RSS_DELETE_NTF, + ETHTOOL_MSG_MSE_GET_REPLY, __ETHTOOL_MSG_KERNEL_CNT, ETHTOOL_MSG_KERNEL_MAX = (__ETHTOOL_MSG_KERNEL_CNT - 1) diff --git a/net/ethtool/Makefile b/net/ethtool/Makefile index 1e493553b977..629c10916670 100644 --- a/net/ethtool/Makefile +++ b/net/ethtool/Makefile @@ -9,4 +9,4 @@ ethtool_nl-y := netlink.o bitset.o strset.o linkinfo.o linkmodes.o rss.o \ channels.o coalesce.o pause.o eee.o tsinfo.o cabletest.o \ tunnels.o fec.o eeprom.o stats.o phc_vclocks.o mm.o \ module.o cmis_fw_update.o cmis_cdb.o pse-pd.o plca.o \ - phy.o tsconfig.o + phy.o tsconfig.o mse.o diff --git a/net/ethtool/mse.c b/net/ethtool/mse.c new file mode 100644 index 000000000000..6aac004c3ffc --- /dev/null +++ b/net/ethtool/mse.c @@ -0,0 +1,329 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include + +#include "netlink.h" +#include "common.h" + +/* Channels A-D only; WORST and LINK are exclusive alternatives */ +#define PHY_MSE_CHANNEL_COUNT 4 + +struct mse_req_info { + struct ethnl_req_info base; +}; + +struct mse_snapshot_entry { + struct phy_mse_snapshot snapshot; + int channel; +}; + +struct mse_reply_data { + struct ethnl_reply_data base; + struct phy_mse_capability capability; + struct mse_snapshot_entry *snapshots; + unsigned int num_snapshots; +}; + +static struct mse_reply_data * +mse_repdata(const struct ethnl_reply_data *reply_base) +{ + return container_of(reply_base, struct mse_reply_data, base); +} + +const struct nla_policy ethnl_mse_get_policy[] = { + [ETHTOOL_A_MSE_HEADER] = NLA_POLICY_NESTED(ethnl_header_policy_phy), +}; + +static int get_snapshot_if_supported(struct phy_device *phydev, + struct mse_reply_data *data, + unsigned int *idx, u32 cap_bit, + enum phy_mse_channel channel) +{ + int ret; + + if (data->capability.supported_caps & cap_bit) { + ret = phydev->drv->get_mse_snapshot(phydev, channel, + &data->snapshots[*idx].snapshot); + if (ret) + return ret; + data->snapshots[*idx].channel = channel; + (*idx)++; + } + + return 0; +} + +static int mse_get_channels(struct phy_device *phydev, + struct mse_reply_data *data) +{ + unsigned int i = 0; + int ret; + + if (!data->capability.supported_caps) + return 0; + + data->snapshots = kcalloc(PHY_MSE_CHANNEL_COUNT, + sizeof(*data->snapshots), GFP_KERNEL); + if (!data->snapshots) + return -ENOMEM; + + /* Priority 1: Individual channels */ + ret = get_snapshot_if_supported(phydev, data, &i, PHY_MSE_CAP_CHANNEL_A, + PHY_MSE_CHANNEL_A); + if (ret) + return ret; + ret = get_snapshot_if_supported(phydev, data, &i, PHY_MSE_CAP_CHANNEL_B, + PHY_MSE_CHANNEL_B); + if (ret) + return ret; + ret = get_snapshot_if_supported(phydev, data, &i, PHY_MSE_CAP_CHANNEL_C, + PHY_MSE_CHANNEL_C); + if (ret) + return ret; + ret = get_snapshot_if_supported(phydev, data, &i, PHY_MSE_CAP_CHANNEL_D, + PHY_MSE_CHANNEL_D); + if (ret) + return ret; + + /* If any individual channels were found, we are done. */ + if (i > 0) { + data->num_snapshots = i; + return 0; + } + + /* Priority 2: Worst channel, if no individual channels supported. */ + ret = get_snapshot_if_supported(phydev, data, &i, + PHY_MSE_CAP_WORST_CHANNEL, + PHY_MSE_CHANNEL_WORST); + if (ret) + return ret; + + /* If worst channel was found, we are done. */ + if (i > 0) { + data->num_snapshots = i; + return 0; + } + + /* Priority 3: Link-wide, if nothing else is supported. */ + ret = get_snapshot_if_supported(phydev, data, &i, PHY_MSE_CAP_LINK, + PHY_MSE_CHANNEL_LINK); + if (ret) + return ret; + + data->num_snapshots = i; + return 0; +} + +static int mse_prepare_data(const struct ethnl_req_info *req_base, + struct ethnl_reply_data *reply_base, + const struct genl_info *info) +{ + struct mse_reply_data *data = mse_repdata(reply_base); + struct net_device *dev = reply_base->dev; + struct phy_device *phydev; + int ret; + + phydev = ethnl_req_get_phydev(req_base, info->attrs, + ETHTOOL_A_MSE_HEADER, info->extack); + if (IS_ERR(phydev)) + return PTR_ERR(phydev); + if (!phydev) + return -EOPNOTSUPP; + + ret = ethnl_ops_begin(dev); + if (ret) + return ret; + + mutex_lock(&phydev->lock); + + if (!phydev->drv || !phydev->drv->get_mse_capability || + !phydev->drv->get_mse_snapshot) { + ret = -EOPNOTSUPP; + goto out_unlock; + } + if (!phydev->link) { + ret = -ENETDOWN; + goto out_unlock; + } + + ret = phydev->drv->get_mse_capability(phydev, &data->capability); + if (ret) + goto out_unlock; + + ret = mse_get_channels(phydev, data); + +out_unlock: + mutex_unlock(&phydev->lock); + ethnl_ops_complete(dev); + if (ret) + kfree(data->snapshots); + return ret; +} + +static void mse_cleanup_data(struct ethnl_reply_data *reply_base) +{ + struct mse_reply_data *data = mse_repdata(reply_base); + + kfree(data->snapshots); +} + +static int mse_reply_size(const struct ethnl_req_info *req_base, + const struct ethnl_reply_data *reply_base) +{ + const struct mse_reply_data *data = mse_repdata(reply_base); + size_t len = 0; + unsigned int i; + + /* ETHTOOL_A_MSE_CAPABILITIES */ + len += nla_total_size(0); + if (data->capability.supported_caps & PHY_MSE_CAP_AVG) + /* ETHTOOL_A_MSE_CAPABILITIES_MAX_AVERAGE_MSE */ + len += nla_total_size(sizeof(u64)); + if (data->capability.supported_caps & (PHY_MSE_CAP_PEAK | + PHY_MSE_CAP_WORST_PEAK)) + /* ETHTOOL_A_MSE_CAPABILITIES_MAX_PEAK_MSE */ + len += nla_total_size(sizeof(u64)); + /* ETHTOOL_A_MSE_CAPABILITIES_REFRESH_RATE_PS */ + len += nla_total_size(sizeof(u64)); + /* ETHTOOL_A_MSE_CAPABILITIES_NUM_SYMBOLS */ + len += nla_total_size(sizeof(u64)); + + for (i = 0; i < data->num_snapshots; i++) { + size_t snapshot_len = 0; + + /* Per-channel nest (e.g., ETHTOOL_A_MSE_CHANNEL_A / _B / _C / + * _D / _WORST_CHANNEL / _LINK) + */ + snapshot_len += nla_total_size(0); + + if (data->capability.supported_caps & PHY_MSE_CAP_AVG) + snapshot_len += nla_total_size(sizeof(u64)); + if (data->capability.supported_caps & PHY_MSE_CAP_PEAK) + snapshot_len += nla_total_size(sizeof(u64)); + if (data->capability.supported_caps & PHY_MSE_CAP_WORST_PEAK) + snapshot_len += nla_total_size(sizeof(u64)); + + len += snapshot_len; + } + + return len; +} + +static int mse_channel_to_attr(int ch) +{ + switch (ch) { + case PHY_MSE_CHANNEL_A: + return ETHTOOL_A_MSE_CHANNEL_A; + case PHY_MSE_CHANNEL_B: + return ETHTOOL_A_MSE_CHANNEL_B; + case PHY_MSE_CHANNEL_C: + return ETHTOOL_A_MSE_CHANNEL_C; + case PHY_MSE_CHANNEL_D: + return ETHTOOL_A_MSE_CHANNEL_D; + case PHY_MSE_CHANNEL_WORST: + return ETHTOOL_A_MSE_WORST_CHANNEL; + case PHY_MSE_CHANNEL_LINK: + return ETHTOOL_A_MSE_LINK; + default: + return -EINVAL; + } +} + +static int mse_fill_reply(struct sk_buff *skb, + const struct ethnl_req_info *req_base, + const struct ethnl_reply_data *reply_base) +{ + const struct mse_reply_data *data = mse_repdata(reply_base); + struct nlattr *nest; + unsigned int i; + int ret; + + nest = nla_nest_start(skb, ETHTOOL_A_MSE_CAPABILITIES); + if (!nest) + return -EMSGSIZE; + + if (data->capability.supported_caps & PHY_MSE_CAP_AVG) { + ret = nla_put_uint(skb, + ETHTOOL_A_MSE_CAPABILITIES_MAX_AVERAGE_MSE, + data->capability.max_average_mse); + if (ret < 0) + goto nla_put_nest_failure; + } + + if (data->capability.supported_caps & (PHY_MSE_CAP_PEAK | + PHY_MSE_CAP_WORST_PEAK)) { + ret = nla_put_uint(skb, ETHTOOL_A_MSE_CAPABILITIES_MAX_PEAK_MSE, + data->capability.max_peak_mse); + if (ret < 0) + goto nla_put_nest_failure; + } + + ret = nla_put_uint(skb, ETHTOOL_A_MSE_CAPABILITIES_REFRESH_RATE_PS, + data->capability.refresh_rate_ps); + if (ret < 0) + goto nla_put_nest_failure; + + ret = nla_put_uint(skb, ETHTOOL_A_MSE_CAPABILITIES_NUM_SYMBOLS, + data->capability.num_symbols); + if (ret < 0) + goto nla_put_nest_failure; + + nla_nest_end(skb, nest); + + for (i = 0; i < data->num_snapshots; i++) { + const struct mse_snapshot_entry *s = &data->snapshots[i]; + int chan_attr; + + chan_attr = mse_channel_to_attr(s->channel); + if (chan_attr < 0) + return chan_attr; + + nest = nla_nest_start(skb, chan_attr); + if (!nest) + return -EMSGSIZE; + + if (data->capability.supported_caps & PHY_MSE_CAP_AVG) { + ret = nla_put_uint(skb, + ETHTOOL_A_MSE_SNAPSHOT_AVERAGE_MSE, + s->snapshot.average_mse); + if (ret) + goto nla_put_nest_failure; + } + if (data->capability.supported_caps & PHY_MSE_CAP_PEAK) { + ret = nla_put_uint(skb, ETHTOOL_A_MSE_SNAPSHOT_PEAK_MSE, + s->snapshot.peak_mse); + if (ret) + goto nla_put_nest_failure; + } + if (data->capability.supported_caps & PHY_MSE_CAP_WORST_PEAK) { + ret = nla_put_uint(skb, + ETHTOOL_A_MSE_SNAPSHOT_WORST_PEAK_MSE, + s->snapshot.worst_peak_mse); + if (ret) + goto nla_put_nest_failure; + } + + nla_nest_end(skb, nest); + } + + return 0; + +nla_put_nest_failure: + nla_nest_cancel(skb, nest); + return ret; +} + +const struct ethnl_request_ops ethnl_mse_request_ops = { + .request_cmd = ETHTOOL_MSG_MSE_GET, + .reply_cmd = ETHTOOL_MSG_MSE_GET_REPLY, + .hdr_attr = ETHTOOL_A_MSE_HEADER, + .req_info_size = sizeof(struct mse_req_info), + .reply_data_size = sizeof(struct mse_reply_data), + + .prepare_data = mse_prepare_data, + .cleanup_data = mse_cleanup_data, + .reply_size = mse_reply_size, + .fill_reply = mse_fill_reply, +}; diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c index 2f813f25f07e..6e5f0f4f815a 100644 --- a/net/ethtool/netlink.c +++ b/net/ethtool/netlink.c @@ -420,6 +420,7 @@ ethnl_default_requests[__ETHTOOL_MSG_USER_CNT] = { [ETHTOOL_MSG_TSCONFIG_GET] = ðnl_tsconfig_request_ops, [ETHTOOL_MSG_TSCONFIG_SET] = ðnl_tsconfig_request_ops, [ETHTOOL_MSG_PHY_GET] = ðnl_phy_request_ops, + [ETHTOOL_MSG_MSE_GET] = ðnl_mse_request_ops, }; static struct ethnl_dump_ctx *ethnl_dump_context(struct netlink_callback *cb) @@ -1534,6 +1535,15 @@ static const struct genl_ops ethtool_genl_ops[] = { .policy = ethnl_rss_delete_policy, .maxattr = ARRAY_SIZE(ethnl_rss_delete_policy) - 1, }, + { + .cmd = ETHTOOL_MSG_MSE_GET, + .doit = ethnl_default_doit, + .start = ethnl_perphy_start, + .dumpit = ethnl_perphy_dumpit, + .done = ethnl_perphy_done, + .policy = ethnl_mse_get_policy, + .maxattr = ARRAY_SIZE(ethnl_mse_get_policy) - 1, + }, }; static const struct genl_multicast_group ethtool_nl_mcgrps[] = { diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h index 1d4f9ecb3d26..89010eaa67df 100644 --- a/net/ethtool/netlink.h +++ b/net/ethtool/netlink.h @@ -442,6 +442,7 @@ extern const struct ethnl_request_ops ethnl_plca_status_request_ops; extern const struct ethnl_request_ops ethnl_mm_request_ops; extern const struct ethnl_request_ops ethnl_phy_request_ops; extern const struct ethnl_request_ops ethnl_tsconfig_request_ops; +extern const struct ethnl_request_ops ethnl_mse_request_ops; extern const struct nla_policy ethnl_header_policy[ETHTOOL_A_HEADER_FLAGS + 1]; extern const struct nla_policy ethnl_header_policy_stats[ETHTOOL_A_HEADER_FLAGS + 1]; @@ -497,6 +498,7 @@ extern const struct nla_policy ethnl_module_fw_flash_act_policy[ETHTOOL_A_MODULE extern const struct nla_policy ethnl_phy_get_policy[ETHTOOL_A_PHY_HEADER + 1]; extern const struct nla_policy ethnl_tsconfig_get_policy[ETHTOOL_A_TSCONFIG_HEADER + 1]; extern const struct nla_policy ethnl_tsconfig_set_policy[ETHTOOL_A_TSCONFIG_MAX + 1]; +extern const struct nla_policy ethnl_mse_get_policy[ETHTOOL_A_MSE_HEADER + 1]; int ethnl_set_features(struct sk_buff *skb, struct genl_info *info); int ethnl_act_cable_test(struct sk_buff *skb, struct genl_info *info); -- cgit v1.2.3 From f16469ee733ac52b2373216803699cbb05e82786 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Fri, 31 Oct 2025 14:28:54 -0700 Subject: PCI/IDE: Enumerate Selective Stream IDE capabilities Link encryption is a new PCIe feature enumerated by "PCIe r7.0 section 7.9.26 IDE Extended Capability". It is both a standalone port + endpoint capability, and a building block for the security protocol defined by "PCIe r7.0 section 11 TEE Device Interface Security Protocol (TDISP)". That protocol coordinates device security setup between a platform TSM (TEE Security Manager) and a device DSM (Device Security Manager). While the platform TSM can allocate resources like Stream ID and manage keys, it still requires system software to manage the IDE capability register block. Add register definitions and basic enumeration in preparation for Selective IDE Stream establishment. A follow on change selects the new CONFIG_PCI_IDE symbol. Note that while the IDE specification defines both a point-to-point "Link Stream" and a Root Port to endpoint "Selective Stream", only "Selective Stream" is considered for Linux as that is the predominant mode expected by Trusted Execution Environment Security Managers (TSMs), and it is the security model that limits the number of PCI components within the TCB in a PCIe topology with switches. Co-developed-by: Alexey Kardashevskiy Signed-off-by: Alexey Kardashevskiy Co-developed-by: Xu Yilun Signed-off-by: Xu Yilun Reviewed-by: Jonathan Cameron Reviewed-by: Alexey Kardashevskiy Reviewed-by: Aneesh Kumar K.V Link: https://patch.msgid.link/20251031212902.2256310-3-dan.j.williams@intel.com Signed-off-by: Dan Williams --- drivers/pci/Kconfig | 3 ++ drivers/pci/Makefile | 1 + drivers/pci/ide.c | 88 +++++++++++++++++++++++++++++++++++++++++++ drivers/pci/pci.h | 6 +++ drivers/pci/probe.c | 1 + include/linux/pci.h | 7 ++++ include/uapi/linux/pci_regs.h | 81 +++++++++++++++++++++++++++++++++++++++ 7 files changed, 187 insertions(+) create mode 100644 drivers/pci/ide.c (limited to 'include/uapi/linux') diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig index f94f5d384362..b28423e2057f 100644 --- a/drivers/pci/Kconfig +++ b/drivers/pci/Kconfig @@ -122,6 +122,9 @@ config XEN_PCIDEV_FRONTEND config PCI_ATS bool +config PCI_IDE + bool + config PCI_DOE bool "Enable PCI Data Object Exchange (DOE) support" help diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile index 67647f1880fb..6612256fd37d 100644 --- a/drivers/pci/Makefile +++ b/drivers/pci/Makefile @@ -34,6 +34,7 @@ obj-$(CONFIG_PCI_P2PDMA) += p2pdma.o obj-$(CONFIG_XEN_PCIDEV_FRONTEND) += xen-pcifront.o obj-$(CONFIG_VGA_ARB) += vgaarb.o obj-$(CONFIG_PCI_DOE) += doe.o +obj-$(CONFIG_PCI_IDE) += ide.o obj-$(CONFIG_PCI_DYNAMIC_OF_NODES) += of_property.o obj-$(CONFIG_PCI_NPEM) += npem.o obj-$(CONFIG_PCIE_TPH) += tph.o diff --git a/drivers/pci/ide.c b/drivers/pci/ide.c new file mode 100644 index 000000000000..26866edf91b4 --- /dev/null +++ b/drivers/pci/ide.c @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright(c) 2024-2025 Intel Corporation. All rights reserved. */ + +/* PCIe r7.0 section 6.33 Integrity & Data Encryption (IDE) */ + +#define dev_fmt(fmt) "PCI/IDE: " fmt +#include +#include +#include + +#include "pci.h" + +static int __sel_ide_offset(u16 ide_cap, u8 nr_link_ide, u8 stream_index, + u8 nr_ide_mem) +{ + u32 offset = ide_cap + PCI_IDE_LINK_STREAM_0 + + nr_link_ide * PCI_IDE_LINK_BLOCK_SIZE; + + /* + * Assume a constant number of address association resources per stream + * index + */ + return offset + stream_index * PCI_IDE_SEL_BLOCK_SIZE(nr_ide_mem); +} + +void pci_ide_init(struct pci_dev *pdev) +{ + u16 nr_link_ide, nr_ide_mem, nr_streams; + u16 ide_cap; + u32 val; + + if (!pci_is_pcie(pdev)) + return; + + ide_cap = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_IDE); + if (!ide_cap) + return; + + pci_read_config_dword(pdev, ide_cap + PCI_IDE_CAP, &val); + if ((val & PCI_IDE_CAP_SELECTIVE) == 0) + return; + + /* + * Require endpoint IDE capability to be paired with IDE Root Port IDE + * capability. + */ + if (pci_pcie_type(pdev) == PCI_EXP_TYPE_ENDPOINT) { + struct pci_dev *rp = pcie_find_root_port(pdev); + + if (!rp->ide_cap) + return; + } + + pdev->ide_cfg = FIELD_GET(PCI_IDE_CAP_SEL_CFG, val); + pdev->ide_tee_limit = FIELD_GET(PCI_IDE_CAP_TEE_LIMITED, val); + + if (val & PCI_IDE_CAP_LINK) + nr_link_ide = 1 + FIELD_GET(PCI_IDE_CAP_LINK_TC_NUM, val); + else + nr_link_ide = 0; + + nr_ide_mem = 0; + nr_streams = 1 + FIELD_GET(PCI_IDE_CAP_SEL_NUM, val); + for (u16 i = 0; i < nr_streams; i++) { + int pos = __sel_ide_offset(ide_cap, nr_link_ide, i, nr_ide_mem); + int nr_assoc; + u32 val; + + pci_read_config_dword(pdev, pos + PCI_IDE_SEL_CAP, &val); + + /* + * Let's not entertain streams that do not have a constant + * number of address association blocks + */ + nr_assoc = FIELD_GET(PCI_IDE_SEL_CAP_ASSOC_NUM, val); + if (i && (nr_assoc != nr_ide_mem)) { + pci_info(pdev, "Unsupported Selective Stream %d capability, SKIP the rest\n", i); + nr_streams = i; + break; + } + + nr_ide_mem = nr_assoc; + } + + pdev->ide_cap = ide_cap; + pdev->nr_link_ide = nr_link_ide; + pdev->nr_ide_mem = nr_ide_mem; +} diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 4492b809094b..86ef13e7cece 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -613,6 +613,12 @@ static inline void pci_doe_sysfs_init(struct pci_dev *pdev) { } static inline void pci_doe_sysfs_teardown(struct pci_dev *pdev) { } #endif +#ifdef CONFIG_PCI_IDE +void pci_ide_init(struct pci_dev *dev); +#else +static inline void pci_ide_init(struct pci_dev *dev) { } +#endif + /** * pci_dev_set_io_state - Set the new error state if possible. * diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 0ce98e18b5a8..4c55020f3ddf 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -2667,6 +2667,7 @@ static void pci_init_capabilities(struct pci_dev *dev) pci_doe_init(dev); /* Data Object Exchange */ pci_tph_init(dev); /* TLP Processing Hints */ pci_rebar_init(dev); /* Resizable BAR */ + pci_ide_init(dev); /* Link Integrity and Data Encryption */ pcie_report_downtraining(dev); pci_init_reset_methods(dev); diff --git a/include/linux/pci.h b/include/linux/pci.h index d1fdf81fbe1e..4402ca931124 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -539,6 +539,13 @@ struct pci_dev { #endif #ifdef CONFIG_PCI_NPEM struct npem *npem; /* Native PCIe Enclosure Management */ +#endif +#ifdef CONFIG_PCI_IDE + u16 ide_cap; /* Link Integrity & Data Encryption */ + u8 nr_ide_mem; /* Address association resources for streams */ + u8 nr_link_ide; /* Link Stream count (Selective Stream offset) */ + unsigned int ide_cfg:1; /* Config cycles over IDE */ + unsigned int ide_tee_limit:1; /* Disallow T=0 traffic over IDE */ #endif u16 acs_cap; /* ACS Capability offset */ u8 supported_speeds; /* Supported Link Speeds Vector */ diff --git a/include/uapi/linux/pci_regs.h b/include/uapi/linux/pci_regs.h index 07e06aafec50..05bd22d9e352 100644 --- a/include/uapi/linux/pci_regs.h +++ b/include/uapi/linux/pci_regs.h @@ -754,6 +754,7 @@ #define PCI_EXT_CAP_ID_NPEM 0x29 /* Native PCIe Enclosure Management */ #define PCI_EXT_CAP_ID_PL_32GT 0x2A /* Physical Layer 32.0 GT/s */ #define PCI_EXT_CAP_ID_DOE 0x2E /* Data Object Exchange */ +#define PCI_EXT_CAP_ID_IDE 0x30 /* Integrity and Data Encryption */ #define PCI_EXT_CAP_ID_PL_64GT 0x31 /* Physical Layer 64.0 GT/s */ #define PCI_EXT_CAP_ID_MAX PCI_EXT_CAP_ID_PL_64GT @@ -1249,4 +1250,84 @@ #define PCI_DVSEC_CXL_PORT_CTL 0x0c #define PCI_DVSEC_CXL_PORT_CTL_UNMASK_SBR 0x00000001 +/* Integrity and Data Encryption Extended Capability */ +#define PCI_IDE_CAP 0x04 +#define PCI_IDE_CAP_LINK 0x1 /* Link IDE Stream Supported */ +#define PCI_IDE_CAP_SELECTIVE 0x2 /* Selective IDE Streams Supported */ +#define PCI_IDE_CAP_FLOWTHROUGH 0x4 /* Flow-Through IDE Stream Supported */ +#define PCI_IDE_CAP_PARTIAL_HEADER_ENC 0x8 /* Partial Header Encryption Supported */ +#define PCI_IDE_CAP_AGGREGATION 0x10 /* Aggregation Supported */ +#define PCI_IDE_CAP_PCRC 0x20 /* PCRC Supported */ +#define PCI_IDE_CAP_IDE_KM 0x40 /* IDE_KM Protocol Supported */ +#define PCI_IDE_CAP_SEL_CFG 0x80 /* Selective IDE for Config Request Support */ +#define PCI_IDE_CAP_ALG __GENMASK(12, 8) /* Supported Algorithms */ +#define PCI_IDE_CAP_ALG_AES_GCM_256 0 /* AES-GCM 256 key size, 96b MAC */ +#define PCI_IDE_CAP_LINK_TC_NUM __GENMASK(15, 13) /* Link IDE TCs */ +#define PCI_IDE_CAP_SEL_NUM __GENMASK(23, 16) /* Supported Selective IDE Streams */ +#define PCI_IDE_CAP_TEE_LIMITED 0x1000000 /* TEE-Limited Stream Supported */ +#define PCI_IDE_CTL 0x08 +#define PCI_IDE_CTL_FLOWTHROUGH_IDE 0x4 /* Flow-Through IDE Stream Enabled */ + +#define PCI_IDE_LINK_STREAM_0 0xc /* First Link Stream Register Block */ +#define PCI_IDE_LINK_BLOCK_SIZE 8 +/* Link IDE Stream block, up to PCI_IDE_CAP_LINK_TC_NUM */ +#define PCI_IDE_LINK_CTL_0 0x00 /* First Link Control Register Offset in block */ +#define PCI_IDE_LINK_CTL_EN 0x1 /* Link IDE Stream Enable */ +#define PCI_IDE_LINK_CTL_TX_AGGR_NPR __GENMASK(3, 2) /* Tx Aggregation Mode NPR */ +#define PCI_IDE_LINK_CTL_TX_AGGR_PR __GENMASK(5, 4) /* Tx Aggregation Mode PR */ +#define PCI_IDE_LINK_CTL_TX_AGGR_CPL __GENMASK(7, 6) /* Tx Aggregation Mode CPL */ +#define PCI_IDE_LINK_CTL_PCRC_EN 0x100 /* PCRC Enable */ +#define PCI_IDE_LINK_CTL_PART_ENC __GENMASK(13, 10) /* Partial Header Encryption Mode */ +#define PCI_IDE_LINK_CTL_ALG __GENMASK(18, 14) /* Selection from PCI_IDE_CAP_ALG */ +#define PCI_IDE_LINK_CTL_TC __GENMASK(21, 19) /* Traffic Class */ +#define PCI_IDE_LINK_CTL_ID __GENMASK(31, 24) /* Stream ID */ +#define PCI_IDE_LINK_STS_0 0x4 /* First Link Status Register Offset in block */ +#define PCI_IDE_LINK_STS_STATE __GENMASK(3, 0) /* Link IDE Stream State */ +#define PCI_IDE_LINK_STS_IDE_FAIL 0x80000000 /* IDE fail message received */ + +/* Selective IDE Stream block, up to PCI_IDE_CAP_SELECTIVE_STREAMS_NUM */ +/* Selective IDE Stream Capability Register */ +#define PCI_IDE_SEL_CAP 0x00 +#define PCI_IDE_SEL_CAP_ASSOC_NUM __GENMASK(3, 0) +/* Selective IDE Stream Control Register */ +#define PCI_IDE_SEL_CTL 0x04 +#define PCI_IDE_SEL_CTL_EN 0x1 /* Selective IDE Stream Enable */ +#define PCI_IDE_SEL_CTL_TX_AGGR_NPR __GENMASK(3, 2) /* Tx Aggregation Mode NPR */ +#define PCI_IDE_SEL_CTL_TX_AGGR_PR __GENMASK(5, 4) /* Tx Aggregation Mode PR */ +#define PCI_IDE_SEL_CTL_TX_AGGR_CPL __GENMASK(7, 6) /* Tx Aggregation Mode CPL */ +#define PCI_IDE_SEL_CTL_PCRC_EN 0x100 /* PCRC Enable */ +#define PCI_IDE_SEL_CTL_CFG_EN 0x200 /* Selective IDE for Configuration Requests */ +#define PCI_IDE_SEL_CTL_PART_ENC __GENMASK(13, 10) /* Partial Header Encryption Mode */ +#define PCI_IDE_SEL_CTL_ALG __GENMASK(18, 14) /* Selection from PCI_IDE_CAP_ALG */ +#define PCI_IDE_SEL_CTL_TC __GENMASK(21, 19) /* Traffic Class */ +#define PCI_IDE_SEL_CTL_DEFAULT 0x400000 /* Default Stream */ +#define PCI_IDE_SEL_CTL_TEE_LIMITED 0x800000 /* TEE-Limited Stream */ +#define PCI_IDE_SEL_CTL_ID __GENMASK(31, 24) /* Stream ID */ +#define PCI_IDE_SEL_CTL_ID_MAX 255 +/* Selective IDE Stream Status Register */ +#define PCI_IDE_SEL_STS 0x08 +#define PCI_IDE_SEL_STS_STATE __GENMASK(3, 0) /* Selective IDE Stream State */ +#define PCI_IDE_SEL_STS_STATE_INSECURE 0 +#define PCI_IDE_SEL_STS_STATE_SECURE 2 +#define PCI_IDE_SEL_STS_IDE_FAIL 0x80000000 /* IDE fail message received */ +/* IDE RID Association Register 1 */ +#define PCI_IDE_SEL_RID_1 0x0c +#define PCI_IDE_SEL_RID_1_LIMIT __GENMASK(23, 8) +/* IDE RID Association Register 2 */ +#define PCI_IDE_SEL_RID_2 0x10 +#define PCI_IDE_SEL_RID_2_VALID 0x1 +#define PCI_IDE_SEL_RID_2_BASE __GENMASK(23, 8) +#define PCI_IDE_SEL_RID_2_SEG __GENMASK(31, 24) +/* Selective IDE Address Association Register Block, up to PCI_IDE_SEL_CAP_ASSOC_NUM */ +#define PCI_IDE_SEL_ADDR_BLOCK_SIZE 12 +#define PCI_IDE_SEL_ADDR_1(x) (20 + (x) * PCI_IDE_SEL_ADDR_BLOCK_SIZE) +#define PCI_IDE_SEL_ADDR_1_VALID 0x1 +#define PCI_IDE_SEL_ADDR_1_BASE_LOW __GENMASK(19, 8) +#define PCI_IDE_SEL_ADDR_1_LIMIT_LOW __GENMASK(31, 20) +/* IDE Address Association Register 2 is "Memory Limit Upper" */ +#define PCI_IDE_SEL_ADDR_2(x) (24 + (x) * PCI_IDE_SEL_ADDR_BLOCK_SIZE) +/* IDE Address Association Register 3 is "Memory Base Upper" */ +#define PCI_IDE_SEL_ADDR_3(x) (28 + (x) * PCI_IDE_SEL_ADDR_BLOCK_SIZE) +#define PCI_IDE_SEL_BLOCK_SIZE(nr_assoc) (20 + PCI_IDE_SEL_ADDR_BLOCK_SIZE * (nr_assoc)) + #endif /* LINUX_PCI_REGS_H */ -- cgit v1.2.3 From 3225f52cde56f46789a4972d3c54df8a4d75f022 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Fri, 31 Oct 2025 14:28:56 -0700 Subject: PCI/TSM: Establish Secure Sessions and Link Encryption The PCIe 7.0 specification, section 11, defines the Trusted Execution Environment (TEE) Device Interface Security Protocol (TDISP). This protocol definition builds upon Component Measurement and Authentication (CMA), and link Integrity and Data Encryption (IDE). It adds support for assigning devices (PCI physical or virtual function) to a confidential VM such that the assigned device is enabled to access guest private memory protected by technologies like Intel TDX, AMD SEV-SNP, RISCV COVE, or ARM CCA. The "TSM" (TEE Security Manager) is a concept in the TDISP specification of an agent that mediates between a "DSM" (Device Security Manager) and system software in both a VMM and a confidential VM. A VMM uses TSM ABIs to setup link security and assign devices. A confidential VM uses TSM ABIs to transition an assigned device into the TDISP "RUN" state and validate its configuration. From a Linux perspective the TSM abstracts many of the details of TDISP, IDE, and CMA. Some of those details leak through at times, but for the most part TDISP is an internal implementation detail of the TSM. CONFIG_PCI_TSM adds an "authenticated" attribute and "tsm/" subdirectory to pci-sysfs. Consider that the TSM driver may itself be a PCI driver. Userspace can watch for the arrival of a "TSM" device, /sys/class/tsm/tsm0/uevent KOBJ_CHANGE, to know when the PCI core has initialized TSM services. The operations that can be executed against a PCI device are split into two mutually exclusive operation sets, "Link" and "Security" (struct pci_tsm_{link,security}_ops). The "Link" operations manage physical link security properties and communication with the device's Device Security Manager firmware. These are the host side operations in TDISP. The "Security" operations coordinate the security state of the assigned virtual device (TDI). These are the guest side operations in TDISP. Only "link" (Secure Session and physical Link Encryption) operations are defined at this stage. There are placeholders for the device security (Trusted Computing Base entry / exit) operations. The locking allows for multiple devices to be executing commands simultaneously, one outstanding command per-device and an rwsem synchronizes the implementation relative to TSM registration/unregistration events. Thanks to Wu Hao for his work on an early draft of this support. Cc: Lukas Wunner Cc: Samuel Ortiz Acked-by: Bjorn Helgaas Reviewed-by: Jonathan Cameron Reviewed-by: Alexey Kardashevskiy Co-developed-by: Xu Yilun Signed-off-by: Xu Yilun Link: https://patch.msgid.link/20251031212902.2256310-5-dan.j.williams@intel.com Signed-off-by: Dan Williams --- Documentation/ABI/testing/sysfs-bus-pci | 51 +++ Documentation/driver-api/pci/index.rst | 1 + Documentation/driver-api/pci/tsm.rst | 21 ++ MAINTAINERS | 4 +- drivers/pci/Kconfig | 15 + drivers/pci/Makefile | 1 + drivers/pci/doe.c | 2 - drivers/pci/pci-sysfs.c | 4 + drivers/pci/pci.h | 10 + drivers/pci/probe.c | 3 + drivers/pci/remove.c | 6 + drivers/pci/tsm.c | 643 ++++++++++++++++++++++++++++++++ drivers/virt/coco/tsm-core.c | 46 ++- include/linux/pci-doe.h | 4 + include/linux/pci-tsm.h | 157 ++++++++ include/linux/pci.h | 3 + include/linux/tsm.h | 5 +- include/uapi/linux/pci_regs.h | 1 + 18 files changed, 971 insertions(+), 6 deletions(-) create mode 100644 Documentation/driver-api/pci/tsm.rst create mode 100644 drivers/pci/tsm.c create mode 100644 include/linux/pci-tsm.h (limited to 'include/uapi/linux') diff --git a/Documentation/ABI/testing/sysfs-bus-pci b/Documentation/ABI/testing/sysfs-bus-pci index 92debe879ffb..6ffe02f854d6 100644 --- a/Documentation/ABI/testing/sysfs-bus-pci +++ b/Documentation/ABI/testing/sysfs-bus-pci @@ -621,3 +621,54 @@ Description: number extended capability. The file is read only and due to the possible sensitivity of accessible serial numbers, admin only. + +What: /sys/bus/pci/devices/.../tsm/ +Contact: linux-coco@lists.linux.dev +Description: + This directory only appears if a physical device function + supports authentication (PCIe CMA-SPDM), interface security + (PCIe TDISP), and is accepted for secure operation by the + platform TSM driver. This attribute directory appears + dynamically after the platform TSM driver loads. So, only after + the /sys/class/tsm/tsm0 device arrives can tools assume that + devices without a tsm/ attribute directory will never have one; + before that, the security capabilities of the device relative to + the platform TSM are unknown. See + Documentation/ABI/testing/sysfs-class-tsm. + +What: /sys/bus/pci/devices/.../tsm/connect +Contact: linux-coco@lists.linux.dev +Description: + (RW) Write the name of a TSM (TEE Security Manager) device from + /sys/class/tsm to this file to establish a connection with the + device. This typically includes an SPDM (DMTF Security + Protocols and Data Models) session over PCIe DOE (Data Object + Exchange) and may also include PCIe IDE (Integrity and Data + Encryption) establishment. Reads from this attribute return the + name of the connected TSM or the empty string if not + connected. A TSM device signals its readiness to accept PCI + connection via a KOBJ_CHANGE event. + +What: /sys/bus/pci/devices/.../tsm/disconnect +Contact: linux-coco@lists.linux.dev +Description: + (WO) Write the name of the TSM device that was specified + to 'connect' to teardown the connection. + +What: /sys/bus/pci/devices/.../authenticated +Contact: linux-pci@vger.kernel.org +Description: + When the device's tsm/ directory is present device + authentication (PCIe CMA-SPDM) and link encryption (PCIe IDE) + are handled by the platform TSM (TEE Security Manager). When the + tsm/ directory is not present this attribute reflects only the + native CMA-SPDM authentication state with the kernel's + certificate store. + + If the attribute is not present, it indicates that + authentication is unsupported by the device, or the TSM has no + available authentication methods for the device. + + When present and the tsm/ attribute directory is present, the + authenticated attribute is an alias for the device 'connect' + state. See the 'tsm/connect' attribute for more details. diff --git a/Documentation/driver-api/pci/index.rst b/Documentation/driver-api/pci/index.rst index a38e475cdbe3..9e1b801d0f74 100644 --- a/Documentation/driver-api/pci/index.rst +++ b/Documentation/driver-api/pci/index.rst @@ -10,6 +10,7 @@ The Linux PCI driver implementer's API guide pci p2pdma + tsm .. only:: subproject and html diff --git a/Documentation/driver-api/pci/tsm.rst b/Documentation/driver-api/pci/tsm.rst new file mode 100644 index 000000000000..232b92bec93f --- /dev/null +++ b/Documentation/driver-api/pci/tsm.rst @@ -0,0 +1,21 @@ +.. SPDX-License-Identifier: GPL-2.0 +.. include:: + +======================================================== +PCI Trusted Execution Environment Security Manager (TSM) +======================================================== + +Subsystem Interfaces +==================== + +.. kernel-doc:: include/linux/pci-ide.h + :internal: + +.. kernel-doc:: drivers/pci/ide.c + :export: + +.. kernel-doc:: include/linux/pci-tsm.h + :internal: + +.. kernel-doc:: drivers/pci/tsm.c + :export: diff --git a/MAINTAINERS b/MAINTAINERS index b8c9929532ed..f1c8793bf03e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -26118,8 +26118,10 @@ L: linux-coco@lists.linux.dev S: Maintained F: Documentation/ABI/testing/configfs-tsm-report F: Documentation/driver-api/coco/ +F: Documentation/driver-api/pci/tsm.rst +F: drivers/pci/tsm.c F: drivers/virt/coco/guest/ -F: include/linux/tsm*.h +F: include/linux/*tsm*.h F: samples/tsm-mr/ TRUSTED SERVICES TEE DRIVER diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig index b28423e2057f..00b0210e1f1d 100644 --- a/drivers/pci/Kconfig +++ b/drivers/pci/Kconfig @@ -125,6 +125,21 @@ config PCI_ATS config PCI_IDE bool +config PCI_TSM + bool "PCI TSM: Device security protocol support" + select PCI_IDE + select PCI_DOE + select TSM + help + The TEE (Trusted Execution Environment) Device Interface + Security Protocol (TDISP) defines a "TSM" as a platform agent + that manages device authentication, link encryption, link + integrity protection, and assignment of PCI device functions + (virtual or physical) to confidential computing VMs that can + access (DMA) guest private memory. + + Enable a platform TSM driver to use this capability. + config PCI_DOE bool "Enable PCI Data Object Exchange (DOE) support" help diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile index 6612256fd37d..2c545f877062 100644 --- a/drivers/pci/Makefile +++ b/drivers/pci/Makefile @@ -35,6 +35,7 @@ obj-$(CONFIG_XEN_PCIDEV_FRONTEND) += xen-pcifront.o obj-$(CONFIG_VGA_ARB) += vgaarb.o obj-$(CONFIG_PCI_DOE) += doe.o obj-$(CONFIG_PCI_IDE) += ide.o +obj-$(CONFIG_PCI_TSM) += tsm.o obj-$(CONFIG_PCI_DYNAMIC_OF_NODES) += of_property.o obj-$(CONFIG_PCI_NPEM) += npem.o obj-$(CONFIG_PCIE_TPH) += tph.o diff --git a/drivers/pci/doe.c b/drivers/pci/doe.c index aae9a8a00406..62be9c8dbc52 100644 --- a/drivers/pci/doe.c +++ b/drivers/pci/doe.c @@ -24,8 +24,6 @@ #include "pci.h" -#define PCI_DOE_FEATURE_DISCOVERY 0 - /* Timeout of 1 second from 6.30.2 Operation, PCI Spec r6.0 */ #define PCI_DOE_TIMEOUT HZ #define PCI_DOE_POLL_INTERVAL (PCI_DOE_TIMEOUT / 128) diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index 9d6f74bd95f8..7f9237a926c2 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -1868,6 +1868,10 @@ const struct attribute_group *pci_dev_attr_groups[] = { #endif #ifdef CONFIG_PCI_DOE &pci_doe_sysfs_group, +#endif +#ifdef CONFIG_PCI_TSM + &pci_tsm_auth_attr_group, + &pci_tsm_attr_group, #endif NULL, }; diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index 86ef13e7cece..6e4cc1c9aa58 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -619,6 +619,16 @@ void pci_ide_init(struct pci_dev *dev); static inline void pci_ide_init(struct pci_dev *dev) { } #endif +#ifdef CONFIG_PCI_TSM +void pci_tsm_init(struct pci_dev *pdev); +void pci_tsm_destroy(struct pci_dev *pdev); +extern const struct attribute_group pci_tsm_attr_group; +extern const struct attribute_group pci_tsm_auth_attr_group; +#else +static inline void pci_tsm_init(struct pci_dev *pdev) { } +static inline void pci_tsm_destroy(struct pci_dev *pdev) { } +#endif + /** * pci_dev_set_io_state - Set the new error state if possible. * diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 4c55020f3ddf..d1467348c169 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -2763,6 +2763,9 @@ void pci_device_add(struct pci_dev *dev, struct pci_bus *bus) ret = device_add(&dev->dev); WARN_ON(ret < 0); + /* Establish pdev->tsm for newly added (e.g. new SR-IOV VFs) */ + pci_tsm_init(dev); + pci_npem_create(dev); pci_doe_sysfs_init(dev); diff --git a/drivers/pci/remove.c b/drivers/pci/remove.c index ce5c25adef55..803391892c4a 100644 --- a/drivers/pci/remove.c +++ b/drivers/pci/remove.c @@ -57,6 +57,12 @@ static void pci_destroy_dev(struct pci_dev *dev) pci_doe_sysfs_teardown(dev); pci_npem_remove(dev); + /* + * While device is in D0 drop the device from TSM link operations + * including unbind and disconnect (IDE + SPDM teardown). + */ + pci_tsm_destroy(dev); + device_del(&dev->dev); down_write(&pci_bus_sem); diff --git a/drivers/pci/tsm.c b/drivers/pci/tsm.c new file mode 100644 index 000000000000..6a2849f77adc --- /dev/null +++ b/drivers/pci/tsm.c @@ -0,0 +1,643 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Interface with platform TEE Security Manager (TSM) objects as defined by + * PCIe r7.0 section 11 TEE Device Interface Security Protocol (TDISP) + * + * Copyright(c) 2024-2025 Intel Corporation. All rights reserved. + */ + +#define dev_fmt(fmt) "PCI/TSM: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include "pci.h" + +/* + * Provide a read/write lock against the init / exit of pdev tsm + * capabilities and arrival/departure of a TSM instance + */ +static DECLARE_RWSEM(pci_tsm_rwsem); + +/* + * Count of TSMs registered that support physical link operations vs device + * security state management. + */ +static int pci_tsm_link_count; +static int pci_tsm_devsec_count; + +static const struct pci_tsm_ops *to_pci_tsm_ops(struct pci_tsm *tsm) +{ + return tsm->tsm_dev->pci_ops; +} + +static inline bool is_dsm(struct pci_dev *pdev) +{ + return pdev->tsm && pdev->tsm->dsm_dev == pdev; +} + +static inline bool has_tee(struct pci_dev *pdev) +{ + return pdev->devcap & PCI_EXP_DEVCAP_TEE; +} + +/* 'struct pci_tsm_pf0' wraps 'struct pci_tsm' when ->dsm_dev == ->pdev (self) */ +static struct pci_tsm_pf0 *to_pci_tsm_pf0(struct pci_tsm *tsm) +{ + /* + * All "link" TSM contexts reference the device that hosts the DSM + * interface for a set of devices. Walk to the DSM device and cast its + * ->tsm context to a 'struct pci_tsm_pf0 *'. + */ + struct pci_dev *pf0 = tsm->dsm_dev; + + if (!is_pci_tsm_pf0(pf0) || !is_dsm(pf0)) { + pci_WARN_ONCE(tsm->pdev, 1, "invalid context object\n"); + return NULL; + } + + return container_of(pf0->tsm, struct pci_tsm_pf0, base_tsm); +} + +static void tsm_remove(struct pci_tsm *tsm) +{ + struct pci_dev *pdev; + + if (!tsm) + return; + + pdev = tsm->pdev; + to_pci_tsm_ops(tsm)->remove(tsm); + pdev->tsm = NULL; +} +DEFINE_FREE(tsm_remove, struct pci_tsm *, if (_T) tsm_remove(_T)) + +static void pci_tsm_walk_fns(struct pci_dev *pdev, + int (*cb)(struct pci_dev *pdev, void *data), + void *data) +{ + /* Walk subordinate physical functions */ + for (int i = 0; i < 8; i++) { + struct pci_dev *pf __free(pci_dev_put) = pci_get_slot( + pdev->bus, PCI_DEVFN(PCI_SLOT(pdev->devfn), i)); + + if (!pf) + continue; + + /* on entry function 0 has already run @cb */ + if (i > 0) + cb(pf, data); + + /* walk virtual functions of each pf */ + for (int j = 0; j < pci_num_vf(pf); j++) { + struct pci_dev *vf __free(pci_dev_put) = + pci_get_domain_bus_and_slot( + pci_domain_nr(pf->bus), + pci_iov_virtfn_bus(pf, j), + pci_iov_virtfn_devfn(pf, j)); + + if (!vf) + continue; + + cb(vf, data); + } + } + + /* + * Walk downstream devices, assumes that an upstream DSM is + * limited to downstream physical functions + */ + if (pci_pcie_type(pdev) == PCI_EXP_TYPE_UPSTREAM && is_dsm(pdev)) + pci_walk_bus(pdev->subordinate, cb, data); +} + +static void pci_tsm_walk_fns_reverse(struct pci_dev *pdev, + int (*cb)(struct pci_dev *pdev, + void *data), + void *data) +{ + /* Reverse walk downstream devices */ + if (pci_pcie_type(pdev) == PCI_EXP_TYPE_UPSTREAM && is_dsm(pdev)) + pci_walk_bus_reverse(pdev->subordinate, cb, data); + + /* Reverse walk subordinate physical functions */ + for (int i = 7; i >= 0; i--) { + struct pci_dev *pf __free(pci_dev_put) = pci_get_slot( + pdev->bus, PCI_DEVFN(PCI_SLOT(pdev->devfn), i)); + + if (!pf) + continue; + + /* reverse walk virtual functions */ + for (int j = pci_num_vf(pf) - 1; j >= 0; j--) { + struct pci_dev *vf __free(pci_dev_put) = + pci_get_domain_bus_and_slot( + pci_domain_nr(pf->bus), + pci_iov_virtfn_bus(pf, j), + pci_iov_virtfn_devfn(pf, j)); + + if (!vf) + continue; + cb(vf, data); + } + + /* on exit, caller will run @cb on function 0 */ + if (i > 0) + cb(pf, data); + } +} + +static int probe_fn(struct pci_dev *pdev, void *dsm) +{ + struct pci_dev *dsm_dev = dsm; + const struct pci_tsm_ops *ops = to_pci_tsm_ops(dsm_dev->tsm); + + pdev->tsm = ops->probe(dsm_dev->tsm->tsm_dev, pdev); + pci_dbg(pdev, "setup TSM context: DSM: %s status: %s\n", + pci_name(dsm_dev), pdev->tsm ? "success" : "failed"); + return 0; +} + +static int pci_tsm_connect(struct pci_dev *pdev, struct tsm_dev *tsm_dev) +{ + int rc; + struct pci_tsm_pf0 *tsm_pf0; + const struct pci_tsm_ops *ops = tsm_dev->pci_ops; + struct pci_tsm *pci_tsm __free(tsm_remove) = ops->probe(tsm_dev, pdev); + + /* connect() mutually exclusive with subfunction pci_tsm_init() */ + lockdep_assert_held_write(&pci_tsm_rwsem); + + if (!pci_tsm) + return -ENXIO; + + pdev->tsm = pci_tsm; + tsm_pf0 = to_pci_tsm_pf0(pdev->tsm); + + /* mutex_intr assumes connect() is always sysfs/user driven */ + ACQUIRE(mutex_intr, lock)(&tsm_pf0->lock); + if ((rc = ACQUIRE_ERR(mutex_intr, &lock))) + return rc; + + rc = ops->connect(pdev); + if (rc) + return rc; + + pdev->tsm = no_free_ptr(pci_tsm); + + /* + * Now that the DSM is established, probe() all the potential + * dependent functions. Failure to probe a function is not fatal + * to connect(), it just disables subsequent security operations + * for that function. + * + * Note this is done unconditionally, without regard to finding + * PCI_EXP_DEVCAP_TEE on the dependent function, for robustness. The DSM + * is the ultimate arbiter of security state relative to a given + * interface id, and if it says it can manage TDISP state of a function, + * let it. + */ + if (has_tee(pdev)) + pci_tsm_walk_fns(pdev, probe_fn, pdev); + return 0; +} + +static ssize_t connect_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct tsm_dev *tsm_dev; + int rc; + + ACQUIRE(rwsem_read_intr, lock)(&pci_tsm_rwsem); + if ((rc = ACQUIRE_ERR(rwsem_read_intr, &lock))) + return rc; + + if (!pdev->tsm) + return sysfs_emit(buf, "\n"); + + tsm_dev = pdev->tsm->tsm_dev; + return sysfs_emit(buf, "%s\n", dev_name(&tsm_dev->dev)); +} + +/* Is @tsm_dev managing physical link / session properties... */ +static bool is_link_tsm(struct tsm_dev *tsm_dev) +{ + return tsm_dev && tsm_dev->pci_ops && tsm_dev->pci_ops->link_ops.probe; +} + +/* ...or is @tsm_dev managing device security state ? */ +static bool is_devsec_tsm(struct tsm_dev *tsm_dev) +{ + return tsm_dev && tsm_dev->pci_ops && tsm_dev->pci_ops->devsec_ops.lock; +} + +static ssize_t connect_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t len) +{ + struct pci_dev *pdev = to_pci_dev(dev); + int rc, id; + + rc = sscanf(buf, "tsm%d\n", &id); + if (rc != 1) + return -EINVAL; + + ACQUIRE(rwsem_write_kill, lock)(&pci_tsm_rwsem); + if ((rc = ACQUIRE_ERR(rwsem_write_kill, &lock))) + return rc; + + if (pdev->tsm) + return -EBUSY; + + struct tsm_dev *tsm_dev __free(put_tsm_dev) = find_tsm_dev(id); + if (!is_link_tsm(tsm_dev)) + return -ENXIO; + + rc = pci_tsm_connect(pdev, tsm_dev); + if (rc) + return rc; + return len; +} +static DEVICE_ATTR_RW(connect); + +static int remove_fn(struct pci_dev *pdev, void *data) +{ + tsm_remove(pdev->tsm); + return 0; +} + +static void __pci_tsm_disconnect(struct pci_dev *pdev) +{ + struct pci_tsm_pf0 *tsm_pf0 = to_pci_tsm_pf0(pdev->tsm); + const struct pci_tsm_ops *ops = to_pci_tsm_ops(pdev->tsm); + + /* disconnect() mutually exclusive with subfunction pci_tsm_init() */ + lockdep_assert_held_write(&pci_tsm_rwsem); + + /* + * disconnect() is uninterruptible as it may be called for device + * teardown + */ + guard(mutex)(&tsm_pf0->lock); + pci_tsm_walk_fns_reverse(pdev, remove_fn, NULL); + ops->disconnect(pdev); +} + +static void pci_tsm_disconnect(struct pci_dev *pdev) +{ + __pci_tsm_disconnect(pdev); + tsm_remove(pdev->tsm); +} + +static ssize_t disconnect_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t len) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct tsm_dev *tsm_dev; + int rc; + + ACQUIRE(rwsem_write_kill, lock)(&pci_tsm_rwsem); + if ((rc = ACQUIRE_ERR(rwsem_write_kill, &lock))) + return rc; + + if (!pdev->tsm) + return -ENXIO; + + tsm_dev = pdev->tsm->tsm_dev; + if (!sysfs_streq(buf, dev_name(&tsm_dev->dev))) + return -EINVAL; + + pci_tsm_disconnect(pdev); + return len; +} +static DEVICE_ATTR_WO(disconnect); + +/* The 'authenticated' attribute is exclusive to the presence of a 'link' TSM */ +static bool pci_tsm_link_group_visible(struct kobject *kobj) +{ + struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj)); + + return pci_tsm_link_count && is_pci_tsm_pf0(pdev); +} +DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(pci_tsm_link); + +/* + * 'link' and 'devsec' TSMs share the same 'tsm/' sysfs group, so the TSM type + * specific attributes need individual visibility checks. + */ +static umode_t pci_tsm_attr_visible(struct kobject *kobj, + struct attribute *attr, int n) +{ + if (pci_tsm_link_group_visible(kobj)) { + if (attr == &dev_attr_connect.attr || + attr == &dev_attr_disconnect.attr) + return attr->mode; + } + + return 0; +} + +static bool pci_tsm_group_visible(struct kobject *kobj) +{ + return pci_tsm_link_group_visible(kobj); +} +DEFINE_SYSFS_GROUP_VISIBLE(pci_tsm); + +static struct attribute *pci_tsm_attrs[] = { + &dev_attr_connect.attr, + &dev_attr_disconnect.attr, + NULL +}; + +const struct attribute_group pci_tsm_attr_group = { + .name = "tsm", + .attrs = pci_tsm_attrs, + .is_visible = SYSFS_GROUP_VISIBLE(pci_tsm), +}; + +static ssize_t authenticated_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + /* + * When the SPDM session established via TSM the 'authenticated' state + * of the device is identical to the connect state. + */ + return connect_show(dev, attr, buf); +} +static DEVICE_ATTR_RO(authenticated); + +static struct attribute *pci_tsm_auth_attrs[] = { + &dev_attr_authenticated.attr, + NULL +}; + +const struct attribute_group pci_tsm_auth_attr_group = { + .attrs = pci_tsm_auth_attrs, + .is_visible = SYSFS_GROUP_VISIBLE(pci_tsm_link), +}; + +/* + * Retrieve physical function0 device whether it has TEE capability or not + */ +static struct pci_dev *pf0_dev_get(struct pci_dev *pdev) +{ + struct pci_dev *pf_dev = pci_physfn(pdev); + + if (PCI_FUNC(pf_dev->devfn) == 0) + return pci_dev_get(pf_dev); + + return pci_get_slot(pf_dev->bus, + pf_dev->devfn - PCI_FUNC(pf_dev->devfn)); +} + +/* + * Find the PCI Device instance that serves as the Device Security Manager (DSM) + * for @pdev. Note that no additional reference is held for the resulting device + * because that resulting object always has a registered lifetime + * greater-than-or-equal to that of the @pdev argument. This is by virtue of + * @pdev being a descendant of, or identical to, the returned DSM device. + */ +static struct pci_dev *find_dsm_dev(struct pci_dev *pdev) +{ + struct device *grandparent; + struct pci_dev *uport; + + if (is_pci_tsm_pf0(pdev)) + return pdev; + + struct pci_dev *pf0 __free(pci_dev_put) = pf0_dev_get(pdev); + if (!pf0) + return NULL; + + if (is_dsm(pf0)) + return pf0; + + /* + * For cases where a switch may be hosting TDISP services on behalf of + * downstream devices, check the first upstream port relative to this + * endpoint. + */ + if (!pdev->dev.parent) + return NULL; + grandparent = pdev->dev.parent->parent; + if (!grandparent) + return NULL; + if (!dev_is_pci(grandparent)) + return NULL; + uport = to_pci_dev(grandparent); + if (!pci_is_pcie(uport) || + pci_pcie_type(uport) != PCI_EXP_TYPE_UPSTREAM) + return NULL; + + if (is_dsm(uport)) + return uport; + return NULL; +} + +/** + * pci_tsm_link_constructor() - base 'struct pci_tsm' initialization for link TSMs + * @pdev: The PCI device + * @tsm: context to initialize + * @tsm_dev: Platform TEE Security Manager, initiator of security operations + */ +int pci_tsm_link_constructor(struct pci_dev *pdev, struct pci_tsm *tsm, + struct tsm_dev *tsm_dev) +{ + if (!is_link_tsm(tsm_dev)) + return -EINVAL; + + tsm->dsm_dev = find_dsm_dev(pdev); + if (!tsm->dsm_dev) { + pci_warn(pdev, "failed to find Device Security Manager\n"); + return -ENXIO; + } + tsm->pdev = pdev; + tsm->tsm_dev = tsm_dev; + + return 0; +} +EXPORT_SYMBOL_GPL(pci_tsm_link_constructor); + +/** + * pci_tsm_pf0_constructor() - common 'struct pci_tsm_pf0' (DSM) initialization + * @pdev: Physical Function 0 PCI device (as indicated by is_pci_tsm_pf0()) + * @tsm: context to initialize + * @tsm_dev: Platform TEE Security Manager, initiator of security operations + */ +int pci_tsm_pf0_constructor(struct pci_dev *pdev, struct pci_tsm_pf0 *tsm, + struct tsm_dev *tsm_dev) +{ + mutex_init(&tsm->lock); + tsm->doe_mb = pci_find_doe_mailbox(pdev, PCI_VENDOR_ID_PCI_SIG, + PCI_DOE_FEATURE_CMA); + if (!tsm->doe_mb) { + pci_warn(pdev, "TSM init failure, no CMA mailbox\n"); + return -ENODEV; + } + + return pci_tsm_link_constructor(pdev, &tsm->base_tsm, tsm_dev); +} +EXPORT_SYMBOL_GPL(pci_tsm_pf0_constructor); + +void pci_tsm_pf0_destructor(struct pci_tsm_pf0 *pf0_tsm) +{ + mutex_destroy(&pf0_tsm->lock); +} +EXPORT_SYMBOL_GPL(pci_tsm_pf0_destructor); + +static void pf0_sysfs_enable(struct pci_dev *pdev) +{ + bool tee = has_tee(pdev); + + pci_dbg(pdev, "Device Security Manager detected (%s%s%s)\n", + pdev->ide_cap ? "IDE" : "", pdev->ide_cap && tee ? " " : "", + tee ? "TEE" : ""); + + sysfs_update_group(&pdev->dev.kobj, &pci_tsm_auth_attr_group); + sysfs_update_group(&pdev->dev.kobj, &pci_tsm_attr_group); +} + +int pci_tsm_register(struct tsm_dev *tsm_dev) +{ + struct pci_dev *pdev = NULL; + + if (!tsm_dev) + return -EINVAL; + + /* The TSM device must only implement one of link_ops or devsec_ops */ + if (!is_link_tsm(tsm_dev) && !is_devsec_tsm(tsm_dev)) + return -EINVAL; + + if (is_link_tsm(tsm_dev) && is_devsec_tsm(tsm_dev)) + return -EINVAL; + + guard(rwsem_write)(&pci_tsm_rwsem); + + /* On first enable, update sysfs groups */ + if (is_link_tsm(tsm_dev) && pci_tsm_link_count++ == 0) { + for_each_pci_dev(pdev) + if (is_pci_tsm_pf0(pdev)) + pf0_sysfs_enable(pdev); + } else if (is_devsec_tsm(tsm_dev)) { + pci_tsm_devsec_count++; + } + + return 0; +} + +static void pci_tsm_fn_exit(struct pci_dev *pdev) +{ + /* TODO: unbind the fn */ + tsm_remove(pdev->tsm); +} + +/** + * __pci_tsm_destroy() - destroy the TSM context for @pdev + * @pdev: device to cleanup + * @tsm_dev: the TSM device being removed, or NULL if @pdev is being removed. + * + * At device removal or TSM unregistration all established context + * with the TSM is torn down. Additionally, if there are no more TSMs + * registered, the PCI tsm/ sysfs attributes are hidden. + */ +static void __pci_tsm_destroy(struct pci_dev *pdev, struct tsm_dev *tsm_dev) +{ + struct pci_tsm *tsm = pdev->tsm; + + lockdep_assert_held_write(&pci_tsm_rwsem); + + /* + * First, handle the TSM removal case to shutdown @pdev sysfs, this is + * skipped if the device itself is being removed since sysfs goes away + * naturally at that point + */ + if (is_link_tsm(tsm_dev) && is_pci_tsm_pf0(pdev) && !pci_tsm_link_count) { + sysfs_update_group(&pdev->dev.kobj, &pci_tsm_auth_attr_group); + sysfs_update_group(&pdev->dev.kobj, &pci_tsm_attr_group); + } + + /* Nothing else to do if this device never attached to the departing TSM */ + if (!tsm) + return; + + /* Now lookup the tsm_dev to destroy TSM context */ + if (!tsm_dev) + tsm_dev = tsm->tsm_dev; + else if (tsm_dev != tsm->tsm_dev) + return; + + if (is_link_tsm(tsm_dev) && is_pci_tsm_pf0(pdev)) + pci_tsm_disconnect(pdev); + else + pci_tsm_fn_exit(pdev); +} + +void pci_tsm_destroy(struct pci_dev *pdev) +{ + guard(rwsem_write)(&pci_tsm_rwsem); + __pci_tsm_destroy(pdev, NULL); +} + +void pci_tsm_init(struct pci_dev *pdev) +{ + guard(rwsem_read)(&pci_tsm_rwsem); + + /* + * Subfunctions are either probed synchronous with connect() or later + * when either the SR-IOV configuration is changed, or, unlikely, + * connect() raced initial bus scanning. + */ + if (pdev->tsm) + return; + + if (pci_tsm_link_count) { + struct pci_dev *dsm = find_dsm_dev(pdev); + + if (!dsm) + return; + + /* + * The only path to init a Device Security Manager capable + * device is via connect(). + */ + if (!dsm->tsm) + return; + + probe_fn(pdev, dsm); + } +} + +void pci_tsm_unregister(struct tsm_dev *tsm_dev) +{ + struct pci_dev *pdev = NULL; + + guard(rwsem_write)(&pci_tsm_rwsem); + if (is_link_tsm(tsm_dev)) + pci_tsm_link_count--; + if (is_devsec_tsm(tsm_dev)) + pci_tsm_devsec_count--; + for_each_pci_dev_reverse(pdev) + __pci_tsm_destroy(pdev, tsm_dev); +} + +int pci_tsm_doe_transfer(struct pci_dev *pdev, u8 type, const void *req, + size_t req_sz, void *resp, size_t resp_sz) +{ + struct pci_tsm_pf0 *tsm; + + if (!pdev->tsm || !is_pci_tsm_pf0(pdev)) + return -ENXIO; + + tsm = to_pci_tsm_pf0(pdev->tsm); + if (!tsm->doe_mb) + return -ENXIO; + + return pci_doe(tsm->doe_mb, PCI_VENDOR_ID_PCI_SIG, type, req, req_sz, + resp, resp_sz); +} +EXPORT_SYMBOL_GPL(pci_tsm_doe_transfer); diff --git a/drivers/virt/coco/tsm-core.c b/drivers/virt/coco/tsm-core.c index 347507cc5e3f..0e705f3067a1 100644 --- a/drivers/virt/coco/tsm-core.c +++ b/drivers/virt/coco/tsm-core.c @@ -8,11 +8,29 @@ #include #include #include +#include static struct class *tsm_class; static DECLARE_RWSEM(tsm_rwsem); static DEFINE_IDA(tsm_ida); +static int match_id(struct device *dev, const void *data) +{ + struct tsm_dev *tsm_dev = container_of(dev, struct tsm_dev, dev); + int id = *(const int *)data; + + return tsm_dev->id == id; +} + +struct tsm_dev *find_tsm_dev(int id) +{ + struct device *dev = class_find_device(tsm_class, NULL, &id, match_id); + + if (!dev) + return NULL; + return container_of(dev, struct tsm_dev, dev); +} + static struct tsm_dev *alloc_tsm_dev(struct device *parent) { struct device *dev; @@ -36,7 +54,29 @@ static struct tsm_dev *alloc_tsm_dev(struct device *parent) return no_free_ptr(tsm_dev); } -struct tsm_dev *tsm_register(struct device *parent) +static struct tsm_dev *tsm_register_pci_or_reset(struct tsm_dev *tsm_dev, + struct pci_tsm_ops *pci_ops) +{ + int rc; + + if (!pci_ops) + return tsm_dev; + + tsm_dev->pci_ops = pci_ops; + rc = pci_tsm_register(tsm_dev); + if (rc) { + dev_err(tsm_dev->dev.parent, + "PCI/TSM registration failure: %d\n", rc); + device_unregister(&tsm_dev->dev); + return ERR_PTR(rc); + } + + /* Notify TSM userspace that PCI/TSM operations are now possible */ + kobject_uevent(&tsm_dev->dev.kobj, KOBJ_CHANGE); + return tsm_dev; +} + +struct tsm_dev *tsm_register(struct device *parent, struct pci_tsm_ops *pci_ops) { struct tsm_dev *tsm_dev __free(put_tsm_dev) = alloc_tsm_dev(parent); struct device *dev; @@ -54,12 +94,14 @@ struct tsm_dev *tsm_register(struct device *parent) if (rc) return ERR_PTR(rc); - return no_free_ptr(tsm_dev); + return tsm_register_pci_or_reset(no_free_ptr(tsm_dev), pci_ops); } EXPORT_SYMBOL_GPL(tsm_register); void tsm_unregister(struct tsm_dev *tsm_dev) { + if (tsm_dev->pci_ops) + pci_tsm_unregister(tsm_dev); device_unregister(&tsm_dev->dev); } EXPORT_SYMBOL_GPL(tsm_unregister); diff --git a/include/linux/pci-doe.h b/include/linux/pci-doe.h index 1f14aed4354b..bd4346a7c4e7 100644 --- a/include/linux/pci-doe.h +++ b/include/linux/pci-doe.h @@ -15,6 +15,10 @@ struct pci_doe_mb; +#define PCI_DOE_FEATURE_DISCOVERY 0 +#define PCI_DOE_FEATURE_CMA 1 +#define PCI_DOE_FEATURE_SSESSION 2 + struct pci_doe_mb *pci_find_doe_mailbox(struct pci_dev *pdev, u16 vendor, u8 type); diff --git a/include/linux/pci-tsm.h b/include/linux/pci-tsm.h new file mode 100644 index 000000000000..e921d30f9b6c --- /dev/null +++ b/include/linux/pci-tsm.h @@ -0,0 +1,157 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __PCI_TSM_H +#define __PCI_TSM_H +#include +#include + +struct pci_tsm; +struct tsm_dev; + +/* + * struct pci_tsm_ops - manage confidential links and security state + * @link_ops: Coordinate PCIe SPDM and IDE establishment via a platform TSM. + * Provide a secure session transport for TDISP state management + * (typically bare metal physical function operations). + * @devsec_ops: Lock, unlock, and interrogate the security state of the + * function via the platform TSM (typically virtual function + * operations). + * + * This operations are mutually exclusive either a tsm_dev instance + * manages physical link properties or it manages function security + * states like TDISP lock/unlock. + */ +struct pci_tsm_ops { + /* + * struct pci_tsm_link_ops - Manage physical link and the TSM/DSM session + * @probe: establish context with the TSM (allocate / wrap 'struct + * pci_tsm') for follow-on link operations + * @remove: destroy link operations context + * @connect: establish / validate a secure connection (e.g. IDE) + * with the device + * @disconnect: teardown the secure link + * + * Context: @probe, @remove, @connect, and @disconnect run under + * pci_tsm_rwsem held for write to sync with TSM unregistration and + * mutual exclusion of @connect and @disconnect. @connect and + * @disconnect additionally run under the DSM lock (struct + * pci_tsm_pf0::lock) as well as @probe and @remove of the subfunctions. + */ + struct_group_tagged(pci_tsm_link_ops, link_ops, + struct pci_tsm *(*probe)(struct tsm_dev *tsm_dev, + struct pci_dev *pdev); + void (*remove)(struct pci_tsm *tsm); + int (*connect)(struct pci_dev *pdev); + void (*disconnect)(struct pci_dev *pdev); + ); + + /* + * struct pci_tsm_devsec_ops - Manage the security state of the function + * @lock: establish context with the TSM (allocate / wrap 'struct + * pci_tsm') for follow-on security state transitions from the + * LOCKED state + * @unlock: destroy TSM context and return device to UNLOCKED state + * + * Context: @lock and @unlock run under pci_tsm_rwsem held for write to + * sync with TSM unregistration and each other + */ + struct_group_tagged(pci_tsm_devsec_ops, devsec_ops, + struct pci_tsm *(*lock)(struct tsm_dev *tsm_dev, + struct pci_dev *pdev); + void (*unlock)(struct pci_tsm *tsm); + ); +}; + +/** + * struct pci_tsm - Core TSM context for a given PCIe endpoint + * @pdev: Back ref to device function, distinguishes type of pci_tsm context + * @dsm_dev: PCI Device Security Manager for link operations on @pdev + * @tsm_dev: PCI TEE Security Manager device for Link Confidentiality or Device + * Function Security operations + * + * This structure is wrapped by low level TSM driver data and returned by + * probe()/lock(), it is freed by the corresponding remove()/unlock(). + * + * For link operations it serves to cache the association between a Device + * Security Manager (DSM) and the functions that manager can assign to a TVM. + * That can be "self", for assigning function0 of a TEE I/O device, a + * sub-function (SR-IOV virtual function, or non-function0 + * multifunction-device), or a downstream endpoint (PCIe upstream switch-port as + * DSM). + */ +struct pci_tsm { + struct pci_dev *pdev; + struct pci_dev *dsm_dev; + struct tsm_dev *tsm_dev; +}; + +/** + * struct pci_tsm_pf0 - Physical Function 0 TDISP link context + * @base_tsm: generic core "tsm" context + * @lock: mutual exclustion for pci_tsm_ops invocation + * @doe_mb: PCIe Data Object Exchange mailbox + */ +struct pci_tsm_pf0 { + struct pci_tsm base_tsm; + struct mutex lock; + struct pci_doe_mb *doe_mb; +}; + +/* physical function0 and capable of 'connect' */ +static inline bool is_pci_tsm_pf0(struct pci_dev *pdev) +{ + if (!pdev) + return false; + + if (!pci_is_pcie(pdev)) + return false; + + if (pdev->is_virtfn) + return false; + + /* + * Allow for a Device Security Manager (DSM) associated with function0 + * of an Endpoint to coordinate TDISP requests for other functions + * (physical or virtual) of the device, or allow for an Upstream Port + * DSM to accept TDISP requests for the Endpoints downstream of the + * switch. + */ + switch (pci_pcie_type(pdev)) { + case PCI_EXP_TYPE_ENDPOINT: + case PCI_EXP_TYPE_UPSTREAM: + case PCI_EXP_TYPE_RC_END: + if (pdev->ide_cap || (pdev->devcap & PCI_EXP_DEVCAP_TEE)) + break; + fallthrough; + default: + return false; + } + + return PCI_FUNC(pdev->devfn) == 0; +} + +#ifdef CONFIG_PCI_TSM +int pci_tsm_register(struct tsm_dev *tsm_dev); +void pci_tsm_unregister(struct tsm_dev *tsm_dev); +int pci_tsm_link_constructor(struct pci_dev *pdev, struct pci_tsm *tsm, + struct tsm_dev *tsm_dev); +int pci_tsm_pf0_constructor(struct pci_dev *pdev, struct pci_tsm_pf0 *tsm, + struct tsm_dev *tsm_dev); +void pci_tsm_pf0_destructor(struct pci_tsm_pf0 *tsm); +int pci_tsm_doe_transfer(struct pci_dev *pdev, u8 type, const void *req, + size_t req_sz, void *resp, size_t resp_sz); +#else +static inline int pci_tsm_register(struct tsm_dev *tsm_dev) +{ + return 0; +} +static inline void pci_tsm_unregister(struct tsm_dev *tsm_dev) +{ +} +static inline int pci_tsm_doe_transfer(struct pci_dev *pdev, u8 type, + const void *req, size_t req_sz, + void *resp, size_t resp_sz) +{ + return -ENXIO; +} +#endif +#endif /*__PCI_TSM_H */ diff --git a/include/linux/pci.h b/include/linux/pci.h index b6a12a82be12..2f9c0cb6a50a 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -546,6 +546,9 @@ struct pci_dev { u8 nr_link_ide; /* Link Stream count (Selective Stream offset) */ unsigned int ide_cfg:1; /* Config cycles over IDE */ unsigned int ide_tee_limit:1; /* Disallow T=0 traffic over IDE */ +#endif +#ifdef CONFIG_PCI_TSM + struct pci_tsm *tsm; /* TSM operation state */ #endif u16 acs_cap; /* ACS Capability offset */ u8 supported_speeds; /* Supported Link Speeds Vector */ diff --git a/include/linux/tsm.h b/include/linux/tsm.h index cd97c63ffa32..22e05b2aac69 100644 --- a/include/linux/tsm.h +++ b/include/linux/tsm.h @@ -108,9 +108,11 @@ struct tsm_report_ops { bool (*report_bin_attr_visible)(int n); }; +struct pci_tsm_ops; struct tsm_dev { struct device dev; int id; + const struct pci_tsm_ops *pci_ops; }; DEFINE_FREE(put_tsm_dev, struct tsm_dev *, @@ -118,6 +120,7 @@ DEFINE_FREE(put_tsm_dev, struct tsm_dev *, int tsm_report_register(const struct tsm_report_ops *ops, void *priv); int tsm_report_unregister(const struct tsm_report_ops *ops); -struct tsm_dev *tsm_register(struct device *parent); +struct tsm_dev *tsm_register(struct device *parent, struct pci_tsm_ops *ops); void tsm_unregister(struct tsm_dev *tsm_dev); +struct tsm_dev *find_tsm_dev(int id); #endif /* __TSM_H */ diff --git a/include/uapi/linux/pci_regs.h b/include/uapi/linux/pci_regs.h index 05bd22d9e352..f2759c1097bc 100644 --- a/include/uapi/linux/pci_regs.h +++ b/include/uapi/linux/pci_regs.h @@ -503,6 +503,7 @@ #define PCI_EXP_DEVCAP_PWR_VAL 0x03fc0000 /* Slot Power Limit Value */ #define PCI_EXP_DEVCAP_PWR_SCL 0x0c000000 /* Slot Power Limit Scale */ #define PCI_EXP_DEVCAP_FLR 0x10000000 /* Function Level Reset */ +#define PCI_EXP_DEVCAP_TEE 0x40000000 /* TEE I/O (TDISP) Support */ #define PCI_EXP_DEVCTL 0x08 /* Device Control */ #define PCI_EXP_DEVCTL_CERE 0x0001 /* Correctable Error Reporting En. */ #define PCI_EXP_DEVCTL_NFERE 0x0002 /* Non-Fatal Error Reporting Enable */ -- cgit v1.2.3 From c0c1262fbfbafe943dbccd5f97b500b72dbd2205 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Fri, 31 Oct 2025 14:28:57 -0700 Subject: PCI: Add PCIe Device 3 Extended Capability enumeration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PCIe r7.0 Section 7.7.9 Device 3 Extended Capability Structure, defines the canonical location for determining the Flit Mode of a device. This status is a dependency for PCIe IDE enabling. Add a new fm_enabled flag to 'struct pci_dev'. Cc: Lukas Wunner Cc: Ilpo Järvinen Cc: Bjorn Helgaas Cc: Samuel Ortiz Cc: Alexey Kardashevskiy Cc: Xu Yilun Acked-by: Bjorn Helgaas Reviewed-by: Jonathan Cameron Link: https://patch.msgid.link/20251031212902.2256310-6-dan.j.williams@intel.com Signed-off-by: Dan Williams --- drivers/pci/probe.c | 12 ++++++++++++ include/linux/pci.h | 1 + include/uapi/linux/pci_regs.h | 7 +++++++ 3 files changed, 20 insertions(+) (limited to 'include/uapi/linux') diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index d1467348c169..3b54f1720be5 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -2283,6 +2283,17 @@ int pci_configure_extended_tags(struct pci_dev *dev, void *ign) return 0; } +static void pci_dev3_init(struct pci_dev *pdev) +{ + u16 cap = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_DEV3); + u32 val = 0; + + if (!cap) + return; + pci_read_config_dword(pdev, cap + PCI_DEV3_STA, &val); + pdev->fm_enabled = !!(val & PCI_DEV3_STA_SEGMENT); +} + /** * pcie_relaxed_ordering_enabled - Probe for PCIe relaxed ordering enable * @dev: PCI device to query @@ -2667,6 +2678,7 @@ static void pci_init_capabilities(struct pci_dev *dev) pci_doe_init(dev); /* Data Object Exchange */ pci_tph_init(dev); /* TLP Processing Hints */ pci_rebar_init(dev); /* Resizable BAR */ + pci_dev3_init(dev); /* Device 3 capabilities */ pci_ide_init(dev); /* Link Integrity and Data Encryption */ pcie_report_downtraining(dev); diff --git a/include/linux/pci.h b/include/linux/pci.h index 2f9c0cb6a50a..ea94799c81b0 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -450,6 +450,7 @@ struct pci_dev { unsigned int pasid_enabled:1; /* Process Address Space ID */ unsigned int pri_enabled:1; /* Page Request Interface */ unsigned int tph_enabled:1; /* TLP Processing Hints */ + unsigned int fm_enabled:1; /* Flit Mode (segment captured) */ unsigned int is_managed:1; /* Managed via devres */ unsigned int is_msi_managed:1; /* MSI release via devres installed */ unsigned int needs_freset:1; /* Requires fundamental reset */ diff --git a/include/uapi/linux/pci_regs.h b/include/uapi/linux/pci_regs.h index f2759c1097bc..3add74ae2594 100644 --- a/include/uapi/linux/pci_regs.h +++ b/include/uapi/linux/pci_regs.h @@ -755,6 +755,7 @@ #define PCI_EXT_CAP_ID_NPEM 0x29 /* Native PCIe Enclosure Management */ #define PCI_EXT_CAP_ID_PL_32GT 0x2A /* Physical Layer 32.0 GT/s */ #define PCI_EXT_CAP_ID_DOE 0x2E /* Data Object Exchange */ +#define PCI_EXT_CAP_ID_DEV3 0x2F /* Device 3 Capability/Control/Status */ #define PCI_EXT_CAP_ID_IDE 0x30 /* Integrity and Data Encryption */ #define PCI_EXT_CAP_ID_PL_64GT 0x31 /* Physical Layer 64.0 GT/s */ #define PCI_EXT_CAP_ID_MAX PCI_EXT_CAP_ID_PL_64GT @@ -1246,6 +1247,12 @@ /* Deprecated old name, replaced with PCI_DOE_DATA_OBJECT_DISC_RSP_3_TYPE */ #define PCI_DOE_DATA_OBJECT_DISC_RSP_3_PROTOCOL PCI_DOE_DATA_OBJECT_DISC_RSP_3_TYPE +/* Device 3 Extended Capability */ +#define PCI_DEV3_CAP 0x04 /* Device 3 Capabilities Register */ +#define PCI_DEV3_CTL 0x08 /* Device 3 Control Register */ +#define PCI_DEV3_STA 0x0c /* Device 3 Status Register */ +#define PCI_DEV3_STA_SEGMENT 0x8 /* Segment Captured (end-to-end flit-mode detected) */ + /* Compute Express Link (CXL r3.1, sec 8.1.5) */ #define PCI_DVSEC_CXL_PORT 3 #define PCI_DVSEC_CXL_PORT_CTL 0x0c -- cgit v1.2.3 From d923739e2e356424cc566143a3323c62cd6ed067 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Mon, 27 Oct 2025 09:44:26 +0100 Subject: rseq: Simplify the event notification Since commit 0190e4198e47 ("rseq: Deprecate RSEQ_CS_FLAG_NO_RESTART_ON_* flags") the bits in task::rseq_event_mask are meaningless and just extra work in terms of setting them individually. Aside of that the only relevant point where an event has to be raised is context switch. Neither the CPU nor MM CID can change without going through a context switch. Collapse them all into a single boolean which simplifies the code a lot and remove the pointless invocations which have been sprinkled all over the place for no value. Signed-off-by: Thomas Gleixner Signed-off-by: Peter Zijlstra (Intel) Signed-off-by: Ingo Molnar Reviewed-by: Mathieu Desnoyers Link: https://patch.msgid.link/20251027084306.336978188@linutronix.de --- fs/exec.c | 2 +- include/linux/rseq.h | 66 ++++++++++------------------------------------- include/linux/sched.h | 10 +++---- include/uapi/linux/rseq.h | 21 +++++---------- kernel/rseq.c | 28 ++++++++++++-------- kernel/sched/core.c | 5 +--- kernel/sched/membarrier.c | 8 +++--- 7 files changed, 48 insertions(+), 92 deletions(-) (limited to 'include/uapi/linux') diff --git a/fs/exec.c b/fs/exec.c index 4298e7e08d5d..e45b29890269 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -1775,7 +1775,7 @@ out: force_fatal_sig(SIGSEGV); sched_mm_cid_after_execve(current); - rseq_set_notify_resume(current); + rseq_sched_switch_event(current); current->in_execve = 0; return retval; diff --git a/include/linux/rseq.h b/include/linux/rseq.h index d72ddf7ce903..241067bf20db 100644 --- a/include/linux/rseq.h +++ b/include/linux/rseq.h @@ -3,38 +3,8 @@ #define _LINUX_RSEQ_H #ifdef CONFIG_RSEQ - -#include #include -#ifdef CONFIG_MEMBARRIER -# define RSEQ_EVENT_GUARD irq -#else -# define RSEQ_EVENT_GUARD preempt -#endif - -/* - * Map the event mask on the user-space ABI enum rseq_cs_flags - * for direct mask checks. - */ -enum rseq_event_mask_bits { - RSEQ_EVENT_PREEMPT_BIT = RSEQ_CS_FLAG_NO_RESTART_ON_PREEMPT_BIT, - RSEQ_EVENT_SIGNAL_BIT = RSEQ_CS_FLAG_NO_RESTART_ON_SIGNAL_BIT, - RSEQ_EVENT_MIGRATE_BIT = RSEQ_CS_FLAG_NO_RESTART_ON_MIGRATE_BIT, -}; - -enum rseq_event_mask { - RSEQ_EVENT_PREEMPT = (1U << RSEQ_EVENT_PREEMPT_BIT), - RSEQ_EVENT_SIGNAL = (1U << RSEQ_EVENT_SIGNAL_BIT), - RSEQ_EVENT_MIGRATE = (1U << RSEQ_EVENT_MIGRATE_BIT), -}; - -static inline void rseq_set_notify_resume(struct task_struct *t) -{ - if (t->rseq) - set_tsk_thread_flag(t, TIF_NOTIFY_RESUME); -} - void __rseq_handle_notify_resume(struct ksignal *sig, struct pt_regs *regs); static inline void rseq_handle_notify_resume(struct pt_regs *regs) @@ -43,35 +13,27 @@ static inline void rseq_handle_notify_resume(struct pt_regs *regs) __rseq_handle_notify_resume(NULL, regs); } -static inline void rseq_signal_deliver(struct ksignal *ksig, - struct pt_regs *regs) +static inline void rseq_signal_deliver(struct ksignal *ksig, struct pt_regs *regs) { if (current->rseq) { - scoped_guard(RSEQ_EVENT_GUARD) - __set_bit(RSEQ_EVENT_SIGNAL_BIT, ¤t->rseq_event_mask); + current->rseq_event_pending = true; __rseq_handle_notify_resume(ksig, regs); } } -/* rseq_preempt() requires preemption to be disabled. */ -static inline void rseq_preempt(struct task_struct *t) +static inline void rseq_sched_switch_event(struct task_struct *t) { - __set_bit(RSEQ_EVENT_PREEMPT_BIT, &t->rseq_event_mask); - rseq_set_notify_resume(t); -} - -/* rseq_migrate() requires preemption to be disabled. */ -static inline void rseq_migrate(struct task_struct *t) -{ - __set_bit(RSEQ_EVENT_MIGRATE_BIT, &t->rseq_event_mask); - rseq_set_notify_resume(t); + if (t->rseq) { + t->rseq_event_pending = true; + set_tsk_thread_flag(t, TIF_NOTIFY_RESUME); + } } static __always_inline void rseq_exit_to_user_mode(void) { if (IS_ENABLED(CONFIG_DEBUG_RSEQ)) { - if (WARN_ON_ONCE(current->rseq && current->rseq_event_mask)) - current->rseq_event_mask = 0; + if (WARN_ON_ONCE(current->rseq && current->rseq_event_pending)) + current->rseq_event_pending = false; } } @@ -85,12 +47,12 @@ static inline void rseq_fork(struct task_struct *t, u64 clone_flags) t->rseq = NULL; t->rseq_len = 0; t->rseq_sig = 0; - t->rseq_event_mask = 0; + t->rseq_event_pending = false; } else { t->rseq = current->rseq; t->rseq_len = current->rseq_len; t->rseq_sig = current->rseq_sig; - t->rseq_event_mask = current->rseq_event_mask; + t->rseq_event_pending = current->rseq_event_pending; } } @@ -99,15 +61,13 @@ static inline void rseq_execve(struct task_struct *t) t->rseq = NULL; t->rseq_len = 0; t->rseq_sig = 0; - t->rseq_event_mask = 0; + t->rseq_event_pending = false; } #else /* CONFIG_RSEQ */ -static inline void rseq_set_notify_resume(struct task_struct *t) { } static inline void rseq_handle_notify_resume(struct pt_regs *regs) { } static inline void rseq_signal_deliver(struct ksignal *ksig, struct pt_regs *regs) { } -static inline void rseq_preempt(struct task_struct *t) { } -static inline void rseq_migrate(struct task_struct *t) { } +static inline void rseq_sched_switch_event(struct task_struct *t) { } static inline void rseq_fork(struct task_struct *t, u64 clone_flags) { } static inline void rseq_execve(struct task_struct *t) { } static inline void rseq_exit_to_user_mode(void) { } diff --git a/include/linux/sched.h b/include/linux/sched.h index b469878de25c..6627c527c2c7 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -1407,14 +1407,14 @@ struct task_struct { #endif /* CONFIG_NUMA_BALANCING */ #ifdef CONFIG_RSEQ - struct rseq __user *rseq; - u32 rseq_len; - u32 rseq_sig; + struct rseq __user *rseq; + u32 rseq_len; + u32 rseq_sig; /* - * RmW on rseq_event_mask must be performed atomically + * RmW on rseq_event_pending must be performed atomically * with respect to preemption. */ - unsigned long rseq_event_mask; + bool rseq_event_pending; # ifdef CONFIG_DEBUG_RSEQ /* * This is a place holder to save a copy of the rseq fields for diff --git a/include/uapi/linux/rseq.h b/include/uapi/linux/rseq.h index c233aae5eac9..1b76d508400c 100644 --- a/include/uapi/linux/rseq.h +++ b/include/uapi/linux/rseq.h @@ -114,20 +114,13 @@ struct rseq { /* * Restartable sequences flags field. * - * This field should only be updated by the thread which - * registered this data structure. Read by the kernel. - * Mainly used for single-stepping through rseq critical sections - * with debuggers. - * - * - RSEQ_CS_FLAG_NO_RESTART_ON_PREEMPT - * Inhibit instruction sequence block restart on preemption - * for this thread. - * - RSEQ_CS_FLAG_NO_RESTART_ON_SIGNAL - * Inhibit instruction sequence block restart on signal - * delivery for this thread. - * - RSEQ_CS_FLAG_NO_RESTART_ON_MIGRATE - * Inhibit instruction sequence block restart on migration for - * this thread. + * This field was initially intended to allow event masking for + * single-stepping through rseq critical sections with debuggers. + * The kernel does not support this anymore and the relevant bits + * are checked for being always false: + * - RSEQ_CS_FLAG_NO_RESTART_ON_PREEMPT + * - RSEQ_CS_FLAG_NO_RESTART_ON_SIGNAL + * - RSEQ_CS_FLAG_NO_RESTART_ON_MIGRATE */ __u32 flags; diff --git a/kernel/rseq.c b/kernel/rseq.c index 80af48a972f0..59adc1a7183b 100644 --- a/kernel/rseq.c +++ b/kernel/rseq.c @@ -78,6 +78,12 @@ #define CREATE_TRACE_POINTS #include +#ifdef CONFIG_MEMBARRIER +# define RSEQ_EVENT_GUARD irq +#else +# define RSEQ_EVENT_GUARD preempt +#endif + /* The original rseq structure size (including padding) is 32 bytes. */ #define ORIG_RSEQ_SIZE 32 @@ -430,11 +436,11 @@ void __rseq_handle_notify_resume(struct ksignal *ksig, struct pt_regs *regs) */ if (regs) { /* - * Read and clear the event mask first. If the task was not - * preempted or migrated or a signal is on the way, there - * is no point in doing any of the heavy lifting here on - * production kernels. In that case TIF_NOTIFY_RESUME was - * raised by some other functionality. + * Read and clear the event pending bit first. If the task + * was not preempted or migrated or a signal is on the way, + * there is no point in doing any of the heavy lifting here + * on production kernels. In that case TIF_NOTIFY_RESUME + * was raised by some other functionality. * * This is correct because the read/clear operation is * guarded against scheduler preemption, which makes it CPU @@ -447,15 +453,15 @@ void __rseq_handle_notify_resume(struct ksignal *ksig, struct pt_regs *regs) * with the result handed in to allow the detection of * inconsistencies. */ - u32 event_mask; + bool event; scoped_guard(RSEQ_EVENT_GUARD) { - event_mask = t->rseq_event_mask; - t->rseq_event_mask = 0; + event = t->rseq_event_pending; + t->rseq_event_pending = false; } - if (IS_ENABLED(CONFIG_DEBUG_RSEQ) || event_mask) { - ret = rseq_ip_fixup(regs, !!event_mask); + if (IS_ENABLED(CONFIG_DEBUG_RSEQ) || event) { + ret = rseq_ip_fixup(regs, event); if (unlikely(ret < 0)) goto error; } @@ -584,7 +590,7 @@ SYSCALL_DEFINE4(rseq, struct rseq __user *, rseq, u32, rseq_len, int, flags, u32 * registered, ensure the cpu_id_start and cpu_id fields * are updated before returning to user-space. */ - rseq_set_notify_resume(current); + rseq_sched_switch_event(current); return 0; } diff --git a/kernel/sched/core.c b/kernel/sched/core.c index f1ebf67b48e2..b75e8e1eca4a 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -3329,7 +3329,6 @@ void set_task_cpu(struct task_struct *p, unsigned int new_cpu) if (p->sched_class->migrate_task_rq) p->sched_class->migrate_task_rq(p, new_cpu); p->se.nr_migrations++; - rseq_migrate(p); sched_mm_cid_migrate_from(p); perf_event_task_migrate(p); } @@ -4763,7 +4762,6 @@ int sched_cgroup_fork(struct task_struct *p, struct kernel_clone_args *kargs) p->sched_task_group = tg; } #endif - rseq_migrate(p); /* * We're setting the CPU for the first time, we don't migrate, * so use __set_task_cpu(). @@ -4827,7 +4825,6 @@ void wake_up_new_task(struct task_struct *p) * as we're not fully set-up yet. */ p->recent_used_cpu = task_cpu(p); - rseq_migrate(p); __set_task_cpu(p, select_task_rq(p, task_cpu(p), &wake_flags)); rq = __task_rq_lock(p, &rf); update_rq_clock(rq); @@ -5121,7 +5118,7 @@ prepare_task_switch(struct rq *rq, struct task_struct *prev, kcov_prepare_switch(prev); sched_info_switch(rq, prev, next); perf_event_task_sched_out(prev, next); - rseq_preempt(prev); + rseq_sched_switch_event(prev); fire_sched_out_preempt_notifiers(prev, next); kmap_local_sched_out(); prepare_task(next); diff --git a/kernel/sched/membarrier.c b/kernel/sched/membarrier.c index 62fba83b7bb1..623445603725 100644 --- a/kernel/sched/membarrier.c +++ b/kernel/sched/membarrier.c @@ -199,7 +199,7 @@ static void ipi_rseq(void *info) * is negligible. */ smp_mb(); - rseq_preempt(current); + rseq_sched_switch_event(current); } static void ipi_sync_rq_state(void *info) @@ -407,9 +407,9 @@ static int membarrier_private_expedited(int flags, int cpu_id) * membarrier, we will end up with some thread in the mm * running without a core sync. * - * For RSEQ, don't rseq_preempt() the caller. User code - * is not supposed to issue syscalls at all from inside an - * rseq critical section. + * For RSEQ, don't invoke rseq_sched_switch_event() on the + * caller. User code is not supposed to issue syscalls at + * all from inside an rseq critical section. */ if (flags != MEMBARRIER_FLAG_SYNC_CORE) { preempt_disable(); -- cgit v1.2.3 From f88191c7f3618405f1fc5c331a94ebfe601c5b08 Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Sat, 1 Nov 2025 18:56:51 +0100 Subject: mptcp: pm: in-kernel: record fullmesh endp nb Instead of iterating over all endpoints, under RCU read lock, just to check if one of them as the fullmesh flag, we can keep a counter of fullmesh endpoint, similar to what is done with the other flags. This counter is now checked, before iterating over all endpoints. Similar to the other counters, this new one is also exposed. A userspace app can then know when it is being used in a fullmesh mode, with potentially (too) many subflows. Reviewed-by: Geliang Tang Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20251101-net-next-mptcp-fm-endp-nb-bind-v1-1-b4166772d6bb@kernel.org Signed-off-by: Jakub Kicinski --- include/uapi/linux/mptcp.h | 3 ++- net/mptcp/pm_kernel.c | 38 +++++++++++++++++++++++++++++++++++--- net/mptcp/protocol.h | 1 + net/mptcp/sockopt.c | 2 ++ 4 files changed, 40 insertions(+), 4 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/mptcp.h b/include/uapi/linux/mptcp.h index 87cfab874e24..04eea6d1d0a9 100644 --- a/include/uapi/linux/mptcp.h +++ b/include/uapi/linux/mptcp.h @@ -70,7 +70,8 @@ struct mptcp_info { __u64 mptcpi_bytes_acked; __u8 mptcpi_subflows_total; __u8 mptcpi_endp_laminar_max; - __u8 reserved[2]; + __u8 mptcpi_endp_fullmesh_max; + __u8 reserved; __u32 mptcpi_last_data_sent; __u32 mptcpi_last_data_recv; __u32 mptcpi_last_ack_recv; diff --git a/net/mptcp/pm_kernel.c b/net/mptcp/pm_kernel.c index 2ae95476dba3..e2918c68ff02 100644 --- a/net/mptcp/pm_kernel.c +++ b/net/mptcp/pm_kernel.c @@ -22,6 +22,7 @@ struct pm_nl_pernet { u8 endp_signal_max; u8 endp_subflow_max; u8 endp_laminar_max; + u8 endp_fullmesh_max; u8 limit_add_addr_accepted; u8 limit_extra_subflows; u8 next_id; @@ -70,6 +71,14 @@ u8 mptcp_pm_get_endp_laminar_max(const struct mptcp_sock *msk) } EXPORT_SYMBOL_GPL(mptcp_pm_get_endp_laminar_max); +u8 mptcp_pm_get_endp_fullmesh_max(const struct mptcp_sock *msk) +{ + struct pm_nl_pernet *pernet = pm_nl_get_pernet_from_msk(msk); + + return READ_ONCE(pernet->endp_fullmesh_max); +} +EXPORT_SYMBOL_GPL(mptcp_pm_get_endp_fullmesh_max); + u8 mptcp_pm_get_limit_add_addr_accepted(const struct mptcp_sock *msk) { struct pm_nl_pernet *pernet = pm_nl_get_pernet_from_msk(msk); @@ -603,9 +612,12 @@ fill_local_addresses_vec(struct mptcp_sock *msk, struct mptcp_addr_info *remote, int i; /* If there is at least one MPTCP endpoint with a fullmesh flag */ - i = fill_local_addresses_vec_fullmesh(msk, remote, locals, c_flag_case); - if (i) - return i; + if (mptcp_pm_get_endp_fullmesh_max(msk)) { + i = fill_local_addresses_vec_fullmesh(msk, remote, locals, + c_flag_case); + if (i) + return i; + } /* If there is at least one MPTCP endpoint with a laminar flag */ if (mptcp_pm_get_endp_laminar_max(msk)) @@ -790,6 +802,10 @@ find_next: addr_max = pernet->endp_laminar_max; WRITE_ONCE(pernet->endp_laminar_max, addr_max + 1); } + if (entry->flags & MPTCP_PM_ADDR_FLAG_FULLMESH) { + addr_max = pernet->endp_fullmesh_max; + WRITE_ONCE(pernet->endp_fullmesh_max, addr_max + 1); + } pernet->endpoints++; if (!entry->addr.port) @@ -1187,6 +1203,10 @@ int mptcp_pm_nl_del_addr_doit(struct sk_buff *skb, struct genl_info *info) addr_max = pernet->endp_laminar_max; WRITE_ONCE(pernet->endp_laminar_max, addr_max - 1); } + if (entry->flags & MPTCP_PM_ADDR_FLAG_FULLMESH) { + addr_max = pernet->endp_fullmesh_max; + WRITE_ONCE(pernet->endp_fullmesh_max, addr_max - 1); + } pernet->endpoints--; list_del_rcu(&entry->list); @@ -1502,6 +1522,18 @@ int mptcp_pm_nl_set_flags(struct mptcp_pm_addr_entry *local, changed = (local->flags ^ entry->flags) & mask; entry->flags = (entry->flags & ~mask) | (local->flags & mask); *local = *entry; + + if (changed & MPTCP_PM_ADDR_FLAG_FULLMESH) { + u8 addr_max = pernet->endp_fullmesh_max; + + if (entry->flags & MPTCP_PM_ADDR_FLAG_FULLMESH) + addr_max++; + else + addr_max--; + + WRITE_ONCE(pernet->endp_fullmesh_max, addr_max); + } + spin_unlock_bh(&pernet->lock); mptcp_pm_nl_set_flags_all(net, local, changed); diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h index 379a88e14e8d..9a3429175758 100644 --- a/net/mptcp/protocol.h +++ b/net/mptcp/protocol.h @@ -1183,6 +1183,7 @@ void __mptcp_pm_kernel_worker(struct mptcp_sock *msk); u8 mptcp_pm_get_endp_signal_max(const struct mptcp_sock *msk); u8 mptcp_pm_get_endp_subflow_max(const struct mptcp_sock *msk); u8 mptcp_pm_get_endp_laminar_max(const struct mptcp_sock *msk); +u8 mptcp_pm_get_endp_fullmesh_max(const struct mptcp_sock *msk); u8 mptcp_pm_get_limit_add_addr_accepted(const struct mptcp_sock *msk); u8 mptcp_pm_get_limit_extra_subflows(const struct mptcp_sock *msk); diff --git a/net/mptcp/sockopt.c b/net/mptcp/sockopt.c index a28a48385885..de90a2897d2d 100644 --- a/net/mptcp/sockopt.c +++ b/net/mptcp/sockopt.c @@ -982,6 +982,8 @@ void mptcp_diag_fill_info(struct mptcp_sock *msk, struct mptcp_info *info) mptcp_pm_get_endp_subflow_max(msk); info->mptcpi_endp_laminar_max = mptcp_pm_get_endp_laminar_max(msk); + info->mptcpi_endp_fullmesh_max = + mptcp_pm_get_endp_fullmesh_max(msk); } if (__mptcp_check_fallback(msk)) -- cgit v1.2.3 From 0bf0e2e4666822b62d7ad6473dc37fd6b377b5f1 Mon Sep 17 00:00:00 2001 From: Damien Le Moal Date: Wed, 5 Nov 2025 06:22:41 +0900 Subject: block: track zone conditions The function blk_revalidate_zone_cond() already caches the condition of all zones of a zoned block device in the zones_cond array of a gendisk. However, the zone conditions are updated only when the device is scanned or revalidated. Implement tracking of the runtime changes to zone conditions using the new cond field in struct blk_zone_wplug. The size of this structure remains 112 Bytes as the new field replaces the 4 Bytes padding at the end of the structure. Beause zones that do not have a zone write plug can be in the empty, implicit open, explicit open or full condition, the zones_cond array of a disk is used to track the conditions, of zones that do not have a zone write plug. The condition of such zone is updated in the disk zones_cond array when a zone reset, reset all or finish operation is executed, and also when a zone write plug is removed from the disk hash table when the zone becomes full. Since a device may automatically close an implicitly open zone when writing to an empty or closed zone, if the total number of open zones has reached the device limit, the BLK_ZONE_COND_IMP_OPEN and BLK_ZONE_COND_CLOSED zone conditions cannot be precisely tracked. To overcome this, the zone condition BLK_ZONE_COND_ACTIVE is introduced to represent a zone that has the condition BLK_ZONE_COND_IMP_OPEN, BLK_ZONE_COND_EXP_OPEN or BLK_ZONE_COND_CLOSED. This follows the definition of an active zone as defined in the NVMe Zoned Namespace specifications. As such, for a zoned device that has a limit on the maximum number of open zones, we will never have more zones in the BLK_ZONE_COND_ACTIVE condition than the device limit. This is compatible with the SCSI ZBC and ATA ZAC specifications for SMR HDDs as these devices do not have a limit on the number of active zones. The function disk_zone_wplug_set_wp_offset() is modified to use the new helper disk_zone_wplug_update_cond() to update a zone write plug condition whenever a zone write plug write offset is updated on submission or merging of write BIOs to a zone. The functions blk_zone_reset_bio_endio(), blk_zone_reset_all_bio_endio() and blk_zone_finish_bio_endio() are modified to update the condition of the zones targeted by reset, reset_all and finish operations, either using though disk_zone_wplug_set_wp_offset() for zones that have a zone write plug, or using the disk_zone_set_cond() helper to update the zones_cond array of the disk for zones that do not have a zone write plug. When a zone write plug is removed from the disk hash table (when the zone becomes empty or full), the condition of struct blk_zone_wplug is used to update the disk zones_cond array. Conversely, when a zone write plug is added to the disk hash table, the zones_cond array is used to initialize the zone write plug condition. Signed-off-by: Damien Le Moal Reviewed-by: Martin K. Petersen Signed-off-by: Jens Axboe --- block/blk-zoned.c | 120 +++++++++++++++++++++++++++++++++++++++--- include/uapi/linux/blkzoned.h | 11 ++++ 2 files changed, 125 insertions(+), 6 deletions(-) (limited to 'include/uapi/linux') diff --git a/block/blk-zoned.c b/block/blk-zoned.c index f62862274f9a..c5fa303093a9 100644 --- a/block/blk-zoned.c +++ b/block/blk-zoned.c @@ -33,6 +33,7 @@ static const char *const zone_cond_name[] = { ZONE_COND_NAME(READONLY), ZONE_COND_NAME(FULL), ZONE_COND_NAME(OFFLINE), + ZONE_COND_NAME(ACTIVE), }; #undef ZONE_COND_NAME @@ -57,6 +58,7 @@ static const char *const zone_cond_name[] = { * @zone_no: The number of the zone the plug is managing. * @wp_offset: The zone write pointer location relative to the start of the zone * as a number of 512B sectors. + * @cond: Condition of the zone */ struct blk_zone_wplug { struct hlist_node node; @@ -69,6 +71,7 @@ struct blk_zone_wplug { unsigned int flags; unsigned int zone_no; unsigned int wp_offset; + enum blk_zone_cond cond; }; static inline unsigned int disk_zone_wplugs_hash_size(struct gendisk *disk) @@ -114,6 +117,57 @@ const char *blk_zone_cond_str(enum blk_zone_cond zone_cond) } EXPORT_SYMBOL_GPL(blk_zone_cond_str); +static void blk_zone_set_cond(u8 *zones_cond, unsigned int zno, + enum blk_zone_cond cond) +{ + if (!zones_cond) + return; + + switch (cond) { + case BLK_ZONE_COND_IMP_OPEN: + case BLK_ZONE_COND_EXP_OPEN: + case BLK_ZONE_COND_CLOSED: + zones_cond[zno] = BLK_ZONE_COND_ACTIVE; + return; + case BLK_ZONE_COND_NOT_WP: + case BLK_ZONE_COND_EMPTY: + case BLK_ZONE_COND_FULL: + case BLK_ZONE_COND_OFFLINE: + case BLK_ZONE_COND_READONLY: + default: + zones_cond[zno] = cond; + return; + } +} + +static void disk_zone_set_cond(struct gendisk *disk, sector_t sector, + enum blk_zone_cond cond) +{ + u8 *zones_cond; + + rcu_read_lock(); + zones_cond = rcu_dereference(disk->zones_cond); + if (zones_cond) { + unsigned int zno = disk_zone_no(disk, sector); + + /* + * The condition of a conventional, readonly and offline zones + * never changes, so do nothing if the target zone is in one of + * these conditions. + */ + switch (zones_cond[zno]) { + case BLK_ZONE_COND_NOT_WP: + case BLK_ZONE_COND_READONLY: + case BLK_ZONE_COND_OFFLINE: + break; + default: + blk_zone_set_cond(zones_cond, zno, cond); + break; + } + } + rcu_read_unlock(); +} + /** * bdev_zone_is_seq - check if a sector belongs to a sequential write zone * @bdev: block device to check @@ -416,6 +470,7 @@ static bool disk_insert_zone_wplug(struct gendisk *disk, { struct blk_zone_wplug *zwplg; unsigned long flags; + u8 *zones_cond; unsigned int idx = hash_32(zwplug->zone_no, disk->zone_wplugs_hash_bits); @@ -431,6 +486,20 @@ static bool disk_insert_zone_wplug(struct gendisk *disk, return false; } } + + /* + * Set the zone condition: if we do not yet have a zones_cond array + * attached to the disk, then this is a zone write plug insert from the + * first call to blk_revalidate_disk_zones(), in which case the zone is + * necessarilly in the active condition. + */ + zones_cond = rcu_dereference_check(disk->zones_cond, + lockdep_is_held(&disk->zone_wplugs_lock)); + if (zones_cond) + zwplug->cond = zones_cond[zwplug->zone_no]; + else + zwplug->cond = BLK_ZONE_COND_ACTIVE; + hlist_add_head_rcu(&zwplug->node, &disk->zone_wplugs_hash[idx]); atomic_inc(&disk->nr_zone_wplugs); spin_unlock_irqrestore(&disk->zone_wplugs_lock, flags); @@ -530,10 +599,15 @@ static void disk_remove_zone_wplug(struct gendisk *disk, /* * Mark the zone write plug as unhashed and drop the extra reference we - * took when the plug was inserted in the hash table. + * took when the plug was inserted in the hash table. Also update the + * disk zone condition array with the current condition of the zone + * write plug. */ zwplug->flags |= BLK_ZONE_WPLUG_UNHASHED; spin_lock_irqsave(&disk->zone_wplugs_lock, flags); + blk_zone_set_cond(rcu_dereference_check(disk->zones_cond, + lockdep_is_held(&disk->zone_wplugs_lock)), + zwplug->zone_no, zwplug->cond); hlist_del_init_rcu(&zwplug->node); atomic_dec(&disk->nr_zone_wplugs); spin_unlock_irqrestore(&disk->zone_wplugs_lock, flags); @@ -635,6 +709,22 @@ static void disk_zone_wplug_abort(struct blk_zone_wplug *zwplug) blk_zone_wplug_bio_io_error(zwplug, bio); } +/* + * Update a zone write plug condition based on the write pointer offset. + */ +static void disk_zone_wplug_update_cond(struct gendisk *disk, + struct blk_zone_wplug *zwplug) +{ + lockdep_assert_held(&zwplug->lock); + + if (disk_zone_wplug_is_full(disk, zwplug)) + zwplug->cond = BLK_ZONE_COND_FULL; + else if (!zwplug->wp_offset) + zwplug->cond = BLK_ZONE_COND_EMPTY; + else + zwplug->cond = BLK_ZONE_COND_ACTIVE; +} + /* * Set a zone write plug write pointer offset to the specified value. * This aborts all plugged BIOs, which is fine as this function is called for @@ -650,6 +740,8 @@ static void disk_zone_wplug_set_wp_offset(struct gendisk *disk, /* Update the zone write pointer and abort all plugged BIOs. */ zwplug->flags &= ~BLK_ZONE_WPLUG_NEED_WP_UPDATE; zwplug->wp_offset = wp_offset; + disk_zone_wplug_update_cond(disk, zwplug); + disk_zone_wplug_abort(zwplug); /* @@ -733,6 +825,7 @@ EXPORT_SYMBOL_GPL(disk_report_zone); static void blk_zone_reset_bio_endio(struct bio *bio) { struct gendisk *disk = bio->bi_bdev->bd_disk; + sector_t sector = bio->bi_iter.bi_sector; struct blk_zone_wplug *zwplug; /* @@ -741,7 +834,7 @@ static void blk_zone_reset_bio_endio(struct bio *bio) * resetting zones while writes are still in-flight will result in the * writes failing anyway. */ - zwplug = disk_get_zone_wplug(disk, bio->bi_iter.bi_sector); + zwplug = disk_get_zone_wplug(disk, sector); if (zwplug) { unsigned long flags; @@ -749,14 +842,18 @@ static void blk_zone_reset_bio_endio(struct bio *bio) disk_zone_wplug_set_wp_offset(disk, zwplug, 0); spin_unlock_irqrestore(&zwplug->lock, flags); disk_put_zone_wplug(zwplug); + } else { + disk_zone_set_cond(disk, sector, BLK_ZONE_COND_EMPTY); } } static void blk_zone_reset_all_bio_endio(struct bio *bio) { struct gendisk *disk = bio->bi_bdev->bd_disk; + sector_t capacity = get_capacity(disk); struct blk_zone_wplug *zwplug; unsigned long flags; + sector_t sector; unsigned int i; /* Update the condition of all zone write plugs. */ @@ -770,12 +867,18 @@ static void blk_zone_reset_all_bio_endio(struct bio *bio) } } rcu_read_unlock(); + + /* Update the cached zone conditions. */ + for (sector = 0; sector < capacity; + sector += bdev_zone_sectors(bio->bi_bdev)) + disk_zone_set_cond(disk, sector, BLK_ZONE_COND_EMPTY); } static void blk_zone_finish_bio_endio(struct bio *bio) { struct block_device *bdev = bio->bi_bdev; struct gendisk *disk = bdev->bd_disk; + sector_t sector = bio->bi_iter.bi_sector; struct blk_zone_wplug *zwplug; /* @@ -784,7 +887,7 @@ static void blk_zone_finish_bio_endio(struct bio *bio) * is fine as resetting zones while writes are still in-flight will * result in the writes failing anyway. */ - zwplug = disk_get_zone_wplug(disk, bio->bi_iter.bi_sector); + zwplug = disk_get_zone_wplug(disk, sector); if (zwplug) { unsigned long flags; @@ -793,6 +896,8 @@ static void blk_zone_finish_bio_endio(struct bio *bio) bdev_zone_sectors(bdev)); spin_unlock_irqrestore(&zwplug->lock, flags); disk_put_zone_wplug(zwplug); + } else { + disk_zone_set_cond(disk, sector, BLK_ZONE_COND_FULL); } } @@ -888,6 +993,7 @@ static inline void disk_zone_wplug_add_bio(struct gendisk *disk, */ void blk_zone_write_plug_bio_merged(struct bio *bio) { + struct gendisk *disk = bio->bi_bdev->bd_disk; struct blk_zone_wplug *zwplug; unsigned long flags; @@ -909,13 +1015,13 @@ void blk_zone_write_plug_bio_merged(struct bio *bio) * have at least one request and one BIO referencing the zone write * plug. So this should not fail. */ - zwplug = disk_get_zone_wplug(bio->bi_bdev->bd_disk, - bio->bi_iter.bi_sector); + zwplug = disk_get_zone_wplug(disk, bio->bi_iter.bi_sector); if (WARN_ON_ONCE(!zwplug)) return; spin_lock_irqsave(&zwplug->lock, flags); zwplug->wp_offset += bio_sectors(bio); + disk_zone_wplug_update_cond(disk, zwplug); spin_unlock_irqrestore(&zwplug->lock, flags); } @@ -974,6 +1080,7 @@ void blk_zone_write_plug_init_request(struct request *req) /* Drop the reference taken by disk_zone_wplug_add_bio(). */ blk_queue_exit(q); zwplug->wp_offset += bio_sectors(bio); + disk_zone_wplug_update_cond(disk, zwplug); req_back_sector += bio_sectors(bio); } @@ -1037,6 +1144,7 @@ static bool blk_zone_wplug_prepare_bio(struct blk_zone_wplug *zwplug, /* Advance the zone write pointer offset. */ zwplug->wp_offset += bio_sectors(bio); + disk_zone_wplug_update_cond(disk, zwplug); return true; } @@ -1683,7 +1791,7 @@ static int blk_revalidate_zone_cond(struct blk_zone *zone, unsigned int idx, return -ENODEV; } - args->zones_cond[idx] = cond; + blk_zone_set_cond(args->zones_cond, idx, cond); return 0; diff --git a/include/uapi/linux/blkzoned.h b/include/uapi/linux/blkzoned.h index f85743ef6e7d..5c7662971414 100644 --- a/include/uapi/linux/blkzoned.h +++ b/include/uapi/linux/blkzoned.h @@ -48,6 +48,8 @@ enum blk_zone_type { * FINISH ZONE command. * @BLK_ZONE_COND_READONLY: The zone is read-only. * @BLK_ZONE_COND_OFFLINE: The zone is offline (sectors cannot be read/written). + * @BLK_ZONE_COND_ACTIVE: The zone is either implicitly open, explicitly open, + * or closed. * * The Zone Condition state machine in the ZBC/ZAC standards maps the above * deinitions as: @@ -61,6 +63,13 @@ enum blk_zone_type { * * Conditions 0x5 to 0xC are reserved by the current ZBC/ZAC spec and should * be considered invalid. + * + * The condition BLK_ZONE_COND_ACTIVE is used only with cached zone reports. + * It is used to report any of the BLK_ZONE_COND_IMP_OPEN, + * BLK_ZONE_COND_EXP_OPEN and BLK_ZONE_COND_CLOSED conditions. Conversely, a + * regular zone report will never report a zone condition using + * BLK_ZONE_COND_ACTIVE and instead use the conditions BLK_ZONE_COND_IMP_OPEN, + * BLK_ZONE_COND_EXP_OPEN or BLK_ZONE_COND_CLOSED as reported by the device. */ enum blk_zone_cond { BLK_ZONE_COND_NOT_WP = 0x0, @@ -71,6 +80,8 @@ enum blk_zone_cond { BLK_ZONE_COND_READONLY = 0xD, BLK_ZONE_COND_FULL = 0xE, BLK_ZONE_COND_OFFLINE = 0xF, + + BLK_ZONE_COND_ACTIVE = 0xFF, }; /** -- cgit v1.2.3 From b30ffcdc0c15a88f8866529d3532454e02571221 Mon Sep 17 00:00:00 2001 From: Damien Le Moal Date: Wed, 5 Nov 2025 06:22:45 +0900 Subject: block: introduce BLKREPORTZONESV2 ioctl Introduce the new BLKREPORTZONESV2 ioctl command to allow user applications access to the fast zone report implemented by blkdev_report_zones_cached(). This new ioctl is defined as number 142 and is documented in include/uapi/linux/fs.h. Unlike the existing BLKREPORTZONES ioctl, this new ioctl uses the flags field of struct blk_zone_report also as an input. If the user sets the BLK_ZONE_REP_CACHED flag as an input, then blkdev_report_zones_cached() is used to generate the zone report using cached zone information. If this flag is not set, then BLKREPORTZONESV2 behaves in the same manner as BLKREPORTZONES and the zone report is generated by accessing the zoned device. Signed-off-by: Damien Le Moal Reviewed-by: Christoph Hellwig Reviewed-by: Chaitanya Kulkarni Reviewed-by: Martin K. Petersen Signed-off-by: Jens Axboe --- block/blk-zoned.c | 25 ++++++++++++++++++++++--- block/ioctl.c | 1 + include/uapi/linux/blkzoned.h | 35 ++++++++++++++++++++++++++++++----- include/uapi/linux/fs.h | 2 +- 4 files changed, 54 insertions(+), 9 deletions(-) (limited to 'include/uapi/linux') diff --git a/block/blk-zoned.c b/block/blk-zoned.c index bbd105b11843..7a7b0704f095 100644 --- a/block/blk-zoned.c +++ b/block/blk-zoned.c @@ -357,7 +357,12 @@ static int blkdev_copy_zone_to_user(struct blk_zone *zone, unsigned int idx, } /* - * BLKREPORTZONE ioctl processing. + * Mask of valid input flags for BLKREPORTZONEV2 ioctl. + */ +#define BLK_ZONE_REPV2_INPUT_FLAGS BLK_ZONE_REP_CACHED + +/* + * BLKREPORTZONE and BLKREPORTZONEV2 ioctl processing. * Called from blkdev_ioctl. */ int blkdev_report_zones_ioctl(struct block_device *bdev, unsigned int cmd, @@ -381,8 +386,22 @@ int blkdev_report_zones_ioctl(struct block_device *bdev, unsigned int cmd, return -EINVAL; args.zones = argp + sizeof(struct blk_zone_report); - ret = blkdev_report_zones(bdev, rep.sector, rep.nr_zones, - blkdev_copy_zone_to_user, &args); + + switch (cmd) { + case BLKREPORTZONE: + ret = blkdev_report_zones(bdev, rep.sector, rep.nr_zones, + blkdev_copy_zone_to_user, &args); + break; + case BLKREPORTZONEV2: + if (rep.flags & ~BLK_ZONE_REPV2_INPUT_FLAGS) + return -EINVAL; + ret = blkdev_report_zones_cached(bdev, rep.sector, rep.nr_zones, + blkdev_copy_zone_to_user, &args); + break; + default: + return -EINVAL; + } + if (ret < 0) return ret; diff --git a/block/ioctl.c b/block/ioctl.c index 3927ca4707d0..698629e4c619 100644 --- a/block/ioctl.c +++ b/block/ioctl.c @@ -581,6 +581,7 @@ static int blkdev_common_ioctl(struct block_device *bdev, blk_mode_t mode, case BLKGETDISKSEQ: return put_u64(argp, bdev->bd_disk->diskseq); case BLKREPORTZONE: + case BLKREPORTZONEV2: return blkdev_report_zones_ioctl(bdev, cmd, arg); case BLKRESETZONE: case BLKOPENZONE: diff --git a/include/uapi/linux/blkzoned.h b/include/uapi/linux/blkzoned.h index 5c7662971414..e33f02703350 100644 --- a/include/uapi/linux/blkzoned.h +++ b/include/uapi/linux/blkzoned.h @@ -87,10 +87,20 @@ enum blk_zone_cond { /** * enum blk_zone_report_flags - Feature flags of reported zone descriptors. * - * @BLK_ZONE_REP_CAPACITY: Zone descriptor has capacity field. + * @BLK_ZONE_REP_CAPACITY: Output only. Indicates that zone descriptors in a + * zone report have a valid capacity field. + * @BLK_ZONE_REP_CACHED: Input only. Indicates that the zone report should be + * generated using cached zone information. In this case, + * the implicit open, explicit open and closed zone + * conditions are all reported with the + * BLK_ZONE_COND_ACTIVE condition. */ enum blk_zone_report_flags { - BLK_ZONE_REP_CAPACITY = (1 << 0), + /* Output flags */ + BLK_ZONE_REP_CAPACITY = (1U << 0), + + /* Input flags */ + BLK_ZONE_REP_CACHED = (1U << 31), }; /** @@ -133,6 +143,10 @@ struct blk_zone { * @sector: starting sector of report * @nr_zones: IN maximum / OUT actual * @flags: one or more flags as defined by enum blk_zone_report_flags. + * @flags: one or more flags as defined by enum blk_zone_report_flags. + * With BLKREPORTZONE, this field is ignored as an input and is valid + * only as an output. Using BLKREPORTZONEV2, this field is used as both + * input and output. * @zones: Space to hold @nr_zones @zones entries on reply. * * The array of at most @nr_zones must follow this structure in memory. @@ -159,9 +173,19 @@ struct blk_zone_range { /** * Zoned block device ioctl's: * - * @BLKREPORTZONE: Get zone information. Takes a zone report as argument. - * The zone report will start from the zone containing the - * sector specified in the report request structure. + * @BLKREPORTZONE: Get zone information from a zoned device. Takes a zone report + * as argument. The zone report will start from the zone + * containing the sector specified in struct blk_zone_report. + * The flags field of struct blk_zone_report is used as an + * output only and ignored as an input. + * DEPRECATED, use BLKREPORTZONEV2 instead. + * @BLKREPORTZONEV2: Same as @BLKREPORTZONE but uses the flags field of + * struct blk_zone_report as an input, allowing to get a zone + * report using cached zone information if the flag + * BLK_ZONE_REP_CACHED is set. In such case, the zone report + * may include zones with the condition @BLK_ZONE_COND_ACTIVE + * (c.f. the description of this condition above for more + * details). * @BLKRESETZONE: Reset the write pointer of the zones in the specified * sector range. The sector range must be zone aligned. * @BLKGETZONESZ: Get the device zone size in number of 512 B sectors. @@ -180,5 +204,6 @@ struct blk_zone_range { #define BLKOPENZONE _IOW(0x12, 134, struct blk_zone_range) #define BLKCLOSEZONE _IOW(0x12, 135, struct blk_zone_range) #define BLKFINISHZONE _IOW(0x12, 136, struct blk_zone_range) +#define BLKREPORTZONEV2 _IOWR(0x12, 142, struct blk_zone_report) #endif /* _UAPI_BLKZONED_H */ diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h index 957ce3343a4f..66ca526cf786 100644 --- a/include/uapi/linux/fs.h +++ b/include/uapi/linux/fs.h @@ -298,7 +298,7 @@ struct file_attr { #define BLKROTATIONAL _IO(0x12,126) #define BLKZEROOUT _IO(0x12,127) #define BLKGETDISKSEQ _IOR(0x12,128,__u64) -/* 130-136 are used by zoned block device ioctls (uapi/linux/blkzoned.h) */ +/* 130-136 and 142 are used by zoned block device ioctls (uapi/linux/blkzoned.h) */ /* 137-141 are used by blk-crypto ioctls (uapi/linux/blk-crypto.h) */ #define BLKTRACESETUP2 _IOWR(0x12, 142, struct blk_user_trace_setup2) -- cgit v1.2.3 From b4ce5923e780d6896d4aaf19de5a27652b8bf1ea Mon Sep 17 00:00:00 2001 From: Anton Protopopov Date: Wed, 5 Nov 2025 09:03:59 +0000 Subject: bpf, x86: add new map type: instructions array MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On bpf(BPF_PROG_LOAD) syscall user-supplied BPF programs are translated by the verifier into "xlated" BPF programs. During this process the original instructions offsets might be adjusted and/or individual instructions might be replaced by new sets of instructions, or deleted. Add a new BPF map type which is aimed to keep track of how, for a given program, the original instructions were relocated during the verification. Also, besides keeping track of the original -> xlated mapping, make x86 JIT to build the xlated -> jitted mapping for every instruction listed in an instruction array. This is required for every future application of instruction arrays: static keys, indirect jumps and indirect calls. A map of the BPF_MAP_TYPE_INSN_ARRAY type must be created with a u32 keys and value of size 8. The values have different semantics for userspace and for BPF space. For userspace a value consists of two u32 values – xlated and jitted offsets. For BPF side the value is a real pointer to a jitted instruction. On map creation/initialization, before loading the program, each element of the map should be initialized to point to an instruction offset within the program. Before the program load such maps should be made frozen. After the program verification xlated and jitted offsets can be read via the bpf(2) syscall. If a tracked instruction is removed by the verifier, then the xlated offset is set to (u32)-1 which is considered to be too big for a valid BPF program offset. One such a map can, obviously, be used to track one and only one BPF program. If the verification process was unsuccessful, then the same map can be re-used to verify the program with a different log level. However, if the program was loaded fine, then such a map, being frozen in any case, can't be reused by other programs even after the program release. Example. Consider the following original and xlated programs: Original prog: Xlated prog: 0: r1 = 0x0 0: r1 = 0 1: *(u32 *)(r10 - 0x4) = r1 1: *(u32 *)(r10 -4) = r1 2: r2 = r10 2: r2 = r10 3: r2 += -0x4 3: r2 += -4 4: r1 = 0x0 ll 4: r1 = map[id:88] 6: call 0x1 6: r1 += 272 7: r0 = *(u32 *)(r2 +0) 8: if r0 >= 0x1 goto pc+3 9: r0 <<= 3 10: r0 += r1 11: goto pc+1 12: r0 = 0 7: r6 = r0 13: r6 = r0 8: if r6 == 0x0 goto +0x2 14: if r6 == 0x0 goto pc+4 9: call 0x76 15: r0 = 0xffffffff8d2079c0 17: r0 = *(u64 *)(r0 +0) 10: *(u64 *)(r6 + 0x0) = r0 18: *(u64 *)(r6 +0) = r0 11: r0 = 0x0 19: r0 = 0x0 12: exit 20: exit An instruction array map, containing, e.g., instructions [0,4,7,12] will be translated by the verifier to [0,4,13,20]. A map with index 5 (the middle of 16-byte instruction) or indexes greater than 12 (outside the program boundaries) would be rejected. The functionality provided by this patch will be extended in consequent patches to implement BPF Static Keys, indirect jumps, and indirect calls. Signed-off-by: Anton Protopopov Reviewed-by: Eduard Zingerman Link: https://lore.kernel.org/r/20251105090410.1250500-2-a.s.protopopov@gmail.com Signed-off-by: Alexei Starovoitov --- arch/x86/net/bpf_jit_comp.c | 9 ++ include/linux/bpf.h | 15 +++ include/linux/bpf_types.h | 1 + include/linux/bpf_verifier.h | 2 + include/uapi/linux/bpf.h | 21 +++ kernel/bpf/Makefile | 2 +- kernel/bpf/bpf_insn_array.c | 286 +++++++++++++++++++++++++++++++++++++++++ kernel/bpf/syscall.c | 22 ++++ kernel/bpf/verifier.c | 45 +++++++ tools/include/uapi/linux/bpf.h | 21 +++ 10 files changed, 423 insertions(+), 1 deletion(-) create mode 100644 kernel/bpf/bpf_insn_array.c (limited to 'include/uapi/linux') diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c index de5083cb1d37..91f92d65ae83 100644 --- a/arch/x86/net/bpf_jit_comp.c +++ b/arch/x86/net/bpf_jit_comp.c @@ -3827,6 +3827,15 @@ out_image: jit_data->header = header; jit_data->rw_header = rw_header; } + + /* + * The bpf_prog_update_insn_ptrs function expects addrs to + * point to the first byte of the jitted instruction (unlike + * the bpf_prog_fill_jited_linfo below, which, for historical + * reasons, expects to point to the next instruction) + */ + bpf_prog_update_insn_ptrs(prog, addrs, image); + /* * ctx.prog_offset is used when CFI preambles put code *before* * the function. See emit_cfi(). For FineIBT specifically this code diff --git a/include/linux/bpf.h b/include/linux/bpf.h index a47d67db3be5..9d41a6affcef 100644 --- a/include/linux/bpf.h +++ b/include/linux/bpf.h @@ -3797,4 +3797,19 @@ int bpf_prog_get_file_line(struct bpf_prog *prog, unsigned long ip, const char * const char **linep, int *nump); struct bpf_prog *bpf_prog_find_from_stack(void); +int bpf_insn_array_init(struct bpf_map *map, const struct bpf_prog *prog); +int bpf_insn_array_ready(struct bpf_map *map); +void bpf_insn_array_release(struct bpf_map *map); +void bpf_insn_array_adjust(struct bpf_map *map, u32 off, u32 len); +void bpf_insn_array_adjust_after_remove(struct bpf_map *map, u32 off, u32 len); + +#ifdef CONFIG_BPF_SYSCALL +void bpf_prog_update_insn_ptrs(struct bpf_prog *prog, u32 *offsets, void *image); +#else +static inline void +bpf_prog_update_insn_ptrs(struct bpf_prog *prog, u32 *offsets, void *image) +{ +} +#endif + #endif /* _LINUX_BPF_H */ diff --git a/include/linux/bpf_types.h b/include/linux/bpf_types.h index fa78f49d4a9a..b13de31e163f 100644 --- a/include/linux/bpf_types.h +++ b/include/linux/bpf_types.h @@ -133,6 +133,7 @@ BPF_MAP_TYPE(BPF_MAP_TYPE_RINGBUF, ringbuf_map_ops) BPF_MAP_TYPE(BPF_MAP_TYPE_BLOOM_FILTER, bloom_filter_map_ops) BPF_MAP_TYPE(BPF_MAP_TYPE_USER_RINGBUF, user_ringbuf_map_ops) BPF_MAP_TYPE(BPF_MAP_TYPE_ARENA, arena_map_ops) +BPF_MAP_TYPE(BPF_MAP_TYPE_INSN_ARRAY, insn_array_map_ops) BPF_LINK_TYPE(BPF_LINK_TYPE_RAW_TRACEPOINT, raw_tracepoint) BPF_LINK_TYPE(BPF_LINK_TYPE_TRACING, tracing) diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h index c6eb68b6389c..6b820d8d77af 100644 --- a/include/linux/bpf_verifier.h +++ b/include/linux/bpf_verifier.h @@ -754,8 +754,10 @@ struct bpf_verifier_env { struct list_head free_list; /* list of struct bpf_verifier_state_list */ struct bpf_map *used_maps[MAX_USED_MAPS]; /* array of map's used by eBPF program */ struct btf_mod_pair used_btfs[MAX_USED_BTFS]; /* array of BTF's used by BPF program */ + struct bpf_map *insn_array_maps[MAX_USED_MAPS]; /* array of INSN_ARRAY map's to be relocated */ u32 used_map_cnt; /* number of used maps */ u32 used_btf_cnt; /* number of used BTF objects */ + u32 insn_array_map_cnt; /* number of used maps of type BPF_MAP_TYPE_INSN_ARRAY */ u32 id_gen; /* used to generate unique reg IDs */ u32 hidden_subprog_cnt; /* number of hidden subprogs */ int exception_callback_subprog; diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index 1d73f165394d..f5713f59ac10 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -1026,6 +1026,7 @@ enum bpf_map_type { BPF_MAP_TYPE_USER_RINGBUF, BPF_MAP_TYPE_CGRP_STORAGE, BPF_MAP_TYPE_ARENA, + BPF_MAP_TYPE_INSN_ARRAY, __MAX_BPF_MAP_TYPE }; @@ -7649,4 +7650,24 @@ enum bpf_kfunc_flags { BPF_F_PAD_ZEROS = (1ULL << 0), }; +/* + * Values of a BPF_MAP_TYPE_INSN_ARRAY entry must be of this type. + * + * Before the map is used the orig_off field should point to an + * instruction inside the program being loaded. The other fields + * must be set to 0. + * + * After the program is loaded, the xlated_off will be adjusted + * by the verifier to point to the index of the original instruction + * in the xlated program. If the instruction is deleted, it will + * be set to (u32)-1. The jitted_off will be set to the corresponding + * offset in the jitted image of the program. + */ +struct bpf_insn_array_value { + __u32 orig_off; + __u32 xlated_off; + __u32 jitted_off; + __u32 :32; +}; + #endif /* _UAPI__LINUX_BPF_H__ */ diff --git a/kernel/bpf/Makefile b/kernel/bpf/Makefile index 7fd0badfacb1..232cbc97434d 100644 --- a/kernel/bpf/Makefile +++ b/kernel/bpf/Makefile @@ -9,7 +9,7 @@ CFLAGS_core.o += -Wno-override-init $(cflags-nogcse-yy) obj-$(CONFIG_BPF_SYSCALL) += syscall.o verifier.o inode.o helpers.o tnum.o log.o token.o liveness.o obj-$(CONFIG_BPF_SYSCALL) += bpf_iter.o map_iter.o task_iter.o prog_iter.o link_iter.o obj-$(CONFIG_BPF_SYSCALL) += hashtab.o arraymap.o percpu_freelist.o bpf_lru_list.o lpm_trie.o map_in_map.o bloom_filter.o -obj-$(CONFIG_BPF_SYSCALL) += local_storage.o queue_stack_maps.o ringbuf.o +obj-$(CONFIG_BPF_SYSCALL) += local_storage.o queue_stack_maps.o ringbuf.o bpf_insn_array.o obj-$(CONFIG_BPF_SYSCALL) += bpf_local_storage.o bpf_task_storage.o obj-${CONFIG_BPF_LSM} += bpf_inode_storage.o obj-$(CONFIG_BPF_SYSCALL) += disasm.o mprog.o diff --git a/kernel/bpf/bpf_insn_array.c b/kernel/bpf/bpf_insn_array.c new file mode 100644 index 000000000000..2053fda377bb --- /dev/null +++ b/kernel/bpf/bpf_insn_array.c @@ -0,0 +1,286 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2025 Isovalent */ + +#include + +struct bpf_insn_array { + struct bpf_map map; + atomic_t used; + long *ips; + DECLARE_FLEX_ARRAY(struct bpf_insn_array_value, values); +}; + +#define cast_insn_array(MAP_PTR) \ + container_of((MAP_PTR), struct bpf_insn_array, map) + +#define INSN_DELETED ((u32)-1) + +static inline u64 insn_array_alloc_size(u32 max_entries) +{ + const u64 base_size = sizeof(struct bpf_insn_array); + const u64 entry_size = sizeof(struct bpf_insn_array_value); + + return base_size + max_entries * (entry_size + sizeof(long)); +} + +static int insn_array_alloc_check(union bpf_attr *attr) +{ + u32 value_size = sizeof(struct bpf_insn_array_value); + + if (attr->max_entries == 0 || attr->key_size != 4 || + attr->value_size != value_size || attr->map_flags != 0) + return -EINVAL; + + return 0; +} + +static void insn_array_free(struct bpf_map *map) +{ + struct bpf_insn_array *insn_array = cast_insn_array(map); + + bpf_map_area_free(insn_array); +} + +static struct bpf_map *insn_array_alloc(union bpf_attr *attr) +{ + u64 size = insn_array_alloc_size(attr->max_entries); + struct bpf_insn_array *insn_array; + + insn_array = bpf_map_area_alloc(size, NUMA_NO_NODE); + if (!insn_array) + return ERR_PTR(-ENOMEM); + + /* ips are allocated right after the insn_array->values[] array */ + insn_array->ips = (void *)&insn_array->values[attr->max_entries]; + + bpf_map_init_from_attr(&insn_array->map, attr); + + return &insn_array->map; +} + +static void *insn_array_lookup_elem(struct bpf_map *map, void *key) +{ + struct bpf_insn_array *insn_array = cast_insn_array(map); + u32 index = *(u32 *)key; + + if (unlikely(index >= insn_array->map.max_entries)) + return NULL; + + return &insn_array->values[index]; +} + +static long insn_array_update_elem(struct bpf_map *map, void *key, void *value, u64 map_flags) +{ + struct bpf_insn_array *insn_array = cast_insn_array(map); + u32 index = *(u32 *)key; + struct bpf_insn_array_value val = {}; + + if (unlikely(index >= insn_array->map.max_entries)) + return -E2BIG; + + if (unlikely(map_flags & BPF_NOEXIST)) + return -EEXIST; + + copy_map_value(map, &val, value); + if (val.jitted_off || val.xlated_off) + return -EINVAL; + + insn_array->values[index].orig_off = val.orig_off; + + return 0; +} + +static long insn_array_delete_elem(struct bpf_map *map, void *key) +{ + return -EINVAL; +} + +static int insn_array_check_btf(const struct bpf_map *map, + const struct btf *btf, + const struct btf_type *key_type, + const struct btf_type *value_type) +{ + if (!btf_type_is_i32(key_type)) + return -EINVAL; + + if (!btf_type_is_i64(value_type)) + return -EINVAL; + + return 0; +} + +static u64 insn_array_mem_usage(const struct bpf_map *map) +{ + return insn_array_alloc_size(map->max_entries); +} + +BTF_ID_LIST_SINGLE(insn_array_btf_ids, struct, bpf_insn_array) + +const struct bpf_map_ops insn_array_map_ops = { + .map_alloc_check = insn_array_alloc_check, + .map_alloc = insn_array_alloc, + .map_free = insn_array_free, + .map_get_next_key = bpf_array_get_next_key, + .map_lookup_elem = insn_array_lookup_elem, + .map_update_elem = insn_array_update_elem, + .map_delete_elem = insn_array_delete_elem, + .map_check_btf = insn_array_check_btf, + .map_mem_usage = insn_array_mem_usage, + .map_btf_id = &insn_array_btf_ids[0], +}; + +static inline bool is_frozen(struct bpf_map *map) +{ + guard(mutex)(&map->freeze_mutex); + + return map->frozen; +} + +static bool is_insn_array(const struct bpf_map *map) +{ + return map->map_type == BPF_MAP_TYPE_INSN_ARRAY; +} + +static inline bool valid_offsets(const struct bpf_insn_array *insn_array, + const struct bpf_prog *prog) +{ + u32 off; + int i; + + for (i = 0; i < insn_array->map.max_entries; i++) { + off = insn_array->values[i].orig_off; + + if (off >= prog->len) + return false; + + if (off > 0) { + if (prog->insnsi[off-1].code == (BPF_LD | BPF_DW | BPF_IMM)) + return false; + } + } + + return true; +} + +int bpf_insn_array_init(struct bpf_map *map, const struct bpf_prog *prog) +{ + struct bpf_insn_array *insn_array = cast_insn_array(map); + struct bpf_insn_array_value *values = insn_array->values; + int i; + + if (!is_frozen(map)) + return -EINVAL; + + if (!valid_offsets(insn_array, prog)) + return -EINVAL; + + /* + * There can be only one program using the map + */ + if (atomic_xchg(&insn_array->used, 1)) + return -EBUSY; + + /* + * Reset all the map indexes to the original values. This is needed, + * e.g., when a replay of verification with different log level should + * be performed. + */ + for (i = 0; i < map->max_entries; i++) + values[i].xlated_off = values[i].orig_off; + + return 0; +} + +int bpf_insn_array_ready(struct bpf_map *map) +{ + struct bpf_insn_array *insn_array = cast_insn_array(map); + int i; + + for (i = 0; i < map->max_entries; i++) { + if (insn_array->values[i].xlated_off == INSN_DELETED) + continue; + if (!insn_array->ips[i]) + return -EFAULT; + } + + return 0; +} + +void bpf_insn_array_release(struct bpf_map *map) +{ + struct bpf_insn_array *insn_array = cast_insn_array(map); + + atomic_set(&insn_array->used, 0); +} + +void bpf_insn_array_adjust(struct bpf_map *map, u32 off, u32 len) +{ + struct bpf_insn_array *insn_array = cast_insn_array(map); + int i; + + if (len <= 1) + return; + + for (i = 0; i < map->max_entries; i++) { + if (insn_array->values[i].xlated_off <= off) + continue; + if (insn_array->values[i].xlated_off == INSN_DELETED) + continue; + insn_array->values[i].xlated_off += len - 1; + } +} + +void bpf_insn_array_adjust_after_remove(struct bpf_map *map, u32 off, u32 len) +{ + struct bpf_insn_array *insn_array = cast_insn_array(map); + int i; + + for (i = 0; i < map->max_entries; i++) { + if (insn_array->values[i].xlated_off < off) + continue; + if (insn_array->values[i].xlated_off == INSN_DELETED) + continue; + if (insn_array->values[i].xlated_off < off + len) + insn_array->values[i].xlated_off = INSN_DELETED; + else + insn_array->values[i].xlated_off -= len; + } +} + +/* + * This function is called by JITs. The image is the real program + * image, the offsets array set up the xlated -> jitted mapping. + * The offsets[xlated] offset should point to the beginning of + * the jitted instruction. + */ +void bpf_prog_update_insn_ptrs(struct bpf_prog *prog, u32 *offsets, void *image) +{ + struct bpf_insn_array *insn_array; + struct bpf_map *map; + u32 xlated_off; + int i, j; + + if (!offsets || !image) + return; + + for (i = 0; i < prog->aux->used_map_cnt; i++) { + map = prog->aux->used_maps[i]; + if (!is_insn_array(map)) + continue; + + insn_array = cast_insn_array(map); + for (j = 0; j < map->max_entries; j++) { + xlated_off = insn_array->values[j].xlated_off; + if (xlated_off == INSN_DELETED) + continue; + if (xlated_off < prog->aux->subprog_start) + continue; + xlated_off -= prog->aux->subprog_start; + if (xlated_off >= prog->len) + continue; + + insn_array->values[j].jitted_off = offsets[xlated_off]; + insn_array->ips[j] = (long)(image + offsets[xlated_off]); + } + } +} diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c index 8a129746bd6c..f62d61b6730a 100644 --- a/kernel/bpf/syscall.c +++ b/kernel/bpf/syscall.c @@ -1493,6 +1493,7 @@ static int map_create(union bpf_attr *attr, bpfptr_t uattr) case BPF_MAP_TYPE_STRUCT_OPS: case BPF_MAP_TYPE_CPUMAP: case BPF_MAP_TYPE_ARENA: + case BPF_MAP_TYPE_INSN_ARRAY: if (!bpf_token_capable(token, CAP_BPF)) goto put_token; break; @@ -2853,6 +2854,23 @@ static int bpf_prog_verify_signature(struct bpf_prog *prog, union bpf_attr *attr return err; } +static int bpf_prog_mark_insn_arrays_ready(struct bpf_prog *prog) +{ + int err; + int i; + + for (i = 0; i < prog->aux->used_map_cnt; i++) { + if (prog->aux->used_maps[i]->map_type != BPF_MAP_TYPE_INSN_ARRAY) + continue; + + err = bpf_insn_array_ready(prog->aux->used_maps[i]); + if (err) + return err; + } + + return 0; +} + /* last field in 'union bpf_attr' used by this command */ #define BPF_PROG_LOAD_LAST_FIELD keyring_id @@ -3082,6 +3100,10 @@ static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr, u32 uattr_size) if (err < 0) goto free_used_maps; + err = bpf_prog_mark_insn_arrays_ready(prog); + if (err < 0) + goto free_used_maps; + err = bpf_prog_alloc_id(prog); if (err) goto free_used_maps; diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index e4928846e763..dfe5741812b9 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -10086,6 +10086,8 @@ static int check_map_func_compatibility(struct bpf_verifier_env *env, func_id != BPF_FUNC_map_push_elem) goto error; break; + case BPF_MAP_TYPE_INSN_ARRAY: + goto error; default: break; } @@ -20582,6 +20584,15 @@ static int __add_used_map(struct bpf_verifier_env *env, struct bpf_map *map) env->used_maps[env->used_map_cnt++] = map; + if (map->map_type == BPF_MAP_TYPE_INSN_ARRAY) { + err = bpf_insn_array_init(map, env->prog); + if (err) { + verbose(env, "Failed to properly initialize insn array\n"); + return err; + } + env->insn_array_maps[env->insn_array_map_cnt++] = map; + } + return env->used_map_cnt - 1; } @@ -20828,6 +20839,33 @@ static void adjust_subprog_starts(struct bpf_verifier_env *env, u32 off, u32 len } } +static void release_insn_arrays(struct bpf_verifier_env *env) +{ + int i; + + for (i = 0; i < env->insn_array_map_cnt; i++) + bpf_insn_array_release(env->insn_array_maps[i]); +} + +static void adjust_insn_arrays(struct bpf_verifier_env *env, u32 off, u32 len) +{ + int i; + + if (len == 1) + return; + + for (i = 0; i < env->insn_array_map_cnt; i++) + bpf_insn_array_adjust(env->insn_array_maps[i], off, len); +} + +static void adjust_insn_arrays_after_remove(struct bpf_verifier_env *env, u32 off, u32 len) +{ + int i; + + for (i = 0; i < env->insn_array_map_cnt; i++) + bpf_insn_array_adjust_after_remove(env->insn_array_maps[i], off, len); +} + static void adjust_poke_descs(struct bpf_prog *prog, u32 off, u32 len) { struct bpf_jit_poke_descriptor *tab = prog->aux->poke_tab; @@ -20869,6 +20907,7 @@ static struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 of } adjust_insn_aux_data(env, new_prog, off, len); adjust_subprog_starts(env, off, len); + adjust_insn_arrays(env, off, len); adjust_poke_descs(new_prog, off, len); return new_prog; } @@ -21052,6 +21091,8 @@ static int verifier_remove_insns(struct bpf_verifier_env *env, u32 off, u32 cnt) if (err) return err; + adjust_insn_arrays_after_remove(env, off, cnt); + memmove(aux_data + off, aux_data + off + cnt, sizeof(*aux_data) * (orig_prog_len - off - cnt)); @@ -21695,6 +21736,8 @@ static int jit_subprogs(struct bpf_verifier_env *env) func[i]->aux->jited_linfo = prog->aux->jited_linfo; func[i]->aux->linfo_idx = env->subprog_info[i].linfo_idx; func[i]->aux->arena = prog->aux->arena; + func[i]->aux->used_maps = env->used_maps; + func[i]->aux->used_map_cnt = env->used_map_cnt; num_exentries = 0; insn = func[i]->insnsi; for (j = 0; j < func[i]->len; j++, insn++) { @@ -24871,6 +24914,8 @@ skip_full_check: adjust_btf_func(env); err_release_maps: + if (ret) + release_insn_arrays(env); if (!env->prog->aux->used_maps) /* if we didn't copy map pointers into bpf_prog_info, release * them now. Otherwise free_used_maps() will release them. diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h index 1d73f165394d..f5713f59ac10 100644 --- a/tools/include/uapi/linux/bpf.h +++ b/tools/include/uapi/linux/bpf.h @@ -1026,6 +1026,7 @@ enum bpf_map_type { BPF_MAP_TYPE_USER_RINGBUF, BPF_MAP_TYPE_CGRP_STORAGE, BPF_MAP_TYPE_ARENA, + BPF_MAP_TYPE_INSN_ARRAY, __MAX_BPF_MAP_TYPE }; @@ -7649,4 +7650,24 @@ enum bpf_kfunc_flags { BPF_F_PAD_ZEROS = (1ULL << 0), }; +/* + * Values of a BPF_MAP_TYPE_INSN_ARRAY entry must be of this type. + * + * Before the map is used the orig_off field should point to an + * instruction inside the program being loaded. The other fields + * must be set to 0. + * + * After the program is loaded, the xlated_off will be adjusted + * by the verifier to point to the index of the original instruction + * in the xlated program. If the instruction is deleted, it will + * be set to (u32)-1. The jitted_off will be set to the corresponding + * offset in the jitted image of the program. + */ +struct bpf_insn_array_value { + __u32 orig_off; + __u32 xlated_off; + __u32 jitted_off; + __u32 :32; +}; + #endif /* _UAPI__LINUX_BPF_H__ */ -- cgit v1.2.3 From c6230446b1a6f3c91effafd99f604de455da52e5 Mon Sep 17 00:00:00 2001 From: Daniel Golle Date: Mon, 3 Nov 2025 12:20:20 +0000 Subject: net: dsa: add tagging driver for MaxLinear GSW1xx switch family Add support for a new DSA tagging protocol driver for the MaxLinear GSW1xx switch family. The GSW1xx switches use a proprietary 8-byte special tag inserted between the source MAC address and the EtherType field to indicate the source and destination ports for frames traversing the CPU port. Implement the tag handling logic to insert the special tag on transmit and parse it on receive. Signed-off-by: Daniel Golle Reviewed-by: Alexander Sverdlin Tested-by: Alexander Sverdlin Link: https://patch.msgid.link/0e973ebfd9433c30c96f50670da9e9449a0d98f2.1762170107.git.daniel@makrotopia.org Signed-off-by: Jakub Kicinski --- MAINTAINERS | 3 +- include/net/dsa.h | 2 + include/uapi/linux/if_ether.h | 1 + net/dsa/Kconfig | 8 +++ net/dsa/Makefile | 1 + net/dsa/tag_mxl-gsw1xx.c | 116 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 net/dsa/tag_mxl-gsw1xx.c (limited to 'include/uapi/linux') diff --git a/MAINTAINERS b/MAINTAINERS index 12cd8a5ab274..0dc4aa37d903 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14053,7 +14053,7 @@ F: tools/testing/selftests/landlock/ K: landlock K: LANDLOCK -LANTIQ / INTEL Ethernet drivers +LANTIQ / MAXLINEAR / INTEL Ethernet DSA drivers M: Hauke Mehrtens L: netdev@vger.kernel.org S: Maintained @@ -14061,6 +14061,7 @@ F: Documentation/devicetree/bindings/net/dsa/lantiq,gswip.yaml F: drivers/net/dsa/lantiq/* F: drivers/net/ethernet/lantiq_xrx200.c F: net/dsa/tag_gswip.c +F: net/dsa/tag_mxl-gsw1xx.c LANTIQ MIPS ARCHITECTURE M: John Crispin diff --git a/include/net/dsa.h b/include/net/dsa.h index 67762fdaf3c7..2df2e2ead9a8 100644 --- a/include/net/dsa.h +++ b/include/net/dsa.h @@ -56,6 +56,7 @@ struct tc_action; #define DSA_TAG_PROTO_VSC73XX_8021Q_VALUE 28 #define DSA_TAG_PROTO_BRCM_LEGACY_FCS_VALUE 29 #define DSA_TAG_PROTO_YT921X_VALUE 30 +#define DSA_TAG_PROTO_MXL_GSW1XX_VALUE 31 enum dsa_tag_protocol { DSA_TAG_PROTO_NONE = DSA_TAG_PROTO_NONE_VALUE, @@ -89,6 +90,7 @@ enum dsa_tag_protocol { DSA_TAG_PROTO_LAN937X = DSA_TAG_PROTO_LAN937X_VALUE, DSA_TAG_PROTO_VSC73XX_8021Q = DSA_TAG_PROTO_VSC73XX_8021Q_VALUE, DSA_TAG_PROTO_YT921X = DSA_TAG_PROTO_YT921X_VALUE, + DSA_TAG_PROTO_MXL_GSW1XX = DSA_TAG_PROTO_MXL_GSW1XX_VALUE, }; struct dsa_switch; diff --git a/include/uapi/linux/if_ether.h b/include/uapi/linux/if_ether.h index cfd200c87e5e..2c93b7b731c8 100644 --- a/include/uapi/linux/if_ether.h +++ b/include/uapi/linux/if_ether.h @@ -92,6 +92,7 @@ #define ETH_P_ETHERCAT 0x88A4 /* EtherCAT */ #define ETH_P_8021AD 0x88A8 /* 802.1ad Service VLAN */ #define ETH_P_802_EX1 0x88B5 /* 802.1 Local Experimental 1. */ +#define ETH_P_MXLGSW 0x88C3 /* MaxLinear GSW DSA [ NOT AN OFFICIALLY REGISTERED ID ] */ #define ETH_P_PREAUTH 0x88C7 /* 802.11 Preauthentication */ #define ETH_P_TIPC 0x88CA /* TIPC */ #define ETH_P_LLDP 0x88CC /* Link Layer Discovery Protocol */ diff --git a/net/dsa/Kconfig b/net/dsa/Kconfig index 6b94028b1fcc..f86b30742122 100644 --- a/net/dsa/Kconfig +++ b/net/dsa/Kconfig @@ -104,6 +104,14 @@ config NET_DSA_TAG_MTK Say Y or M if you want to enable support for tagging frames for Mediatek switches. +config NET_DSA_TAG_MXL_GSW1XX + tristate "Tag driver for MaxLinear GSW1xx switches" + help + The GSW1xx family of switches supports an 8-byte special tag which + can be used on the CPU port of the switch. + Say Y or M if you want to enable support for tagging frames for + MaxLinear GSW1xx switches. + config NET_DSA_TAG_KSZ tristate "Tag driver for Microchip 8795/937x/9477/9893 families of switches" help diff --git a/net/dsa/Makefile b/net/dsa/Makefile index 4b011a1d5c87..42d173f5a701 100644 --- a/net/dsa/Makefile +++ b/net/dsa/Makefile @@ -28,6 +28,7 @@ obj-$(CONFIG_NET_DSA_TAG_HELLCREEK) += tag_hellcreek.o obj-$(CONFIG_NET_DSA_TAG_KSZ) += tag_ksz.o obj-$(CONFIG_NET_DSA_TAG_LAN9303) += tag_lan9303.o obj-$(CONFIG_NET_DSA_TAG_MTK) += tag_mtk.o +obj-$(CONFIG_NET_DSA_TAG_MXL_GSW1XX) += tag_mxl-gsw1xx.o obj-$(CONFIG_NET_DSA_TAG_NONE) += tag_none.o obj-$(CONFIG_NET_DSA_TAG_OCELOT) += tag_ocelot.o obj-$(CONFIG_NET_DSA_TAG_OCELOT_8021Q) += tag_ocelot_8021q.o diff --git a/net/dsa/tag_mxl-gsw1xx.c b/net/dsa/tag_mxl-gsw1xx.c new file mode 100644 index 000000000000..701a079955f2 --- /dev/null +++ b/net/dsa/tag_mxl-gsw1xx.c @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * DSA driver Special Tag support for MaxLinear GSW1xx switch chips + * + * Copyright (C) 2025 Daniel Golle + * Copyright (C) 2023 - 2024 MaxLinear Inc. + */ + +#include +#include +#include +#include + +#include "tag.h" + +/* To define the outgoing port and to discover the incoming port a special + * tag is used by the GSW1xx. + * + * Dest MAC Src MAC special TAG EtherType + * ...| 1 2 3 4 5 6 | 1 2 3 4 5 6 | 1 2 3 4 5 6 7 8 | 1 2 |... + * |<--------------->| + */ + +#define GSW1XX_TAG_NAME "gsw1xx" + +/* special tag header length (RX and TX) */ +#define GSW1XX_HEADER_LEN 8 + +/* Word 0 = Ethertype -> 0x88C3 */ + +/* Word 1 */ +#define GSW1XX_TX_PORT_MAP GENMASK(7, 0) +#define GSW1XX_TX_PORT_MAP_EN BIT(15) +#define GSW1XX_TX_CLASS_EN BIT(14) +#define GSW1XX_TX_TIME_STAMP_EN BIT(13) +#define GSW1XX_TX_LRN_DIS BIT(12) +#define GSW1XX_TX_CLASS GENMASK(11, 8) + +/* special tag in RX path header */ +/* Word 2 */ +#define GSW1XX_RX_PORT_MAP GENMASK(15, 8) + +static struct sk_buff *gsw1xx_tag_xmit(struct sk_buff *skb, + struct net_device *dev) +{ + struct dsa_port *dp = dsa_user_to_port(dev); + __be16 *gsw1xx_tag; + + /* provide additional space 'GSW1XX_HEADER_LEN' bytes */ + skb_push(skb, GSW1XX_HEADER_LEN); + + /* add space between MAC address and Ethertype */ + dsa_alloc_etype_header(skb, GSW1XX_HEADER_LEN); + + /* special tag ingress */ + gsw1xx_tag = dsa_etype_header_pos_tx(skb); + gsw1xx_tag[0] = htons(ETH_P_MXLGSW); + gsw1xx_tag[1] = htons(GSW1XX_TX_PORT_MAP_EN | GSW1XX_TX_LRN_DIS | + FIELD_PREP(GSW1XX_TX_PORT_MAP, BIT(dp->index))); + + gsw1xx_tag[2] = 0; + gsw1xx_tag[3] = 0; + + return skb; +} + +static struct sk_buff *gsw1xx_tag_rcv(struct sk_buff *skb, + struct net_device *dev) +{ + int port; + __be16 *gsw1xx_tag; + + if (unlikely(!pskb_may_pull(skb, GSW1XX_HEADER_LEN))) { + dev_warn_ratelimited(&dev->dev, "Dropping packet, cannot pull SKB\n"); + return NULL; + } + + gsw1xx_tag = dsa_etype_header_pos_rx(skb); + + if (unlikely(ntohs(gsw1xx_tag[0]) != ETH_P_MXLGSW)) { + dev_warn_ratelimited(&dev->dev, "Dropping packet due to invalid special tag\n"); + dev_warn_ratelimited(&dev->dev, "Tag: %8ph\n", gsw1xx_tag); + return NULL; + } + + /* Get source port information */ + port = FIELD_GET(GSW1XX_RX_PORT_MAP, ntohs(gsw1xx_tag[1])); + skb->dev = dsa_conduit_find_user(dev, 0, port); + if (!skb->dev) { + dev_warn_ratelimited(&dev->dev, "Dropping packet due to invalid source port\n"); + dev_warn_ratelimited(&dev->dev, "Tag: %8ph\n", gsw1xx_tag); + return NULL; + } + + /* remove the GSW1xx special tag between MAC addresses and the current + * ethertype field. + */ + skb_pull_rcsum(skb, GSW1XX_HEADER_LEN); + dsa_strip_etype_header(skb, GSW1XX_HEADER_LEN); + + return skb; +} + +static const struct dsa_device_ops gsw1xx_netdev_ops = { + .name = GSW1XX_TAG_NAME, + .proto = DSA_TAG_PROTO_MXL_GSW1XX, + .xmit = gsw1xx_tag_xmit, + .rcv = gsw1xx_tag_rcv, + .needed_headroom = GSW1XX_HEADER_LEN, +}; + +MODULE_DESCRIPTION("DSA tag driver for MaxLinear GSW1xx 8 byte protocol"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_MXL_GSW1XX, GSW1XX_TAG_NAME); + +module_dsa_tag_driver(gsw1xx_netdev_ops); -- cgit v1.2.3 From dae4a92399fa8d68aa917db6bb3245f83021e762 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Wed, 5 Nov 2025 16:26:02 -0800 Subject: psp: report basic stats from the core Track and report stats common to all psp devices from the core. A 'stale-event' is when the core marks the rx state of an active psp_assoc as incapable of authenticating psp encapsulated data. Signed-off-by: Daniel Zahka Link: https://patch.msgid.link/20251106002608.1578518-2-daniel.zahka@gmail.com Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/psp.yaml | 40 +++++++++++++++++++ include/net/psp/types.h | 9 +++++ include/uapi/linux/psp.h | 10 +++++ net/psp/psp-nl-gen.c | 19 +++++++++ net/psp/psp-nl-gen.h | 2 + net/psp/psp_nl.c | 74 ++++++++++++++++++++++++++++++++++++ net/psp/psp_sock.c | 4 +- 7 files changed, 157 insertions(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/Documentation/netlink/specs/psp.yaml b/Documentation/netlink/specs/psp.yaml index 944429e5c9a8..914148221384 100644 --- a/Documentation/netlink/specs/psp.yaml +++ b/Documentation/netlink/specs/psp.yaml @@ -76,6 +76,28 @@ attribute-sets: name: spi doc: Security Parameters Index (SPI) of the association. type: u32 + - + name: stats + attributes: + - + name: dev-id + doc: PSP device ID. + type: u32 + checks: + min: 1 + - + name: key-rotations + type: uint + doc: | + Number of key rotations during the lifetime of the device. + Kernel statistic. + - + name: stale-events + type: uint + doc: | + Number of times a socket's Rx got shut down due to using + a key which went stale (fully rotated out). + Kernel statistic. operations: list: @@ -177,6 +199,24 @@ operations: pre: psp-assoc-device-get-locked post: psp-device-unlock + - + name: get-stats + doc: Get device statistics. + attribute-set: stats + do: + request: + attributes: + - dev-id + reply: &stats-all + attributes: + - dev-id + - key-rotations + - stale-events + pre: psp-device-get-locked + post: psp-device-unlock + dump: + reply: *stats-all + mcast-groups: list: - diff --git a/include/net/psp/types.h b/include/net/psp/types.h index 31cee64b7c86..5b0ccaac3882 100644 --- a/include/net/psp/types.h +++ b/include/net/psp/types.h @@ -59,6 +59,10 @@ struct psp_dev_config { * device key * @stale_assocs: associations which use a rotated out key * + * @stats: statistics maintained by the core + * @stats.rotations: See stats attr key-rotations + * @stats.stales: See stats attr stale-events + * * @rcu: RCU head for freeing the structure */ struct psp_dev { @@ -81,6 +85,11 @@ struct psp_dev { struct list_head prev_assocs; struct list_head stale_assocs; + struct { + unsigned long rotations; + unsigned long stales; + } stats; + struct rcu_head rcu; }; diff --git a/include/uapi/linux/psp.h b/include/uapi/linux/psp.h index 607c42c39ba5..31592760ad79 100644 --- a/include/uapi/linux/psp.h +++ b/include/uapi/linux/psp.h @@ -45,6 +45,15 @@ enum { PSP_A_KEYS_MAX = (__PSP_A_KEYS_MAX - 1) }; +enum { + PSP_A_STATS_DEV_ID = 1, + PSP_A_STATS_KEY_ROTATIONS, + PSP_A_STATS_STALE_EVENTS, + + __PSP_A_STATS_MAX, + PSP_A_STATS_MAX = (__PSP_A_STATS_MAX - 1) +}; + enum { PSP_CMD_DEV_GET = 1, PSP_CMD_DEV_ADD_NTF, @@ -55,6 +64,7 @@ enum { PSP_CMD_KEY_ROTATE_NTF, PSP_CMD_RX_ASSOC, PSP_CMD_TX_ASSOC, + PSP_CMD_GET_STATS, __PSP_CMD_MAX, PSP_CMD_MAX = (__PSP_CMD_MAX - 1) diff --git a/net/psp/psp-nl-gen.c b/net/psp/psp-nl-gen.c index 9fdd6f831803..73f8b06d66f0 100644 --- a/net/psp/psp-nl-gen.c +++ b/net/psp/psp-nl-gen.c @@ -47,6 +47,11 @@ static const struct nla_policy psp_tx_assoc_nl_policy[PSP_A_ASSOC_SOCK_FD + 1] = [PSP_A_ASSOC_SOCK_FD] = { .type = NLA_U32, }, }; +/* PSP_CMD_GET_STATS - do */ +static const struct nla_policy psp_get_stats_nl_policy[PSP_A_STATS_DEV_ID + 1] = { + [PSP_A_STATS_DEV_ID] = NLA_POLICY_MIN(NLA_U32, 1), +}; + /* Ops table for psp */ static const struct genl_split_ops psp_nl_ops[] = { { @@ -99,6 +104,20 @@ static const struct genl_split_ops psp_nl_ops[] = { .maxattr = PSP_A_ASSOC_SOCK_FD, .flags = GENL_CMD_CAP_DO, }, + { + .cmd = PSP_CMD_GET_STATS, + .pre_doit = psp_device_get_locked, + .doit = psp_nl_get_stats_doit, + .post_doit = psp_device_unlock, + .policy = psp_get_stats_nl_policy, + .maxattr = PSP_A_STATS_DEV_ID, + .flags = GENL_CMD_CAP_DO, + }, + { + .cmd = PSP_CMD_GET_STATS, + .dumpit = psp_nl_get_stats_dumpit, + .flags = GENL_CMD_CAP_DUMP, + }, }; static const struct genl_multicast_group psp_nl_mcgrps[] = { diff --git a/net/psp/psp-nl-gen.h b/net/psp/psp-nl-gen.h index 25268ed11fb5..5bc3b5d5a53e 100644 --- a/net/psp/psp-nl-gen.h +++ b/net/psp/psp-nl-gen.h @@ -28,6 +28,8 @@ int psp_nl_dev_set_doit(struct sk_buff *skb, struct genl_info *info); int psp_nl_key_rotate_doit(struct sk_buff *skb, struct genl_info *info); int psp_nl_rx_assoc_doit(struct sk_buff *skb, struct genl_info *info); int psp_nl_tx_assoc_doit(struct sk_buff *skb, struct genl_info *info); +int psp_nl_get_stats_doit(struct sk_buff *skb, struct genl_info *info); +int psp_nl_get_stats_dumpit(struct sk_buff *skb, struct netlink_callback *cb); enum { PSP_NLGRP_MGMT, diff --git a/net/psp/psp_nl.c b/net/psp/psp_nl.c index 8aaca62744c3..f990cccbe99c 100644 --- a/net/psp/psp_nl.c +++ b/net/psp/psp_nl.c @@ -262,6 +262,7 @@ int psp_nl_key_rotate_doit(struct sk_buff *skb, struct genl_info *info) psd->generation & ~PSP_GEN_VALID_MASK); psp_assocs_key_rotated(psd); + psd->stats.rotations++; nlmsg_end(ntf, (struct nlmsghdr *)ntf->data); genlmsg_multicast_netns(&psp_nl_family, dev_net(psd->main_netdev), ntf, @@ -503,3 +504,76 @@ err_free_msg: nlmsg_free(rsp); return err; } + +static int +psp_nl_stats_fill(struct psp_dev *psd, struct sk_buff *rsp, + const struct genl_info *info) +{ + void *hdr; + + hdr = genlmsg_iput(rsp, info); + if (!hdr) + return -EMSGSIZE; + + if (nla_put_u32(rsp, PSP_A_STATS_DEV_ID, psd->id) || + nla_put_uint(rsp, PSP_A_STATS_KEY_ROTATIONS, + psd->stats.rotations) || + nla_put_uint(rsp, PSP_A_STATS_STALE_EVENTS, psd->stats.stales)) + goto err_cancel_msg; + + genlmsg_end(rsp, hdr); + return 0; + +err_cancel_msg: + genlmsg_cancel(rsp, hdr); + return -EMSGSIZE; +} + +int psp_nl_get_stats_doit(struct sk_buff *skb, struct genl_info *info) +{ + struct psp_dev *psd = info->user_ptr[0]; + struct sk_buff *rsp; + int err; + + rsp = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (!rsp) + return -ENOMEM; + + err = psp_nl_stats_fill(psd, rsp, info); + if (err) + goto err_free_msg; + + return genlmsg_reply(rsp, info); + +err_free_msg: + nlmsg_free(rsp); + return err; +} + +static int +psp_nl_stats_get_dumpit_one(struct sk_buff *rsp, struct netlink_callback *cb, + struct psp_dev *psd) +{ + if (psp_dev_check_access(psd, sock_net(rsp->sk))) + return 0; + + return psp_nl_stats_fill(psd, rsp, genl_info_dump(cb)); +} + +int psp_nl_get_stats_dumpit(struct sk_buff *rsp, struct netlink_callback *cb) +{ + struct psp_dev *psd; + int err = 0; + + mutex_lock(&psp_devs_lock); + xa_for_each_start(&psp_devs, cb->args[0], psd, cb->args[0]) { + mutex_lock(&psd->lock); + err = psp_nl_stats_get_dumpit_one(rsp, cb, psd); + mutex_unlock(&psd->lock); + if (err) + break; + } + mutex_unlock(&psp_devs_lock); + + return err; +} diff --git a/net/psp/psp_sock.c b/net/psp/psp_sock.c index a931d825d1cc..f785672b7df6 100644 --- a/net/psp/psp_sock.c +++ b/net/psp/psp_sock.c @@ -253,8 +253,10 @@ void psp_assocs_key_rotated(struct psp_dev *psd) /* Mark the stale associations as invalid, they will no longer * be able to Rx any traffic. */ - list_for_each_entry_safe(pas, next, &psd->prev_assocs, assocs_list) + list_for_each_entry_safe(pas, next, &psd->prev_assocs, assocs_list) { pas->generation |= ~PSP_GEN_VALID_MASK; + psd->stats.stales++; + } list_splice_init(&psd->prev_assocs, &psd->stale_assocs); list_splice_init(&psd->active_assocs, &psd->prev_assocs); -- cgit v1.2.3 From f05d26198cf2c71f25f6bbe62ca4481c15543922 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Wed, 5 Nov 2025 16:26:04 -0800 Subject: psp: add stats from psp spec to driver facing api Provide a driver api for reporting device statistics required by the "Implementation Requirements" section of the PSP Architecture Specification. Use a warning to ensure drivers report stats required by the spec. Signed-off-by: Daniel Zahka Link: https://patch.msgid.link/20251106002608.1578518-4-daniel.zahka@gmail.com Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/psp.yaml | 55 ++++++++++++++++++++++++++++++++++++ include/net/psp/types.h | 23 +++++++++++++++ include/uapi/linux/psp.h | 8 ++++++ net/psp/psp_main.c | 3 +- net/psp/psp_nl.c | 21 +++++++++++++- 5 files changed, 108 insertions(+), 2 deletions(-) (limited to 'include/uapi/linux') diff --git a/Documentation/netlink/specs/psp.yaml b/Documentation/netlink/specs/psp.yaml index 914148221384..f3a57782d2cf 100644 --- a/Documentation/netlink/specs/psp.yaml +++ b/Documentation/netlink/specs/psp.yaml @@ -98,6 +98,61 @@ attribute-sets: Number of times a socket's Rx got shut down due to using a key which went stale (fully rotated out). Kernel statistic. + - + name: rx-packets + type: uint + doc: | + Number of successfully processed and authenticated PSP packets. + Device statistic (from the PSP spec). + - + name: rx-bytes + type: uint + doc: | + Number of successfully authenticated PSP bytes received, counting from + the first byte after the IV through the last byte of payload. + The fixed initial portion of the PSP header (16 bytes) + and the PSP trailer/ICV (16 bytes) are not included in this count. + Device statistic (from the PSP spec). + - + name: rx-auth-fail + type: uint + doc: | + Number of received PSP packets with unsuccessful authentication. + Device statistic (from the PSP spec). + - + name: rx-error + type: uint + doc: | + Number of received PSP packets with length/framing errors. + Device statistic (from the PSP spec). + - + name: rx-bad + type: uint + doc: | + Number of received PSP packets with miscellaneous errors + (invalid master key indicated by SPI, unsupported version, etc.) + Device statistic (from the PSP spec). + - + name: tx-packets + type: uint + doc: | + Number of successfully processed PSP packets for transmission. + Device statistic (from the PSP spec). + - + name: tx-bytes + type: uint + doc: | + Number of successfully processed PSP bytes for transmit, counting from + the first byte after the IV through the last byte of payload. + The fixed initial portion of the PSP header (16 bytes) + and the PSP trailer/ICV (16 bytes) are not included in this count. + Device statistic (from the PSP spec). + - + name: tx-error + type: uint + doc: | + Number of PSP packets for transmission with errors. + Device statistic (from the PSP spec). operations: list: diff --git a/include/net/psp/types.h b/include/net/psp/types.h index 5b0ccaac3882..25a9096d4e7d 100644 --- a/include/net/psp/types.h +++ b/include/net/psp/types.h @@ -150,6 +150,22 @@ struct psp_assoc { u8 drv_data[] __aligned(8); }; +struct psp_dev_stats { + union { + struct { + u64 rx_packets; + u64 rx_bytes; + u64 rx_auth_fail; + u64 rx_error; + u64 rx_bad; + u64 tx_packets; + u64 tx_bytes; + u64 tx_error; + }; + DECLARE_FLEX_ARRAY(u64, required); + }; +}; + /** * struct psp_dev_ops - netdev driver facing PSP callbacks */ @@ -188,6 +204,13 @@ struct psp_dev_ops { * Remove an association from the device. */ void (*tx_key_del)(struct psp_dev *psd, struct psp_assoc *pas); + + /** + * @get_stats: get statistics from the device + * Stats required by the spec must be maintained and filled in. + * Stats must be filled in member-by-member, never memset the struct. + */ + void (*get_stats)(struct psp_dev *psd, struct psp_dev_stats *stats); }; #endif /* __NET_PSP_H */ diff --git a/include/uapi/linux/psp.h b/include/uapi/linux/psp.h index 31592760ad79..d8449c043ba1 100644 --- a/include/uapi/linux/psp.h +++ b/include/uapi/linux/psp.h @@ -49,6 +49,14 @@ enum { PSP_A_STATS_DEV_ID = 1, PSP_A_STATS_KEY_ROTATIONS, PSP_A_STATS_STALE_EVENTS, + PSP_A_STATS_RX_PACKETS, + PSP_A_STATS_RX_BYTES, + PSP_A_STATS_RX_AUTH_FAIL, + PSP_A_STATS_RX_ERROR, + PSP_A_STATS_RX_BAD, + PSP_A_STATS_TX_PACKETS, + PSP_A_STATS_TX_BYTES, + PSP_A_STATS_TX_ERROR, __PSP_A_STATS_MAX, PSP_A_STATS_MAX = (__PSP_A_STATS_MAX - 1) diff --git a/net/psp/psp_main.c b/net/psp/psp_main.c index 481aaf0fc9fc..a8534124f626 100644 --- a/net/psp/psp_main.c +++ b/net/psp/psp_main.c @@ -60,7 +60,8 @@ psp_dev_create(struct net_device *netdev, !psd_ops->key_rotate || !psd_ops->rx_spi_alloc || !psd_ops->tx_key_add || - !psd_ops->tx_key_del)) + !psd_ops->tx_key_del || + !psd_ops->get_stats)) return ERR_PTR(-EINVAL); psd = kzalloc(sizeof(*psd), GFP_KERNEL); diff --git a/net/psp/psp_nl.c b/net/psp/psp_nl.c index f990cccbe99c..6afd7707ec12 100644 --- a/net/psp/psp_nl.c +++ b/net/psp/psp_nl.c @@ -1,5 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only +#include #include #include #include @@ -509,7 +510,17 @@ static int psp_nl_stats_fill(struct psp_dev *psd, struct sk_buff *rsp, const struct genl_info *info) { + unsigned int required_cnt = sizeof(struct psp_dev_stats) / sizeof(u64); + struct psp_dev_stats stats; void *hdr; + int i; + + memset(&stats, 0xff, sizeof(stats)); + psd->ops->get_stats(psd, &stats); + + for (i = 0; i < required_cnt; i++) + if (WARN_ON_ONCE(stats.required[i] == ETHTOOL_STAT_NOT_SET)) + return -EOPNOTSUPP; hdr = genlmsg_iput(rsp, info); if (!hdr) @@ -518,7 +529,15 @@ psp_nl_stats_fill(struct psp_dev *psd, struct sk_buff *rsp, if (nla_put_u32(rsp, PSP_A_STATS_DEV_ID, psd->id) || nla_put_uint(rsp, PSP_A_STATS_KEY_ROTATIONS, psd->stats.rotations) || - nla_put_uint(rsp, PSP_A_STATS_STALE_EVENTS, psd->stats.stales)) + nla_put_uint(rsp, PSP_A_STATS_STALE_EVENTS, psd->stats.stales) || + nla_put_uint(rsp, PSP_A_STATS_RX_PACKETS, stats.rx_packets) || + nla_put_uint(rsp, PSP_A_STATS_RX_BYTES, stats.rx_bytes) || + nla_put_uint(rsp, PSP_A_STATS_RX_AUTH_FAIL, stats.rx_auth_fail) || + nla_put_uint(rsp, PSP_A_STATS_RX_ERROR, stats.rx_error) || + nla_put_uint(rsp, PSP_A_STATS_RX_BAD, stats.rx_bad) || + nla_put_uint(rsp, PSP_A_STATS_TX_PACKETS, stats.tx_packets) || + nla_put_uint(rsp, PSP_A_STATS_TX_BYTES, stats.tx_bytes) || + nla_put_uint(rsp, PSP_A_STATS_TX_ERROR, stats.tx_error)) goto err_cancel_msg; genlmsg_end(rsp, hdr); -- cgit v1.2.3 From 62ed1b58224636185fa689db81224b8c8af46473 Mon Sep 17 00:00:00 2001 From: Li Nan Date: Mon, 3 Nov 2025 20:57:57 +0800 Subject: md: allow configuring logical block size Previously, raid array used the maximum logical block size (LBS) of all member disks. Adding a larger LBS disk at runtime could unexpectedly increase RAID's LBS, risking corruption of existing partitions. This can be reproduced by: ``` # LBS of sd[de] is 512 bytes, sdf is 4096 bytes. mdadm -CRq /dev/md0 -l1 -n3 /dev/sd[de] missing --assume-clean # LBS is 512 cat /sys/block/md0/queue/logical_block_size # create partition md0p1 parted -s /dev/md0 mklabel gpt mkpart primary 1MiB 100% lsblk | grep md0p1 # LBS becomes 4096 after adding sdf mdadm --add -q /dev/md0 /dev/sdf cat /sys/block/md0/queue/logical_block_size # partition lost partprobe /dev/md0 lsblk | grep md0p1 ``` Simply restricting larger-LBS disks is inflexible. In some scenarios, only disks with 512 bytes LBS are available currently, but later, disks with 4KB LBS may be added to the array. Making LBS configurable is the best way to solve this scenario. After this patch, the raid will: - store LBS in disk metadata - add a read-write sysfs 'mdX/logical_block_size' Future mdadm should support setting LBS via metadata field during RAID creation and the new sysfs. Though the kernel allows runtime LBS changes, users should avoid modifying it after creating partitions or filesystems to prevent compatibility issues. Only 1.x metadata supports configurable LBS. 0.90 metadata inits all fields to default values at auto-detect. Supporting 0.90 would require more extensive changes and no such use case has been observed. Note that many RAID paths rely on PAGE_SIZE alignment, including for metadata I/O. A larger LBS than PAGE_SIZE will result in metadata read/write failures. So this config should be prevented. Link: https://lore.kernel.org/linux-raid/20251103125757.1405796-6-linan666@huaweicloud.com Signed-off-by: Li Nan Reviewed-by: Xiao Ni Signed-off-by: Yu Kuai --- Documentation/admin-guide/md.rst | 10 ++++++ drivers/md/md-linear.c | 1 + drivers/md/md.c | 77 ++++++++++++++++++++++++++++++++++++++++ drivers/md/md.h | 1 + drivers/md/raid0.c | 1 + drivers/md/raid1.c | 1 + drivers/md/raid10.c | 1 + drivers/md/raid5.c | 1 + include/uapi/linux/raid/md_p.h | 3 +- 9 files changed, 95 insertions(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/Documentation/admin-guide/md.rst b/Documentation/admin-guide/md.rst index deed823eab01..dc7eab191caa 100644 --- a/Documentation/admin-guide/md.rst +++ b/Documentation/admin-guide/md.rst @@ -238,6 +238,16 @@ All md devices contain: the number of devices in a raid4/5/6, or to support external metadata formats which mandate such clipping. + logical_block_size + Configure the array's logical block size in bytes. This attribute + is only supported for 1.x meta. Write the value before starting + array. The final array LBS uses the maximum between this + configuration and LBS of all combined devices. Note that + LBS cannot exceed PAGE_SIZE before RAID supports folio. + WARNING: Arrays created on new kernel cannot be assembled at old + kernel due to padding check, Set module parameter 'check_new_feature' + to false to bypass, but data loss may occur. + reshape_position This is either ``none`` or a sector number within the devices of the array where ``reshape`` is up to. If this is set, the three diff --git a/drivers/md/md-linear.c b/drivers/md/md-linear.c index 25a6ddedea65..8d7b82c4a723 100644 --- a/drivers/md/md-linear.c +++ b/drivers/md/md-linear.c @@ -72,6 +72,7 @@ static int linear_set_limits(struct mddev *mddev) md_init_stacking_limits(&lim); lim.max_hw_sectors = mddev->chunk_sectors; + lim.logical_block_size = mddev->logical_block_size; lim.max_write_zeroes_sectors = mddev->chunk_sectors; lim.max_hw_wzeroes_unmap_sectors = mddev->chunk_sectors; lim.io_min = mddev->chunk_sectors << 9; diff --git a/drivers/md/md.c b/drivers/md/md.c index 9676e2477df6..7b5c5967568f 100644 --- a/drivers/md/md.c +++ b/drivers/md/md.c @@ -1999,6 +1999,7 @@ static int super_1_validate(struct mddev *mddev, struct md_rdev *freshest, struc mddev->layout = le32_to_cpu(sb->layout); mddev->raid_disks = le32_to_cpu(sb->raid_disks); mddev->dev_sectors = le64_to_cpu(sb->size); + mddev->logical_block_size = le32_to_cpu(sb->logical_block_size); mddev->events = ev1; mddev->bitmap_info.offset = 0; mddev->bitmap_info.space = 0; @@ -2208,6 +2209,7 @@ static void super_1_sync(struct mddev *mddev, struct md_rdev *rdev) sb->chunksize = cpu_to_le32(mddev->chunk_sectors); sb->level = cpu_to_le32(mddev->level); sb->layout = cpu_to_le32(mddev->layout); + sb->logical_block_size = cpu_to_le32(mddev->logical_block_size); if (test_bit(FailFast, &rdev->flags)) sb->devflags |= FailFast1; else @@ -5936,6 +5938,68 @@ static struct md_sysfs_entry md_serialize_policy = __ATTR(serialize_policy, S_IRUGO | S_IWUSR, serialize_policy_show, serialize_policy_store); +static int mddev_set_logical_block_size(struct mddev *mddev, + unsigned int lbs) +{ + int err = 0; + struct queue_limits lim; + + if (queue_logical_block_size(mddev->gendisk->queue) >= lbs) { + pr_err("%s: Cannot set LBS smaller than mddev LBS %u\n", + mdname(mddev), lbs); + return -EINVAL; + } + + lim = queue_limits_start_update(mddev->gendisk->queue); + lim.logical_block_size = lbs; + pr_info("%s: logical_block_size is changed, data may be lost\n", + mdname(mddev)); + err = queue_limits_commit_update(mddev->gendisk->queue, &lim); + if (err) + return err; + + mddev->logical_block_size = lbs; + /* New lbs will be written to superblock after array is running */ + set_bit(MD_SB_CHANGE_DEVS, &mddev->sb_flags); + return 0; +} + +static ssize_t +lbs_show(struct mddev *mddev, char *page) +{ + return sprintf(page, "%u\n", mddev->logical_block_size); +} + +static ssize_t +lbs_store(struct mddev *mddev, const char *buf, size_t len) +{ + unsigned int lbs; + int err = -EBUSY; + + /* Only 1.x meta supports configurable LBS */ + if (mddev->major_version == 0) + return -EINVAL; + + if (mddev->pers) + return -EBUSY; + + err = kstrtouint(buf, 10, &lbs); + if (err < 0) + return -EINVAL; + + err = mddev_lock(mddev); + if (err) + goto unlock; + + err = mddev_set_logical_block_size(mddev, lbs); + +unlock: + mddev_unlock(mddev); + return err ?: len; +} + +static struct md_sysfs_entry md_logical_block_size = +__ATTR(logical_block_size, 0644, lbs_show, lbs_store); static struct attribute *md_default_attrs[] = { &md_level.attr, @@ -5958,6 +6022,7 @@ static struct attribute *md_default_attrs[] = { &md_consistency_policy.attr, &md_fail_last_dev.attr, &md_serialize_policy.attr, + &md_logical_block_size.attr, NULL, }; @@ -6088,6 +6153,17 @@ int mddev_stack_rdev_limits(struct mddev *mddev, struct queue_limits *lim, return -EINVAL; } + /* + * Before RAID adding folio support, the logical_block_size + * should be smaller than the page size. + */ + if (lim->logical_block_size > PAGE_SIZE) { + pr_err("%s: logical_block_size must not larger than PAGE_SIZE\n", + mdname(mddev)); + return -EINVAL; + } + mddev->logical_block_size = lim->logical_block_size; + return 0; } EXPORT_SYMBOL_GPL(mddev_stack_rdev_limits); @@ -6699,6 +6775,7 @@ static void md_clean(struct mddev *mddev) mddev->chunk_sectors = 0; mddev->ctime = mddev->utime = 0; mddev->layout = 0; + mddev->logical_block_size = 0; mddev->max_disks = 0; mddev->events = 0; mddev->can_decrease_events = 0; diff --git a/drivers/md/md.h b/drivers/md/md.h index fd6e001c1d38..6985f2829bbd 100644 --- a/drivers/md/md.h +++ b/drivers/md/md.h @@ -433,6 +433,7 @@ struct mddev { sector_t array_sectors; /* exported array size */ int external_size; /* size managed * externally */ + unsigned int logical_block_size; __u64 events; /* If the last 'event' was simply a clean->dirty transition, and * we didn't write it to the spares, then it is safe and simple diff --git a/drivers/md/raid0.c b/drivers/md/raid0.c index fbf763401521..47aee1b1d4d1 100644 --- a/drivers/md/raid0.c +++ b/drivers/md/raid0.c @@ -380,6 +380,7 @@ static int raid0_set_limits(struct mddev *mddev) lim.max_hw_sectors = mddev->chunk_sectors; lim.max_write_zeroes_sectors = mddev->chunk_sectors; lim.max_hw_wzeroes_unmap_sectors = mddev->chunk_sectors; + lim.logical_block_size = mddev->logical_block_size; lim.io_min = mddev->chunk_sectors << 9; lim.io_opt = lim.io_min * mddev->raid_disks; lim.chunk_sectors = mddev->chunk_sectors; diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c index 592a40233004..57d50465eed1 100644 --- a/drivers/md/raid1.c +++ b/drivers/md/raid1.c @@ -3213,6 +3213,7 @@ static int raid1_set_limits(struct mddev *mddev) md_init_stacking_limits(&lim); lim.max_write_zeroes_sectors = 0; lim.max_hw_wzeroes_unmap_sectors = 0; + lim.logical_block_size = mddev->logical_block_size; lim.features |= BLK_FEAT_ATOMIC_WRITES; err = mddev_stack_rdev_limits(mddev, &lim, MDDEV_STACK_INTEGRITY); if (err) diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c index 14dcd5142eb4..84be4cc7e873 100644 --- a/drivers/md/raid10.c +++ b/drivers/md/raid10.c @@ -4000,6 +4000,7 @@ static int raid10_set_queue_limits(struct mddev *mddev) md_init_stacking_limits(&lim); lim.max_write_zeroes_sectors = 0; lim.max_hw_wzeroes_unmap_sectors = 0; + lim.logical_block_size = mddev->logical_block_size; lim.io_min = mddev->chunk_sectors << 9; lim.chunk_sectors = mddev->chunk_sectors; lim.io_opt = lim.io_min * raid10_nr_stripes(conf); diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c index 24b32a0c95b4..cdbc7eba5c54 100644 --- a/drivers/md/raid5.c +++ b/drivers/md/raid5.c @@ -7745,6 +7745,7 @@ static int raid5_set_limits(struct mddev *mddev) stripe = roundup_pow_of_two(data_disks * (mddev->chunk_sectors << 9)); md_init_stacking_limits(&lim); + lim.logical_block_size = mddev->logical_block_size; lim.io_min = mddev->chunk_sectors << 9; lim.io_opt = lim.io_min * (conf->raid_disks - conf->max_degraded); lim.features |= BLK_FEAT_RAID_PARTIAL_STRIPES_EXPENSIVE; diff --git a/include/uapi/linux/raid/md_p.h b/include/uapi/linux/raid/md_p.h index ac74133a4768..310068bb2a1d 100644 --- a/include/uapi/linux/raid/md_p.h +++ b/include/uapi/linux/raid/md_p.h @@ -291,7 +291,8 @@ struct mdp_superblock_1 { __le64 resync_offset; /* data before this offset (from data_offset) known to be in sync */ __le32 sb_csum; /* checksum up to devs[max_dev] */ __le32 max_dev; /* size of devs[] array to consider */ - __u8 pad3[64-32]; /* set to 0 when writing */ + __le32 logical_block_size; /* same as q->limits->logical_block_size */ + __u8 pad3[64-36]; /* set to 0 when writing */ /* device state information. Indexed by dev_number. * 2 bytes per device -- cgit v1.2.3 From 0e535824d0bcf7c9bb0532d902283c31c78cd6f3 Mon Sep 17 00:00:00 2001 From: Saeed Mahameed Date: Fri, 7 Nov 2025 23:04:02 -0800 Subject: devlink: Introduce switchdev_inactive eswitch mode Adds DEVLINK_ESWITCH_MODE_SWITCHDEV_INACTIVE attribute to UAPI and documentation. Before having traffic flow through an eswitch, a user may want to have the ability to block traffic towards the FDB until FDB is fully programmed and the user is ready to send traffic to it. For example: when two eswitches are present for vports in a multi-PF setup, one eswitch may take over the traffic from the other when the user chooses. Before this take over, a user may want to first program the inactive eswitch and then once ready redirect traffic to this new eswitch. switchdev modes transition semantics: legacy->switchdev_inactive: Create switchdev mode normally, traffic not allowed to flow yet. switchdev_inactive->switchdev: Enable traffic to flow. switchdev->switchdev_inactive: Block traffic on the FDB, FDB and representros state and content is preserved. When eswitch is configured to this mode, traffic is ignored/dropped on this eswitch FDB, while current configuration is kept, e.g FDB rules and netdev representros are kept available, FDB programming is allowed. Example: # start inactive switchdev devlink dev eswitch set pci/0000:08:00.1 mode switchdev_inactive # setup TC rules, representors etc .. # activate devlink dev eswitch set pci/0000:08:00.1 mode switchdev Signed-off-by: Saeed Mahameed Reviewed-by: Jiri Pirko Link: https://patch.msgid.link/20251108070404.1551708-2-saeed@kernel.org Signed-off-by: Paolo Abeni --- Documentation/netlink/specs/devlink.yaml | 2 ++ Documentation/networking/devlink/devlink-eswitch-attr.rst | 13 +++++++++++++ include/uapi/linux/devlink.h | 1 + net/devlink/netlink_gen.c | 2 +- 4 files changed, 17 insertions(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/Documentation/netlink/specs/devlink.yaml b/Documentation/netlink/specs/devlink.yaml index 3db59c965869..426d5aa7d955 100644 --- a/Documentation/netlink/specs/devlink.yaml +++ b/Documentation/netlink/specs/devlink.yaml @@ -99,6 +99,8 @@ definitions: name: legacy - name: switchdev + - + name: switchdev-inactive - type: enum name: eswitch-inline-mode diff --git a/Documentation/networking/devlink/devlink-eswitch-attr.rst b/Documentation/networking/devlink/devlink-eswitch-attr.rst index 08bb39ab1528..eafe09abc40c 100644 --- a/Documentation/networking/devlink/devlink-eswitch-attr.rst +++ b/Documentation/networking/devlink/devlink-eswitch-attr.rst @@ -39,6 +39,10 @@ The following is a list of E-Switch attributes. rules. * ``switchdev`` allows for more advanced offloading capabilities of the E-Switch to hardware. + * ``switchdev_inactive`` switchdev mode but starts inactive, doesn't allow traffic + until explicitly activated. This mode is useful for orchestrators that + want to prepare the device in switchdev mode but only activate it when + all configurations are done. * - ``inline-mode`` - enum - Some HWs need the VF driver to put part of the packet @@ -74,3 +78,12 @@ Example Usage # enable encap-mode with legacy mode $ devlink dev eswitch set pci/0000:08:00.0 mode legacy inline-mode none encap-mode basic + + # start switchdev mode in inactive state + $ devlink dev eswitch set pci/0000:08:00.0 mode switchdev_inactive + + # setup switchdev configurations, representors, FDB entries, etc.. + ... + + # activate switchdev mode to allow traffic + $ devlink dev eswitch set pci/0000:08:00.0 mode switchdev diff --git a/include/uapi/linux/devlink.h b/include/uapi/linux/devlink.h index bcad11a787a5..157f11d3fb72 100644 --- a/include/uapi/linux/devlink.h +++ b/include/uapi/linux/devlink.h @@ -181,6 +181,7 @@ enum devlink_sb_threshold_type { enum devlink_eswitch_mode { DEVLINK_ESWITCH_MODE_LEGACY, DEVLINK_ESWITCH_MODE_SWITCHDEV, + DEVLINK_ESWITCH_MODE_SWITCHDEV_INACTIVE, }; enum devlink_eswitch_inline_mode { diff --git a/net/devlink/netlink_gen.c b/net/devlink/netlink_gen.c index 9fd00977d59e..5ad435aee29d 100644 --- a/net/devlink/netlink_gen.c +++ b/net/devlink/netlink_gen.c @@ -229,7 +229,7 @@ static const struct nla_policy devlink_eswitch_get_nl_policy[DEVLINK_ATTR_DEV_NA static const struct nla_policy devlink_eswitch_set_nl_policy[DEVLINK_ATTR_ESWITCH_ENCAP_MODE + 1] = { [DEVLINK_ATTR_BUS_NAME] = { .type = NLA_NUL_STRING, }, [DEVLINK_ATTR_DEV_NAME] = { .type = NLA_NUL_STRING, }, - [DEVLINK_ATTR_ESWITCH_MODE] = NLA_POLICY_MAX(NLA_U16, 1), + [DEVLINK_ATTR_ESWITCH_MODE] = NLA_POLICY_MAX(NLA_U16, 2), [DEVLINK_ATTR_ESWITCH_INLINE_MODE] = NLA_POLICY_MAX(NLA_U8, 3), [DEVLINK_ATTR_ESWITCH_ENCAP_MODE] = NLA_POLICY_MAX(NLA_U8, 1), }; -- cgit v1.2.3 From 1602bad16d7df82faca6d7c70821117684a66f49 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Tue, 11 Nov 2025 09:12:58 -0500 Subject: vfs: expose delegation support to userland Now that support for recallable directory delegations is available, expose this functionality to userland with new F_SETDELEG and F_GETDELEG commands for fcntl(). Note that this also allows userland to request a FL_DELEG type lease on files too. Userland applications that do will get signalled when there are metadata changes in addition to just data changes (which is a limitation of FL_LEASE leases). These commands accept a new "struct delegation" argument that contains a flags field for future expansion. Signed-off-by: Jeff Layton Link: https://patch.msgid.link/20251111-dir-deleg-ro-v6-17-52f3feebb2f2@kernel.org Reviewed-by: Jan Kara Signed-off-by: Christian Brauner --- fs/fcntl.c | 13 +++++++++++++ fs/locks.c | 45 ++++++++++++++++++++++++++++++++++++++++----- include/linux/filelock.h | 12 ++++++++++++ include/uapi/linux/fcntl.h | 11 +++++++++++ 4 files changed, 76 insertions(+), 5 deletions(-) (limited to 'include/uapi/linux') diff --git a/fs/fcntl.c b/fs/fcntl.c index 72f8433d9109..f93dbca08435 100644 --- a/fs/fcntl.c +++ b/fs/fcntl.c @@ -445,6 +445,7 @@ static long do_fcntl(int fd, unsigned int cmd, unsigned long arg, struct file *filp) { void __user *argp = (void __user *)arg; + struct delegation deleg; int argi = (int)arg; struct flock flock; long err = -EINVAL; @@ -550,6 +551,18 @@ static long do_fcntl(int fd, unsigned int cmd, unsigned long arg, case F_SET_RW_HINT: err = fcntl_set_rw_hint(filp, arg); break; + case F_GETDELEG: + if (copy_from_user(&deleg, argp, sizeof(deleg))) + return -EFAULT; + err = fcntl_getdeleg(filp, &deleg); + if (!err && copy_to_user(argp, &deleg, sizeof(deleg))) + return -EFAULT; + break; + case F_SETDELEG: + if (copy_from_user(&deleg, argp, sizeof(deleg))) + return -EFAULT; + err = fcntl_setdeleg(fd, filp, &deleg); + break; default: break; } diff --git a/fs/locks.c b/fs/locks.c index dd290a87f58e..7f4ccc7974bc 100644 --- a/fs/locks.c +++ b/fs/locks.c @@ -1703,7 +1703,7 @@ EXPORT_SYMBOL(lease_get_mtime); * XXX: sfr & willy disagree over whether F_INPROGRESS * should be returned to userspace. */ -int fcntl_getlease(struct file *filp) +static int __fcntl_getlease(struct file *filp, unsigned int flavor) { struct file_lease *fl; struct inode *inode = file_inode(filp); @@ -1719,7 +1719,8 @@ int fcntl_getlease(struct file *filp) list_for_each_entry(fl, &ctx->flc_lease, c.flc_list) { if (fl->c.flc_file != filp) continue; - type = target_leasetype(fl); + if (fl->c.flc_flags & flavor) + type = target_leasetype(fl); break; } spin_unlock(&ctx->flc_lock); @@ -1730,6 +1731,19 @@ int fcntl_getlease(struct file *filp) return type; } +int fcntl_getlease(struct file *filp) +{ + return __fcntl_getlease(filp, FL_LEASE); +} + +int fcntl_getdeleg(struct file *filp, struct delegation *deleg) +{ + if (deleg->d_flags != 0 || deleg->__pad != 0) + return -EINVAL; + deleg->d_type = __fcntl_getlease(filp, FL_DELEG); + return 0; +} + /** * check_conflicting_open - see if the given file points to an inode that has * an existing open that would conflict with the @@ -2039,13 +2053,13 @@ vfs_setlease(struct file *filp, int arg, struct file_lease **lease, void **priv) } EXPORT_SYMBOL_GPL(vfs_setlease); -static int do_fcntl_add_lease(unsigned int fd, struct file *filp, int arg) +static int do_fcntl_add_lease(unsigned int fd, struct file *filp, unsigned int flavor, int arg) { struct file_lease *fl; struct fasync_struct *new; int error; - fl = lease_alloc(filp, FL_LEASE, arg); + fl = lease_alloc(filp, flavor, arg); if (IS_ERR(fl)) return PTR_ERR(fl); @@ -2081,7 +2095,28 @@ int fcntl_setlease(unsigned int fd, struct file *filp, int arg) if (arg == F_UNLCK) return vfs_setlease(filp, F_UNLCK, NULL, (void **)&filp); - return do_fcntl_add_lease(fd, filp, arg); + return do_fcntl_add_lease(fd, filp, FL_LEASE, arg); +} + +/** + * fcntl_setdeleg - sets a delegation on an open file + * @fd: open file descriptor + * @filp: file pointer + * @deleg: delegation request from userland + * + * Call this fcntl to establish a delegation on the file. + * Note that you also need to call %F_SETSIG to + * receive a signal when the lease is broken. + */ +int fcntl_setdeleg(unsigned int fd, struct file *filp, struct delegation *deleg) +{ + /* For now, no flags are supported */ + if (deleg->d_flags != 0 || deleg->__pad != 0) + return -EINVAL; + + if (deleg->d_type == F_UNLCK) + return vfs_setlease(filp, F_UNLCK, NULL, (void **)&filp); + return do_fcntl_add_lease(fd, filp, FL_DELEG, deleg->d_type); } /** diff --git a/include/linux/filelock.h b/include/linux/filelock.h index 208d108df2d7..54b824c05299 100644 --- a/include/linux/filelock.h +++ b/include/linux/filelock.h @@ -159,6 +159,8 @@ int fcntl_setlk64(unsigned int, struct file *, unsigned int, int fcntl_setlease(unsigned int fd, struct file *filp, int arg); int fcntl_getlease(struct file *filp); +int fcntl_setdeleg(unsigned int fd, struct file *filp, struct delegation *deleg); +int fcntl_getdeleg(struct file *filp, struct delegation *deleg); static inline bool lock_is_unlock(struct file_lock *fl) { @@ -278,6 +280,16 @@ static inline int fcntl_getlease(struct file *filp) return F_UNLCK; } +static inline int fcntl_setdeleg(unsigned int fd, struct file *filp, struct delegation *deleg) +{ + return -EINVAL; +} + +static inline int fcntl_getdeleg(struct file *filp, struct delegation *deleg) +{ + return -EINVAL; +} + static inline bool lock_is_unlock(struct file_lock *fl) { return false; diff --git a/include/uapi/linux/fcntl.h b/include/uapi/linux/fcntl.h index 3741ea1b73d8..008fac15e573 100644 --- a/include/uapi/linux/fcntl.h +++ b/include/uapi/linux/fcntl.h @@ -79,6 +79,17 @@ */ #define RWF_WRITE_LIFE_NOT_SET RWH_WRITE_LIFE_NOT_SET +/* Set/Get delegations */ +#define F_GETDELEG (F_LINUX_SPECIFIC_BASE + 15) +#define F_SETDELEG (F_LINUX_SPECIFIC_BASE + 16) + +/* Argument structure for F_GETDELEG and F_SETDELEG */ +struct delegation { + uint32_t d_flags; /* Must be 0 */ + uint16_t d_type; /* F_RDLCK, F_WRLCK, F_UNLCK */ + uint16_t __pad; /* Must be 0 */ +}; + /* * Types of directory notifications that may be requested. */ -- cgit v1.2.3 From ad9c62bd8946621ed02ac94131a921222508a8bc Mon Sep 17 00:00:00 2001 From: Jiaqi Yan Date: Mon, 13 Oct 2025 18:59:01 +0000 Subject: KVM: arm64: VM exit to userspace to handle SEA When APEI fails to handle a stage-2 synchronous external abort (SEA), today KVM injects an asynchronous SError to the VCPU then resumes it, which usually results in unpleasant guest kernel panic. One major situation of guest SEA is when vCPU consumes recoverable uncorrected memory error (UER). Although SError and guest kernel panic effectively stops the propagation of corrupted memory, guest may re-use the corrupted memory if auto-rebooted; in worse case, guest boot may run into poisoned memory. So there is room to recover from an UER in a more graceful manner. Alternatively KVM can redirect the synchronous SEA event to VMM to - Reduce blast radius if possible. VMM can inject a SEA to VCPU via KVM's existing KVM_SET_VCPU_EVENTS API. If the memory poison consumption or fault is not from guest kernel, blast radius can be limited to the triggering thread in guest userspace, so VM can keep running. - Allow VMM to protect from future memory poison consumption by unmapping the page from stage-2, or to interrupt guest of the poisoned page so guest kernel can unmap it from stage-1 page table. - Allow VMM to track SEA events that VM customers care about, to restart VM when certain number of distinct poison events have happened, to provide observability to customers in log management UI. Introduce an userspace-visible feature to enable VMM handle SEA: - KVM_CAP_ARM_SEA_TO_USER. As the alternative fallback behavior when host APEI fails to claim a SEA, userspace can opt in this new capability to let KVM exit to userspace during SEA if it is not owned by host. - KVM_EXIT_ARM_SEA. A new exit reason is introduced for this. KVM fills kvm_run.arm_sea with as much as possible information about the SEA, enabling VMM to emulate SEA to guest by itself. - Sanitized ESR_EL2. The general rule is to keep only the bits useful for userspace and relevant to guest memory. - Flags indicating if faulting guest physical address is valid. - Faulting guest physical and virtual addresses if valid. Signed-off-by: Jiaqi Yan Co-developed-by: Oliver Upton Signed-off-by: Oliver Upton Link: https://msgid.link/20251013185903.1372553-2-jiaqiyan@google.com Signed-off-by: Oliver Upton --- arch/arm64/include/asm/kvm_host.h | 2 ++ arch/arm64/kvm/arm.c | 5 +++ arch/arm64/kvm/mmu.c | 68 ++++++++++++++++++++++++++++++++++++++- include/uapi/linux/kvm.h | 10 ++++++ 4 files changed, 84 insertions(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h index 64302c438355..366bf337ef64 100644 --- a/arch/arm64/include/asm/kvm_host.h +++ b/arch/arm64/include/asm/kvm_host.h @@ -350,6 +350,8 @@ struct kvm_arch { #define KVM_ARCH_FLAG_GUEST_HAS_SVE 9 /* MIDR_EL1, REVIDR_EL1, and AIDR_EL1 are writable from userspace */ #define KVM_ARCH_FLAG_WRITABLE_IMP_ID_REGS 10 + /* Unhandled SEAs are taken to userspace */ +#define KVM_ARCH_FLAG_EXIT_SEA 11 unsigned long flags; /* VM-wide vCPU feature set */ diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c index 870953b4a8a7..511d2e8ef6c7 100644 --- a/arch/arm64/kvm/arm.c +++ b/arch/arm64/kvm/arm.c @@ -132,6 +132,10 @@ int kvm_vm_ioctl_enable_cap(struct kvm *kvm, } mutex_unlock(&kvm->lock); break; + case KVM_CAP_ARM_SEA_TO_USER: + r = 0; + set_bit(KVM_ARCH_FLAG_EXIT_SEA, &kvm->arch.flags); + break; default: break; } @@ -327,6 +331,7 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext) case KVM_CAP_IRQFD_RESAMPLE: case KVM_CAP_COUNTER_OFFSET: case KVM_CAP_ARM_WRITABLE_IMP_ID_REGS: + case KVM_CAP_ARM_SEA_TO_USER: r = 1; break; case KVM_CAP_SET_GUEST_DEBUG2: diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c index 7cc964af8d30..58cb169727a6 100644 --- a/arch/arm64/kvm/mmu.c +++ b/arch/arm64/kvm/mmu.c @@ -1899,8 +1899,48 @@ static void handle_access_fault(struct kvm_vcpu *vcpu, phys_addr_t fault_ipa) read_unlock(&vcpu->kvm->mmu_lock); } +/* + * Returns true if the SEA should be handled locally within KVM if the abort + * is caused by a kernel memory allocation (e.g. stage-2 table memory). + */ +static bool host_owns_sea(struct kvm_vcpu *vcpu, u64 esr) +{ + /* + * Without FEAT_RAS HCR_EL2.TEA is RES0, meaning any external abort + * taken from a guest EL to EL2 is due to a host-imposed access (e.g. + * stage-2 PTW). + */ + if (!cpus_have_final_cap(ARM64_HAS_RAS_EXTN)) + return true; + + /* KVM owns the VNCR when the vCPU isn't in a nested context. */ + if (is_hyp_ctxt(vcpu) && !kvm_vcpu_trap_is_iabt(vcpu) && (esr & ESR_ELx_VNCR)) + return true; + + /* + * Determining if an external abort during a table walk happened at + * stage-2 is only possible with S1PTW is set. Otherwise, since KVM + * sets HCR_EL2.TEA, SEAs due to a stage-1 walk (i.e. accessing the + * PA of the stage-1 descriptor) can reach here and are reported + * with a TTW ESR value. + */ + return (esr_fsc_is_sea_ttw(esr) && (esr & ESR_ELx_S1PTW)); +} + int kvm_handle_guest_sea(struct kvm_vcpu *vcpu) { + struct kvm *kvm = vcpu->kvm; + struct kvm_run *run = vcpu->run; + u64 esr = kvm_vcpu_get_esr(vcpu); + u64 esr_mask = ESR_ELx_EC_MASK | + ESR_ELx_IL | + ESR_ELx_FnV | + ESR_ELx_EA | + ESR_ELx_CM | + ESR_ELx_WNR | + ESR_ELx_FSC; + u64 ipa; + /* * Give APEI the opportunity to claim the abort before handling it * within KVM. apei_claim_sea() expects to be called with IRQs enabled. @@ -1909,7 +1949,33 @@ int kvm_handle_guest_sea(struct kvm_vcpu *vcpu) if (apei_claim_sea(NULL) == 0) return 1; - return kvm_inject_serror(vcpu); + if (host_owns_sea(vcpu, esr) || + !test_bit(KVM_ARCH_FLAG_EXIT_SEA, &vcpu->kvm->arch.flags)) + return kvm_inject_serror(vcpu); + + /* ESR_ELx.SET is RES0 when FEAT_RAS isn't implemented. */ + if (kvm_has_ras(kvm)) + esr_mask |= ESR_ELx_SET_MASK; + + /* + * Exit to userspace, and provide faulting guest virtual and physical + * addresses in case userspace wants to emulate SEA to guest by + * writing to FAR_ELx and HPFAR_ELx registers. + */ + memset(&run->arm_sea, 0, sizeof(run->arm_sea)); + run->exit_reason = KVM_EXIT_ARM_SEA; + run->arm_sea.esr = esr & esr_mask; + + if (!(esr & ESR_ELx_FnV)) + run->arm_sea.gva = kvm_vcpu_get_hfar(vcpu); + + ipa = kvm_vcpu_get_fault_ipa(vcpu); + if (ipa != INVALID_GPA) { + run->arm_sea.flags |= KVM_EXIT_ARM_SEA_FLAG_GPA_VALID; + run->arm_sea.gpa = ipa; + } + + return 0; } /** diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h index 52f6000ab020..1e541193e98d 100644 --- a/include/uapi/linux/kvm.h +++ b/include/uapi/linux/kvm.h @@ -179,6 +179,7 @@ struct kvm_xen_exit { #define KVM_EXIT_LOONGARCH_IOCSR 38 #define KVM_EXIT_MEMORY_FAULT 39 #define KVM_EXIT_TDX 40 +#define KVM_EXIT_ARM_SEA 41 /* For KVM_EXIT_INTERNAL_ERROR */ /* Emulate instruction failed. */ @@ -473,6 +474,14 @@ struct kvm_run { } setup_event_notify; }; } tdx; + /* KVM_EXIT_ARM_SEA */ + struct { +#define KVM_EXIT_ARM_SEA_FLAG_GPA_VALID (1ULL << 0) + __u64 flags; + __u64 esr; + __u64 gva; + __u64 gpa; + } arm_sea; /* Fix the size of the union. */ char padding[256]; }; @@ -963,6 +972,7 @@ struct kvm_enable_cap { #define KVM_CAP_RISCV_MP_STATE_RESET 242 #define KVM_CAP_ARM_CACHEABLE_PFNMAP_SUPPORTED 243 #define KVM_CAP_GUEST_MEMFD_FLAGS 244 +#define KVM_CAP_ARM_SEA_TO_USER 245 struct kvm_irq_routing_irqchip { __u32 irqchip; -- cgit v1.2.3 From 2647e2ecc096d2330d6b6a34a3a1f0a99828c14c Mon Sep 17 00:00:00 2001 From: Pavel Begunkov Date: Thu, 13 Nov 2025 10:48:57 +0000 Subject: io_uring/query: introduce zcrx query Add a new query type IO_URING_QUERY_ZCRX returning the user some basic information about the interface, which includes allowed flags for areas and registration and supported IORING_REGISTER_ZCRX_CTRL subcodes. There is also a chicken-egg problem with user provided refill queue memory, where offsets and size information is returned after registration, but to properly allocate memory you need to know it beforehand, which is why the userspace currently has to guess the RQ headers size and severely overestimates it. Return the size information. It's split into "size" and "alignment" fields because for default placement modes the user is interested in the aligned size, however if it gets support for more flexible placement, it'll need to only know the actual header size. Signed-off-by: Pavel Begunkov Signed-off-by: Jens Axboe --- include/uapi/linux/io_uring/query.h | 16 ++++++++++++++++ io_uring/query.c | 19 +++++++++++++++++++ 2 files changed, 35 insertions(+) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/io_uring/query.h b/include/uapi/linux/io_uring/query.h index 3539ccbfd064..fc0cb1580e47 100644 --- a/include/uapi/linux/io_uring/query.h +++ b/include/uapi/linux/io_uring/query.h @@ -18,6 +18,7 @@ struct io_uring_query_hdr { enum { IO_URING_QUERY_OPCODES = 0, + IO_URING_QUERY_ZCRX = 1, __IO_URING_QUERY_MAX, }; @@ -41,4 +42,19 @@ struct io_uring_query_opcode { __u32 __pad; }; +struct io_uring_query_zcrx { + /* Bitmask of supported ZCRX_REG_* flags, */ + __u64 register_flags; + /* Bitmask of all supported IORING_ZCRX_AREA_* flags */ + __u64 area_flags; + /* The number of supported ZCRX_CTRL_* opcodes */ + __u32 nr_ctrl_opcodes; + __u32 __resv1; + /* The refill ring header size */ + __u32 rq_hdr_size; + /* The alignment for the header */ + __u32 rq_hdr_alignment; + __u64 __resv2; +}; + #endif diff --git a/io_uring/query.c b/io_uring/query.c index e1435cdc2665..6f9fa5153903 100644 --- a/io_uring/query.c +++ b/io_uring/query.c @@ -4,9 +4,11 @@ #include "query.h" #include "io_uring.h" +#include "zcrx.h" union io_query_data { struct io_uring_query_opcode opcodes; + struct io_uring_query_zcrx zcrx; }; #define IO_MAX_QUERY_SIZE sizeof(union io_query_data) @@ -27,6 +29,20 @@ static ssize_t io_query_ops(union io_query_data *data) return sizeof(*e); } +static ssize_t io_query_zcrx(union io_query_data *data) +{ + struct io_uring_query_zcrx *e = &data->zcrx; + + e->register_flags = ZCRX_REG_IMPORT; + e->area_flags = IORING_ZCRX_AREA_DMABUF; + e->nr_ctrl_opcodes = __ZCRX_CTRL_LAST; + e->rq_hdr_size = sizeof(struct io_uring); + e->rq_hdr_alignment = L1_CACHE_BYTES; + e->__resv1 = 0; + e->__resv2 = 0; + return sizeof(*e); +} + static int io_handle_query_entry(struct io_ring_ctx *ctx, union io_query_data *data, void __user *uhdr, u64 *next_entry) @@ -55,6 +71,9 @@ static int io_handle_query_entry(struct io_ring_ctx *ctx, case IO_URING_QUERY_OPCODES: ret = io_query_ops(data); break; + case IO_URING_QUERY_ZCRX: + ret = io_query_zcrx(data); + break; } if (ret >= 0) { -- cgit v1.2.3 From 4aaa9bc4d5921363490d95fe66c4db086a915799 Mon Sep 17 00:00:00 2001 From: Pavel Begunkov Date: Thu, 13 Nov 2025 10:48:58 +0000 Subject: io_uring/query: introduce rings info query Same problem as with zcrx in the previous patch, the user needs to know SQ/CQ header sizes to allocated memory before setup to use it for user provided rings, i.e. IORING_SETUP_NO_MMAP, however that information is only returned after registration, hence the user is guessing kernel implementation details. Return the header size and alignment, which is split with the same motivation, to allow the user to know the real structure size without alignment in case there will be more flexible placement schemes in the future. Signed-off-by: Pavel Begunkov Signed-off-by: Jens Axboe --- include/uapi/linux/io_uring/query.h | 8 ++++++++ io_uring/query.c | 13 +++++++++++++ 2 files changed, 21 insertions(+) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/io_uring/query.h b/include/uapi/linux/io_uring/query.h index fc0cb1580e47..2456e6c5ebb5 100644 --- a/include/uapi/linux/io_uring/query.h +++ b/include/uapi/linux/io_uring/query.h @@ -19,6 +19,7 @@ struct io_uring_query_hdr { enum { IO_URING_QUERY_OPCODES = 0, IO_URING_QUERY_ZCRX = 1, + IO_URING_QUERY_SCQ = 2, __IO_URING_QUERY_MAX, }; @@ -57,4 +58,11 @@ struct io_uring_query_zcrx { __u64 __resv2; }; +struct io_uring_query_scq { + /* The SQ/CQ rings header size */ + __u64 hdr_size; + /* The alignment for the header */ + __u64 hdr_alignment; +}; + #endif diff --git a/io_uring/query.c b/io_uring/query.c index 6f9fa5153903..e61b6221f87f 100644 --- a/io_uring/query.c +++ b/io_uring/query.c @@ -9,6 +9,7 @@ union io_query_data { struct io_uring_query_opcode opcodes; struct io_uring_query_zcrx zcrx; + struct io_uring_query_scq scq; }; #define IO_MAX_QUERY_SIZE sizeof(union io_query_data) @@ -43,6 +44,15 @@ static ssize_t io_query_zcrx(union io_query_data *data) return sizeof(*e); } +static ssize_t io_query_scq(union io_query_data *data) +{ + struct io_uring_query_scq *e = &data->scq; + + e->hdr_size = sizeof(struct io_rings); + e->hdr_alignment = SMP_CACHE_BYTES; + return sizeof(*e); +} + static int io_handle_query_entry(struct io_ring_ctx *ctx, union io_query_data *data, void __user *uhdr, u64 *next_entry) @@ -74,6 +84,9 @@ static int io_handle_query_entry(struct io_ring_ctx *ctx, case IO_URING_QUERY_ZCRX: ret = io_query_zcrx(data); break; + case IO_URING_QUERY_SCQ: + ret = io_query_scq(data); + break; } if (ret >= 0) { -- cgit v1.2.3 From d663976dad68de9b2e3df59cc31f0a24ee4c4511 Mon Sep 17 00:00:00 2001 From: Pavel Begunkov Date: Thu, 13 Nov 2025 10:46:12 +0000 Subject: io_uring/zcrx: introduce IORING_REGISTER_ZCRX_CTRL It'll be annoying and take enough of boilerplate code to implement new zcrx features as separate io_uring register opcode. Introduce IORING_REGISTER_ZCRX_CTRL that will multiplex such calls to zcrx. Note, there are no real users of the opcode in this patch. Signed-off-by: Pavel Begunkov Signed-off-by: Jens Axboe --- include/uapi/linux/io_uring.h | 13 +++++++++++++ io_uring/register.c | 3 +++ io_uring/zcrx.c | 21 +++++++++++++++++++++ io_uring/zcrx.h | 6 ++++++ 4 files changed, 43 insertions(+) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/io_uring.h b/include/uapi/linux/io_uring.h index e96080db3e4d..0e1d353fab1d 100644 --- a/include/uapi/linux/io_uring.h +++ b/include/uapi/linux/io_uring.h @@ -697,6 +697,9 @@ enum io_uring_register_op { /* query various aspects of io_uring, see linux/io_uring/query.h */ IORING_REGISTER_QUERY = 35, + /* auxiliary zcrx configuration, see enum zcrx_ctrl_op */ + IORING_REGISTER_ZCRX_CTRL = 36, + /* this goes last */ IORING_REGISTER_LAST, @@ -1078,6 +1081,16 @@ struct io_uring_zcrx_ifq_reg { __u64 __resv[3]; }; +enum zcrx_ctrl_op { + __ZCRX_CTRL_LAST, +}; + +struct zcrx_ctrl { + __u32 zcrx_id; + __u32 op; /* see enum zcrx_ctrl_op */ + __u64 __resv[8]; +}; + #ifdef __cplusplus } #endif diff --git a/io_uring/register.c b/io_uring/register.c index 334a457da3f7..fc66a5364483 100644 --- a/io_uring/register.c +++ b/io_uring/register.c @@ -815,6 +815,9 @@ static int __io_uring_register(struct io_ring_ctx *ctx, unsigned opcode, case IORING_REGISTER_QUERY: ret = io_query(ctx, arg, nr_args); break; + case IORING_REGISTER_ZCRX_CTRL: + ret = io_zcrx_ctrl(ctx, arg, nr_args); + break; default: ret = -EINVAL; break; diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c index 149bf9d5b983..0b5f4320c7a9 100644 --- a/io_uring/zcrx.c +++ b/io_uring/zcrx.c @@ -941,6 +941,27 @@ static const struct memory_provider_ops io_uring_pp_zc_ops = { .uninstall = io_pp_uninstall, }; +int io_zcrx_ctrl(struct io_ring_ctx *ctx, void __user *arg, unsigned nr_args) +{ + struct zcrx_ctrl ctrl; + struct io_zcrx_ifq *zcrx; + + if (nr_args) + return -EINVAL; + if (copy_from_user(&ctrl, arg, sizeof(ctrl))) + return -EFAULT; + if (!mem_is_zero(&ctrl.__resv, sizeof(ctrl.__resv))) + return -EFAULT; + + zcrx = xa_load(&ctx->zcrx_ctxs, ctrl.zcrx_id); + if (!zcrx) + return -ENXIO; + if (ctrl.op >= __ZCRX_CTRL_LAST) + return -EOPNOTSUPP; + + return -EINVAL; +} + static bool io_zcrx_queue_cqe(struct io_kiocb *req, struct net_iov *niov, struct io_zcrx_ifq *ifq, int off, int len) { diff --git a/io_uring/zcrx.h b/io_uring/zcrx.h index c9b9bfae0547..f29edc22c91f 100644 --- a/io_uring/zcrx.h +++ b/io_uring/zcrx.h @@ -65,6 +65,7 @@ struct io_zcrx_ifq { }; #if defined(CONFIG_IO_URING_ZCRX) +int io_zcrx_ctrl(struct io_ring_ctx *ctx, void __user *arg, unsigned nr_arg); int io_register_zcrx_ifq(struct io_ring_ctx *ctx, struct io_uring_zcrx_ifq_reg __user *arg); void io_unregister_zcrx_ifqs(struct io_ring_ctx *ctx); @@ -93,6 +94,11 @@ static inline struct io_mapped_region *io_zcrx_get_region(struct io_ring_ctx *ct { return NULL; } +static inline int io_zcrx_ctrl(struct io_ring_ctx *ctx, + void __user *arg, unsigned nr_arg) +{ + return -EOPNOTSUPP; +} #endif int io_recvzc(struct io_kiocb *req, unsigned int issue_flags); -- cgit v1.2.3 From 475eb39b00478b1898bc9080344dcd8e86c53c7a Mon Sep 17 00:00:00 2001 From: Pavel Begunkov Date: Thu, 13 Nov 2025 10:46:13 +0000 Subject: io_uring/zcrx: add sync refill queue flushing Add an zcrx interface via IORING_REGISTER_ZCRX_CTRL that forces the kernel to flush / consume entries from the refill queue. Just as with the IORING_REGISTER_ZCRX_REFILL attempt, the motivation is to address cases where the refill queue becomes full, and the user can't return buffers and needs to stash them. It's still a slow path, and the user should size refill queue appropriately, but it should be helpful for handling temporary traffic spikes and other unpredictable conditions. The interface is simpler comparing to ZCRX_REFILL as it doesn't need temporary refill entry arrays and gives natural batching, whereas ZCRX_REFILL requires even more user logic to be somewhat efficient. Also, add a structure for the operation. It's not currently used but can serve for future improvements like limiting the number of buffers to process, etc. Signed-off-by: Pavel Begunkov Signed-off-by: Jens Axboe --- include/uapi/linux/io_uring.h | 10 +++++- io_uring/zcrx.c | 74 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 80 insertions(+), 4 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/io_uring.h b/include/uapi/linux/io_uring.h index 0e1d353fab1d..db47fced2cc6 100644 --- a/include/uapi/linux/io_uring.h +++ b/include/uapi/linux/io_uring.h @@ -1082,13 +1082,21 @@ struct io_uring_zcrx_ifq_reg { }; enum zcrx_ctrl_op { + ZCRX_CTRL_FLUSH_RQ, + __ZCRX_CTRL_LAST, }; +struct zcrx_ctrl_flush_rq { + __u64 __resv[6]; +}; + struct zcrx_ctrl { __u32 zcrx_id; __u32 op; /* see enum zcrx_ctrl_op */ - __u64 __resv[8]; + __u64 __resv[2]; + + struct zcrx_ctrl_flush_rq zc_flush; }; #ifdef __cplusplus diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c index 0b5f4320c7a9..08c103af69bc 100644 --- a/io_uring/zcrx.c +++ b/io_uring/zcrx.c @@ -941,6 +941,71 @@ static const struct memory_provider_ops io_uring_pp_zc_ops = { .uninstall = io_pp_uninstall, }; +static unsigned zcrx_parse_rq(netmem_ref *netmem_array, unsigned nr, + struct io_zcrx_ifq *zcrx) +{ + unsigned int mask = zcrx->rq_entries - 1; + unsigned int i; + + guard(spinlock_bh)(&zcrx->rq_lock); + + nr = min(nr, io_zcrx_rqring_entries(zcrx)); + for (i = 0; i < nr; i++) { + struct io_uring_zcrx_rqe *rqe = io_zcrx_get_rqe(zcrx, mask); + struct net_iov *niov; + + if (!io_parse_rqe(rqe, zcrx, &niov)) + break; + netmem_array[i] = net_iov_to_netmem(niov); + } + + smp_store_release(&zcrx->rq_ring->head, zcrx->cached_rq_head); + return i; +} + +#define ZCRX_FLUSH_BATCH 32 + +static void zcrx_return_buffers(netmem_ref *netmems, unsigned nr) +{ + unsigned i; + + for (i = 0; i < nr; i++) { + netmem_ref netmem = netmems[i]; + struct net_iov *niov = netmem_to_net_iov(netmem); + + if (!io_zcrx_put_niov_uref(niov)) + continue; + if (!page_pool_unref_and_test(netmem)) + continue; + io_zcrx_return_niov(niov); + } +} + +static int zcrx_flush_rq(struct io_ring_ctx *ctx, struct io_zcrx_ifq *zcrx, + struct zcrx_ctrl *ctrl) +{ + struct zcrx_ctrl_flush_rq *frq = &ctrl->zc_flush; + netmem_ref netmems[ZCRX_FLUSH_BATCH]; + unsigned total = 0; + unsigned nr; + + if (!mem_is_zero(&frq->__resv, sizeof(frq->__resv))) + return -EINVAL; + + do { + nr = zcrx_parse_rq(netmems, ZCRX_FLUSH_BATCH, zcrx); + + zcrx_return_buffers(netmems, nr); + total += nr; + + if (fatal_signal_pending(current)) + break; + cond_resched(); + } while (nr == ZCRX_FLUSH_BATCH && total < zcrx->rq_entries); + + return 0; +} + int io_zcrx_ctrl(struct io_ring_ctx *ctx, void __user *arg, unsigned nr_args) { struct zcrx_ctrl ctrl; @@ -956,10 +1021,13 @@ int io_zcrx_ctrl(struct io_ring_ctx *ctx, void __user *arg, unsigned nr_args) zcrx = xa_load(&ctx->zcrx_ctxs, ctrl.zcrx_id); if (!zcrx) return -ENXIO; - if (ctrl.op >= __ZCRX_CTRL_LAST) - return -EOPNOTSUPP; - return -EINVAL; + switch (ctrl.op) { + case ZCRX_CTRL_FLUSH_RQ: + return zcrx_flush_rq(ctx, zcrx, &ctrl); + } + + return -EOPNOTSUPP; } static bool io_zcrx_queue_cqe(struct io_kiocb *req, struct net_iov *niov, -- cgit v1.2.3 From d7af80b213e5675664b14f12240cb282e81773d5 Mon Sep 17 00:00:00 2001 From: Pavel Begunkov Date: Thu, 13 Nov 2025 10:46:16 +0000 Subject: io_uring/zcrx: export zcrx via a file Add an option to wrap a zcrx instance into a file and expose it to the user space. Currently, users can't do anything meaningful with the file, but it'll be used in a next patch to import it into another io_uring instance. It's implemented as a new op called ZCRX_CTRL_EXPORT for the IORING_REGISTER_ZCRX_CTRL registration opcode. Signed-off-by: David Wei Signed-off-by: Pavel Begunkov Signed-off-by: Jens Axboe --- include/uapi/linux/io_uring.h | 11 ++++++- io_uring/zcrx.c | 68 +++++++++++++++++++++++++++++++++++++++---- 2 files changed, 72 insertions(+), 7 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/io_uring.h b/include/uapi/linux/io_uring.h index db47fced2cc6..4bedc0310a55 100644 --- a/include/uapi/linux/io_uring.h +++ b/include/uapi/linux/io_uring.h @@ -1083,6 +1083,7 @@ struct io_uring_zcrx_ifq_reg { enum zcrx_ctrl_op { ZCRX_CTRL_FLUSH_RQ, + ZCRX_CTRL_EXPORT, __ZCRX_CTRL_LAST, }; @@ -1091,12 +1092,20 @@ struct zcrx_ctrl_flush_rq { __u64 __resv[6]; }; +struct zcrx_ctrl_export { + __u32 zcrx_fd; + __u32 __resv1[11]; +}; + struct zcrx_ctrl { __u32 zcrx_id; __u32 op; /* see enum zcrx_ctrl_op */ __u64 __resv[2]; - struct zcrx_ctrl_flush_rq zc_flush; + union { + struct zcrx_ctrl_export zc_export; + struct zcrx_ctrl_flush_rq zc_flush; + }; }; #ifdef __cplusplus diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c index e60c5c00a611..815992aff246 100644 --- a/io_uring/zcrx.c +++ b/io_uring/zcrx.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -586,6 +587,15 @@ static void io_zcrx_scrub(struct io_zcrx_ifq *ifq) } } +static void zcrx_unregister(struct io_zcrx_ifq *ifq) +{ + if (refcount_dec_and_test(&ifq->user_refs)) { + io_close_queue(ifq); + io_zcrx_scrub(ifq); + } + io_put_zcrx_ifq(ifq); +} + struct io_mapped_region *io_zcrx_get_region(struct io_ring_ctx *ctx, unsigned int id) { @@ -596,6 +606,55 @@ struct io_mapped_region *io_zcrx_get_region(struct io_ring_ctx *ctx, return ifq ? &ifq->region : NULL; } +static int zcrx_box_release(struct inode *inode, struct file *file) +{ + struct io_zcrx_ifq *ifq = file->private_data; + + if (WARN_ON_ONCE(!ifq)) + return -EFAULT; + zcrx_unregister(ifq); + return 0; +} + +static const struct file_operations zcrx_box_fops = { + .owner = THIS_MODULE, + .release = zcrx_box_release, +}; + +static int zcrx_export(struct io_ring_ctx *ctx, struct io_zcrx_ifq *ifq, + struct zcrx_ctrl *ctrl, void __user *arg) +{ + struct zcrx_ctrl_export *ce = &ctrl->zc_export; + struct file *file; + int fd = -1; + + if (!mem_is_zero(ce, sizeof(*ce))) + return -EINVAL; + fd = get_unused_fd_flags(O_CLOEXEC); + if (fd < 0) + return fd; + + ce->zcrx_fd = fd; + if (copy_to_user(arg, ctrl, sizeof(*ctrl))) { + put_unused_fd(fd); + return -EFAULT; + } + + refcount_inc(&ifq->refs); + refcount_inc(&ifq->user_refs); + + file = anon_inode_create_getfile("[zcrx]", &zcrx_box_fops, + ifq, O_CLOEXEC, NULL); + if (IS_ERR(file)) { + put_unused_fd(fd); + zcrx_unregister(ifq); + return PTR_ERR(file); + } + + fd_install(fd, file); + return 0; +} + int io_register_zcrx_ifq(struct io_ring_ctx *ctx, struct io_uring_zcrx_ifq_reg __user *arg) { @@ -742,12 +801,7 @@ void io_unregister_zcrx_ifqs(struct io_ring_ctx *ctx) } if (!ifq) break; - - if (refcount_dec_and_test(&ifq->user_refs)) { - io_close_queue(ifq); - io_zcrx_scrub(ifq); - } - io_put_zcrx_ifq(ifq); + zcrx_unregister(ifq); } xa_destroy(&ctx->zcrx_ctxs); @@ -1028,6 +1082,8 @@ int io_zcrx_ctrl(struct io_ring_ctx *ctx, void __user *arg, unsigned nr_args) switch (ctrl.op) { case ZCRX_CTRL_FLUSH_RQ: return zcrx_flush_rq(ctx, zcrx, &ctrl); + case ZCRX_CTRL_EXPORT: + return zcrx_export(ctx, zcrx, &ctrl, arg); } return -EOPNOTSUPP; -- cgit v1.2.3 From 00d91481279fb2df8c46d19090578afd523ca630 Mon Sep 17 00:00:00 2001 From: David Wei Date: Thu, 13 Nov 2025 10:46:18 +0000 Subject: io_uring/zcrx: share an ifq between rings Add a way to share an ifq from a src ring that is real (i.e. bound to a HW RX queue) with other rings. This is done by passing a new flag IORING_ZCRX_IFQ_REG_IMPORT in the registration struct io_uring_zcrx_ifq_reg, alongside the fd of an exported zcrx ifq. Signed-off-by: David Wei Signed-off-by: Pavel Begunkov Signed-off-by: Jens Axboe --- include/uapi/linux/io_uring.h | 4 +++ io_uring/zcrx.c | 63 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 65 insertions(+), 2 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/io_uring.h b/include/uapi/linux/io_uring.h index 4bedc0310a55..deb772222b6d 100644 --- a/include/uapi/linux/io_uring.h +++ b/include/uapi/linux/io_uring.h @@ -1063,6 +1063,10 @@ struct io_uring_zcrx_area_reg { __u64 __resv2[2]; }; +enum zcrx_reg_flags { + ZCRX_REG_IMPORT = 1, +}; + /* * Argument for IORING_REGISTER_ZCRX_IFQ */ diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c index da7e556c349e..b99cf2c6670a 100644 --- a/io_uring/zcrx.c +++ b/io_uring/zcrx.c @@ -660,6 +660,63 @@ static int zcrx_export(struct io_ring_ctx *ctx, struct io_zcrx_ifq *ifq, return 0; } +static int import_zcrx(struct io_ring_ctx *ctx, + struct io_uring_zcrx_ifq_reg __user *arg, + struct io_uring_zcrx_ifq_reg *reg) +{ + struct io_zcrx_ifq *ifq; + struct file *file; + int fd, ret; + u32 id; + + if (!(ctx->flags & IORING_SETUP_DEFER_TASKRUN)) + return -EINVAL; + if (!(ctx->flags & (IORING_SETUP_CQE32|IORING_SETUP_CQE_MIXED))) + return -EINVAL; + if (reg->if_rxq || reg->rq_entries || reg->area_ptr || reg->region_ptr) + return -EINVAL; + + fd = reg->if_idx; + CLASS(fd, f)(fd); + if (fd_empty(f)) + return -EBADF; + + file = fd_file(f); + if (file->f_op != &zcrx_box_fops || !file->private_data) + return -EBADF; + + ifq = file->private_data; + refcount_inc(&ifq->refs); + refcount_inc(&ifq->user_refs); + + scoped_guard(mutex, &ctx->mmap_lock) { + ret = xa_alloc(&ctx->zcrx_ctxs, &id, NULL, xa_limit_31b, GFP_KERNEL); + if (ret) + goto err; + } + + reg->zcrx_id = id; + io_fill_zcrx_offsets(®->offsets); + if (copy_to_user(arg, reg, sizeof(*reg))) { + ret = -EFAULT; + goto err_xa_erase; + } + + scoped_guard(mutex, &ctx->mmap_lock) { + ret = -ENOMEM; + if (xa_store(&ctx->zcrx_ctxs, id, ifq, GFP_KERNEL)) + goto err_xa_erase; + } + + return 0; +err_xa_erase: + scoped_guard(mutex, &ctx->mmap_lock) + xa_erase(&ctx->zcrx_ctxs, id); +err: + zcrx_unregister(ifq); + return ret; +} + int io_register_zcrx_ifq(struct io_ring_ctx *ctx, struct io_uring_zcrx_ifq_reg __user *arg) { @@ -685,11 +742,13 @@ int io_register_zcrx_ifq(struct io_ring_ctx *ctx, return -EINVAL; if (copy_from_user(®, arg, sizeof(reg))) return -EFAULT; - if (copy_from_user(&rd, u64_to_user_ptr(reg.region_ptr), sizeof(rd))) - return -EFAULT; if (!mem_is_zero(®.__resv, sizeof(reg.__resv)) || reg.__resv2 || reg.zcrx_id) return -EINVAL; + if (reg.flags & ZCRX_REG_IMPORT) + return import_zcrx(ctx, arg, ®); + if (copy_from_user(&rd, u64_to_user_ptr(reg.region_ptr), sizeof(rd))) + return -EFAULT; if (reg.if_rxq == -1 || !reg.rq_entries || reg.flags) return -EINVAL; if (reg.rq_entries > IO_RQ_MAX_ENTRIES) { -- cgit v1.2.3 From e36dbd1cf3dfc4ce18e9f7a80183b53cae257e30 Mon Sep 17 00:00:00 2001 From: Jacopo Mondi Date: Fri, 4 Jul 2025 17:43:19 +0200 Subject: media: uapi: Introduce V4L2 generic ISP types Introduce v4l2-isp.h in the Linux kernel uAPI. The header includes types for generic ISP configuration parameters and will be extended in the future with support for generic ISP statistics formats. Generic ISP parameters support is provided by introducing two new types that represent an extensible and versioned buffer of ISP configuration parameters. The v4l2_params_buffer represents the container for the ISP configuration data block. The generic type is defined with a 0-sized data member that the ISP driver implementations shall properly size according to their capabilities. The v4l2_params_block_header structure represents the header to be prepend to each ISP configuration block. Signed-off-by: Daniel Scally Reviewed-by: Daniel Scally Reviewed-by: Laurent Pinchart Reviewed-by: Michael Riesch Acked-by: Sakari Ailus Tested-by: Lad Prabhakar Signed-off-by: Jacopo Mondi Signed-off-by: Hans Verkuil --- MAINTAINERS | 6 +++ include/uapi/linux/media/v4l2-isp.h | 102 ++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 include/uapi/linux/media/v4l2-isp.h (limited to 'include/uapi/linux') diff --git a/MAINTAINERS b/MAINTAINERS index 42b20f33f3bb..97c8a4cdbc2c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -26885,6 +26885,12 @@ F: drivers/media/i2c/vd55g1.c F: drivers/media/i2c/vd56g3.c F: drivers/media/i2c/vgxy61.c +V4L2 GENERIC ISP PARAMETERS AND STATISTIC FORMATS +M: Jacopo Mondi +L: linux-media@vger.kernel.org +S: Maintained +F: include/uapi/linux/media/v4l2-isp.h + VF610 NAND DRIVER M: Stefan Agner L: linux-mtd@lists.infradead.org diff --git a/include/uapi/linux/media/v4l2-isp.h b/include/uapi/linux/media/v4l2-isp.h new file mode 100644 index 000000000000..779168f9058e --- /dev/null +++ b/include/uapi/linux/media/v4l2-isp.h @@ -0,0 +1,102 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * Video4Linux2 generic ISP parameters and statistics support + * + * Copyright (C) 2025 Ideas On Board Oy + * Author: Jacopo Mondi + */ + +#ifndef _UAPI_V4L2_ISP_H_ +#define _UAPI_V4L2_ISP_H_ + +#include +#include + +/** + * enum v4l2_isp_params_version - V4L2 ISP parameters versioning + * + * @V4L2_ISP_PARAMS_VERSION_V0: First version of the V4L2 ISP parameters format + * (for compatibility) + * @V4L2_ISP_PARAMS_VERSION_V1: First version of the V4L2 ISP parameters format + * + * V0 and V1 are identical in order to support drivers compatible with the V4L2 + * ISP parameters format already upstreamed which use either 0 or 1 as their + * versioning identifier. Both V0 and V1 refers to the first version of the + * V4L2 ISP parameters format. + * + * Future revisions of the V4L2 ISP parameters format should start from the + * value of 2. + */ +enum v4l2_isp_params_version { + V4L2_ISP_PARAMS_VERSION_V0 = 0, + V4L2_ISP_PARAMS_VERSION_V1 +}; + +#define V4L2_ISP_PARAMS_FL_BLOCK_DISABLE (1U << 0) +#define V4L2_ISP_PARAMS_FL_BLOCK_ENABLE (1U << 1) + +/* + * Reserve the first 8 bits for V4L2_ISP_PARAMS_FL_* flag. + * + * Driver-specific flags should be defined as: + * #define DRIVER_SPECIFIC_FLAG0 ((1U << V4L2_ISP_PARAMS_FL_DRIVER_FLAGS(0)) + * #define DRIVER_SPECIFIC_FLAG1 ((1U << V4L2_ISP_PARAMS_FL_DRIVER_FLAGS(1)) + */ +#define V4L2_ISP_PARAMS_FL_DRIVER_FLAGS(n) ((n) + 8) + +/** + * struct v4l2_isp_params_block_header - V4L2 extensible parameters block header + * @type: The parameters block type (driver-specific) + * @flags: A bitmask of block flags (driver-specific) + * @size: Size (in bytes) of the parameters block, including this header + * + * This structure represents the common part of all the ISP configuration + * blocks. Each parameters block shall embed an instance of this structure type + * as its first member, followed by the block-specific configuration data. + * + * The @type field is an ISP driver-specific value that identifies the block + * type. The @size field specifies the size of the parameters block. + * + * The @flags field is a bitmask of per-block flags V4L2_PARAMS_ISP_FL_* and + * driver-specific flags specified by the driver header. + */ +struct v4l2_isp_params_block_header { + __u16 type; + __u16 flags; + __u32 size; +} __attribute__((aligned(8))); + +/** + * struct v4l2_isp_params_buffer - V4L2 extensible parameters configuration + * @version: The parameters buffer version (driver-specific) + * @data_size: The configuration data effective size, excluding this header + * @data: The configuration data + * + * This structure contains the configuration parameters of the ISP algorithms, + * serialized by userspace into a data buffer. Each configuration parameter + * block is represented by a block-specific structure which contains a + * :c:type:`v4l2_isp_params_block_header` entry as first member. Userspace + * populates the @data buffer with configuration parameters for the blocks that + * it intends to configure. As a consequence, the data buffer effective size + * changes according to the number of ISP blocks that userspace intends to + * configure and is set by userspace in the @data_size field. + * + * The parameters buffer is versioned by the @version field to allow modifying + * and extending its definition. Userspace shall populate the @version field to + * inform the driver about the version it intends to use. The driver will parse + * and handle the @data buffer according to the data layout specific to the + * indicated version and return an error if the desired version is not + * supported. + * + * For each ISP block that userspace wants to configure, a block-specific + * structure is appended to the @data buffer, one after the other without gaps + * in between. Userspace shall populate the @data_size field with the effective + * size, in bytes, of the @data buffer. + */ +struct v4l2_isp_params_buffer { + __u32 version; + __u32 data_size; + __u8 data[] __counted_by(data_size); +}; + +#endif /* _UAPI_V4L2_ISP_H_ */ -- cgit v1.2.3 From 1e8152db64bdee9f13e84e516c2b8a9bb10f025e Mon Sep 17 00:00:00 2001 From: Jacopo Mondi Date: Mon, 7 Jul 2025 14:13:53 +0200 Subject: media: uapi: Convert RkISP1 to V4L2 extensible params With the introduction of common types for extensible parameters format, convert the rkisp1-config.h header to use the new types. Factor out the documentation that is now part of the common header and only keep the driver-specific on in place. The conversion to use common types doesn't impact userspace as the new types are either identical to the ones already existing in the RkISP1 uAPI or are 1-to-1 type convertible. Reviewed-by: Daniel Scally Reviewed-by: Laurent Pinchart Reviewed-by: Michael Riesch Acked-by: Sakari Ailus Tested-by: Lad Prabhakar Signed-off-by: Jacopo Mondi Signed-off-by: Hans Verkuil --- include/uapi/linux/rkisp1-config.h | 107 +++++++++---------------------------- 1 file changed, 24 insertions(+), 83 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/rkisp1-config.h b/include/uapi/linux/rkisp1-config.h index 3b060ea6eed7..b2d2a71f7baf 100644 --- a/include/uapi/linux/rkisp1-config.h +++ b/include/uapi/linux/rkisp1-config.h @@ -7,8 +7,13 @@ #ifndef _UAPI_RKISP1_CONFIG_H #define _UAPI_RKISP1_CONFIG_H +#ifdef __KERNEL__ +#include +#endif /* __KERNEL__ */ #include +#include + /* Defect Pixel Cluster Detection */ #define RKISP1_CIF_ISP_MODULE_DPCC (1U << 0) /* Black Level Subtraction */ @@ -1158,79 +1163,26 @@ enum rkisp1_ext_params_block_type { RKISP1_EXT_PARAMS_BLOCK_TYPE_WDR, }; -#define RKISP1_EXT_PARAMS_FL_BLOCK_DISABLE (1U << 0) -#define RKISP1_EXT_PARAMS_FL_BLOCK_ENABLE (1U << 1) +/* For backward compatibility */ +#define RKISP1_EXT_PARAMS_FL_BLOCK_DISABLE V4L2_ISP_PARAMS_FL_BLOCK_DISABLE +#define RKISP1_EXT_PARAMS_FL_BLOCK_ENABLE V4L2_ISP_PARAMS_FL_BLOCK_ENABLE /* A bitmask of parameters blocks supported on the current hardware. */ #define RKISP1_CID_SUPPORTED_PARAMS_BLOCKS (V4L2_CID_USER_RKISP1_BASE + 0x01) /** - * struct rkisp1_ext_params_block_header - RkISP1 extensible parameters block - * header + * rkisp1_ext_params_block_header - RkISP1 extensible parameters block header * * This structure represents the common part of all the ISP configuration - * blocks. Each parameters block shall embed an instance of this structure type - * as its first member, followed by the block-specific configuration data. The - * driver inspects this common header to discern the block type and its size and - * properly handle the block content by casting it to the correct block-specific - * type. + * blocks and is identical to :c:type:`v4l2_isp_params_block_header`. * - * The @type field is one of the values enumerated by + * The type field is one of the values enumerated by * :c:type:`rkisp1_ext_params_block_type` and specifies how the data should be - * interpreted by the driver. The @size field specifies the size of the - * parameters block and is used by the driver for validation purposes. - * - * The @flags field is a bitmask of per-block flags RKISP1_EXT_PARAMS_FL_*. - * - * When userspace wants to configure and enable an ISP block it shall fully - * populate the block configuration and set the - * RKISP1_EXT_PARAMS_FL_BLOCK_ENABLE bit in the @flags field. - * - * When userspace simply wants to disable an ISP block the - * RKISP1_EXT_PARAMS_FL_BLOCK_DISABLE bit should be set in @flags field. The - * driver ignores the rest of the block configuration structure in this case. - * - * If a new configuration of an ISP block has to be applied userspace shall - * fully populate the ISP block configuration and omit setting the - * RKISP1_EXT_PARAMS_FL_BLOCK_ENABLE and RKISP1_EXT_PARAMS_FL_BLOCK_DISABLE bits - * in the @flags field. - * - * Setting both the RKISP1_EXT_PARAMS_FL_BLOCK_ENABLE and - * RKISP1_EXT_PARAMS_FL_BLOCK_DISABLE bits in the @flags field is not allowed - * and not accepted by the driver. - * - * Userspace is responsible for correctly populating the parameters block header - * fields (@type, @flags and @size) and the block-specific parameters. - * - * For example: + * interpreted by the driver. * - * .. code-block:: c - * - * void populate_bls(struct rkisp1_ext_params_block_header *block) { - * struct rkisp1_ext_params_bls_config *bls = - * (struct rkisp1_ext_params_bls_config *)block; - * - * bls->header.type = RKISP1_EXT_PARAMS_BLOCK_ID_BLS; - * bls->header.flags = RKISP1_EXT_PARAMS_FL_BLOCK_ENABLE; - * bls->header.size = sizeof(*bls); - * - * bls->config.enable_auto = 0; - * bls->config.fixed_val.r = blackLevelRed_; - * bls->config.fixed_val.gr = blackLevelGreenR_; - * bls->config.fixed_val.gb = blackLevelGreenB_; - * bls->config.fixed_val.b = blackLevelBlue_; - * } - * - * @type: The parameters block type, see - * :c:type:`rkisp1_ext_params_block_type` - * @flags: A bitmask of block flags - * @size: Size (in bytes) of the parameters block, including this header + * The flags field is a bitmask of per-block flags RKISP1_EXT_PARAMS_FL_*. */ -struct rkisp1_ext_params_block_header { - __u16 type; - __u16 flags; - __u32 size; -}; +#define rkisp1_ext_params_block_header v4l2_isp_params_block_header /** * struct rkisp1_ext_params_bls_config - RkISP1 extensible params BLS config @@ -1588,27 +1540,14 @@ struct rkisp1_ext_params_wdr_config { * @RKISP1_EXT_PARAM_BUFFER_V1: First version of RkISP1 extensible parameters */ enum rksip1_ext_param_buffer_version { - RKISP1_EXT_PARAM_BUFFER_V1 = 1, + RKISP1_EXT_PARAM_BUFFER_V1 = V4L2_ISP_PARAMS_VERSION_V1, }; /** * struct rkisp1_ext_params_cfg - RkISP1 extensible parameters configuration * - * This struct contains the configuration parameters of the RkISP1 ISP - * algorithms, serialized by userspace into a data buffer. Each configuration - * parameter block is represented by a block-specific structure which contains a - * :c:type:`rkisp1_ext_params_block_header` entry as first member. Userspace - * populates the @data buffer with configuration parameters for the blocks that - * it intends to configure. As a consequence, the data buffer effective size - * changes according to the number of ISP blocks that userspace intends to - * configure and is set by userspace in the @data_size field. - * - * The parameters buffer is versioned by the @version field to allow modifying - * and extending its definition. Userspace shall populate the @version field to - * inform the driver about the version it intends to use. The driver will parse - * and handle the @data buffer according to the data layout specific to the - * indicated version and return an error if the desired version is not - * supported. + * This is the driver-specific implementation of + * :c:type:`v4l2_isp_params_buffer`. * * Currently the single RKISP1_EXT_PARAM_BUFFER_V1 version is supported. * When a new format version will be added, a mechanism for userspace to query @@ -1624,11 +1563,6 @@ enum rksip1_ext_param_buffer_version { * the maximum value represents the blocks supported by the kernel driver, * independently of the device instance. * - * For each ISP block that userspace wants to configure, a block-specific - * structure is appended to the @data buffer, one after the other without gaps - * in between nor overlaps. Userspace shall populate the @data_size field with - * the effective size, in bytes, of the @data buffer. - * * The expected memory layout of the parameters buffer is:: * * +-------------------- struct rkisp1_ext_params_cfg -------------------+ @@ -1678,4 +1612,11 @@ struct rkisp1_ext_params_cfg { __u8 data[RKISP1_EXT_PARAMS_MAX_SIZE]; }; +#ifdef __KERNEL__ +/* Make sure the header is type-convertible to the generic v4l2 params one */ +static_assert((sizeof(struct rkisp1_ext_params_cfg) - + RKISP1_EXT_PARAMS_MAX_SIZE) == + sizeof(struct v4l2_isp_params_buffer)); +#endif /* __KERNEL__ */ + #endif /* _UAPI_RKISP1_CONFIG_H */ -- cgit v1.2.3 From 45662082855c6acd1719c11e077388cbccf3baf2 Mon Sep 17 00:00:00 2001 From: Jacopo Mondi Date: Mon, 7 Jul 2025 14:18:52 +0200 Subject: media: uapi: Convert Amlogic C3 to V4L2 extensible params With the introduction of common types for extensible parameters format, convert the c3-isp-config.h header to use the new types. Factor-out the documentation that is now part of the common header and only keep the driver-specific on in place. The conversion to use common types doesn't impact userspace as the new types are either identical to the ones already existing in the C3 ISP uAPI or are 1-to-1 type convertible. Reviewed-by: Daniel Scally Reviewed-by: Keke Li Reviewed-by: Laurent Pinchart Acked-by: Sakari Ailus Tested-by: Lad Prabhakar Signed-off-by: Jacopo Mondi Signed-off-by: Hans Verkuil --- include/uapi/linux/media/amlogic/c3-isp-config.h | 92 +++++++----------------- 1 file changed, 24 insertions(+), 68 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/media/amlogic/c3-isp-config.h b/include/uapi/linux/media/amlogic/c3-isp-config.h index ed085ea62a57..0a3c1cc55ccb 100644 --- a/include/uapi/linux/media/amlogic/c3-isp-config.h +++ b/include/uapi/linux/media/amlogic/c3-isp-config.h @@ -6,8 +6,13 @@ #ifndef _UAPI_C3_ISP_CONFIG_H_ #define _UAPI_C3_ISP_CONFIG_H_ +#ifdef __KERNEL__ +#include +#endif /* __KERNEL__ */ #include +#include + /* * Frames are split into zones of almost equal width and height - a zone is a * rectangular tile of a frame. The metering blocks within the ISP collect @@ -141,7 +146,7 @@ struct c3_isp_stats_info { * @C3_ISP_PARAMS_BUFFER_V0: First version of C3 ISP parameters block */ enum c3_isp_params_buffer_version { - C3_ISP_PARAMS_BUFFER_V0, + C3_ISP_PARAMS_BUFFER_V0 = V4L2_ISP_PARAMS_VERSION_V0, }; /** @@ -176,62 +181,23 @@ enum c3_isp_params_block_type { C3_ISP_PARAMS_BLOCK_SENTINEL }; -#define C3_ISP_PARAMS_BLOCK_FL_DISABLE (1U << 0) -#define C3_ISP_PARAMS_BLOCK_FL_ENABLE (1U << 1) +/* For backward compatibility */ +#define C3_ISP_PARAMS_BLOCK_FL_DISABLE V4L2_ISP_PARAMS_FL_BLOCK_DISABLE +#define C3_ISP_PARAMS_BLOCK_FL_ENABLE V4L2_ISP_PARAMS_FL_BLOCK_ENABLE /** * struct c3_isp_params_block_header - C3 ISP parameter block header * * This structure represents the common part of all the ISP configuration - * blocks. Each parameters block shall embed an instance of this structure type - * as its first member, followed by the block-specific configuration data. The - * driver inspects this common header to discern the block type and its size and - * properly handle the block content by casting it to the correct block-specific - * type. + * blocks and is identical to :c:type:`v4l2_isp_params_block_header`. * - * The @type field is one of the values enumerated by + * The type field is one of the values enumerated by * :c:type:`c3_isp_params_block_type` and specifies how the data should be - * interpreted by the driver. The @size field specifies the size of the - * parameters block and is used by the driver for validation purposes. The - * @flags field is a bitmask of per-block flags C3_ISP_PARAMS_FL*. - * - * When userspace wants to disable an ISP block the - * C3_ISP_PARAMS_BLOCK_FL_DISABLED bit should be set in the @flags field. In - * this case userspace may optionally omit the remainder of the configuration - * block, which will be ignored by the driver. - * - * When a new configuration of an ISP block needs to be applied userspace - * shall fully populate the ISP block and omit setting the - * C3_ISP_PARAMS_BLOCK_FL_DISABLED bit in the @flags field. - * - * Userspace is responsible for correctly populating the parameters block header - * fields (@type, @flags and @size) and the block-specific parameters. - * - * For example: - * - * .. code-block:: c + * interpreted by the driver. * - * void populate_pst_gamma(struct c3_isp_params_block_header *block) { - * struct c3_isp_params_pst_gamma *gamma = - * (struct c3_isp_params_pst_gamma *)block; - * - * gamma->header.type = C3_ISP_PARAMS_BLOCK_PST_GAMMA; - * gamma->header.flags = C3_ISP_PARAMS_BLOCK_FL_ENABLE; - * gamma->header.size = sizeof(*gamma); - * - * for (unsigned int i = 0; i < 129; i++) - * gamma->pst_gamma_lut[i] = i; - * } - * - * @type: The parameters block type from :c:type:`c3_isp_params_block_type` - * @flags: A bitmask of block flags - * @size: Size (in bytes) of the parameters block, including this header + * The flags field is a bitmask of per-block flags C3_ISP_PARAMS_FL_*. */ -struct c3_isp_params_block_header { - __u16 type; - __u16 flags; - __u32 size; -}; +#define c3_isp_params_block_header v4l2_isp_params_block_header /** * struct c3_isp_params_awb_gains - Gains for auto-white balance @@ -498,26 +464,10 @@ struct c3_isp_params_blc { /** * struct c3_isp_params_cfg - C3 ISP configuration parameters * - * This struct contains the configuration parameters of the C3 ISP - * algorithms, serialized by userspace into an opaque data buffer. Each - * configuration parameter block is represented by a block-specific structure - * which contains a :c:type:`c3_isp_param_block_header` entry as first - * member. Userspace populates the @data buffer with configuration parameters - * for the blocks that it intends to configure. As a consequence, the data - * buffer effective size changes according to the number of ISP blocks that - * userspace intends to configure. - * - * The parameters buffer is versioned by the @version field to allow modifying - * and extending its definition. Userspace should populate the @version field to - * inform the driver about the version it intends to use. The driver will parse - * and handle the @data buffer according to the data layout specific to the - * indicated revision and return an error if the desired revision is not - * supported. - * - * For each ISP block that userspace wants to configure, a block-specific - * structure is appended to the @data buffer, one after the other without gaps - * in between nor overlaps. Userspace shall populate the @total_size field with - * the effective size, in bytes, of the @data buffer. + * This is the driver-specific implementation of + * :c:type:`v4l2_isp_params_buffer`. + * + * Currently only C3_ISP_PARAM_BUFFER_V0 is supported. * * The expected memory layout of the parameters buffer is:: * @@ -561,4 +511,10 @@ struct c3_isp_params_cfg { __u8 data[C3_ISP_PARAMS_MAX_SIZE]; }; +#ifdef __KERNEL__ +/* Make sure the header is type-convertible to the generic v4l2 params one */ +static_assert((sizeof(struct c3_isp_params_cfg) - C3_ISP_PARAMS_MAX_SIZE) == + sizeof(struct v4l2_isp_params_buffer)); +#endif /* __KERNEL__ */ + #endif -- cgit v1.2.3 From ec4ac3cb7198070611987a6e91829fce0f4ce6d0 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Tue, 11 Nov 2025 16:15:45 +0000 Subject: media: uapi: Add MEDIA_BUS_FMT_RGB202020_1X60 format code The Mali-C55 ISP by ARM requires 20-bits per colour channel input on the bus. Add a new media bus format code to represent it. Reviewed-by: Lad Prabhakar Tested-by: Lad Prabhakar Reviewed-by: Laurent Pinchart Acked-by: Nayden Kanchev Co-developed-by: Jacopo Mondi Signed-off-by: Jacopo Mondi Signed-off-by: Daniel Scally Signed-off-by: Hans Verkuil --- .../userspace-api/media/v4l/subdev-formats.rst | 168 +++++++++++++++++++++ include/uapi/linux/media-bus-format.h | 3 +- 2 files changed, 170 insertions(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/Documentation/userspace-api/media/v4l/subdev-formats.rst b/Documentation/userspace-api/media/v4l/subdev-formats.rst index 1904390df830..894592e15a2b 100644 --- a/Documentation/userspace-api/media/v4l/subdev-formats.rst +++ b/Documentation/userspace-api/media/v4l/subdev-formats.rst @@ -2225,6 +2225,174 @@ The following table list existing packed 48bit wide RGB formats. \endgroup +The following table list existing packed 60bit wide RGB formats. + +.. tabularcolumns:: |p{4.0cm}|p{0.7cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}| + +.. _v4l2-mbus-pixelcode-rgb-60: + +.. raw:: latex + + \begingroup + \tiny + \setlength{\tabcolsep}{2pt} + +.. flat-table:: 60bit RGB formats + :header-rows: 3 + :stub-columns: 0 + :widths: 36 7 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + + * - Identifier + - Code + - + - :cspan:`31` Data organization + * - + - + - Bit + - + - + - + - + - 59 + - 58 + - 57 + - 56 + - 55 + - 54 + - 53 + - 52 + - 51 + - 50 + - 49 + - 48 + - 47 + - 46 + - 45 + - 44 + - 43 + - 42 + - 41 + - 40 + - 39 + - 38 + - 37 + - 36 + - 35 + - 34 + - 33 + - 32 + * - + - + - + - 31 + - 30 + - 29 + - 28 + - 27 + - 26 + - 25 + - 24 + - 23 + - 22 + - 21 + - 20 + - 19 + - 18 + - 17 + - 16 + - 15 + - 14 + - 13 + - 12 + - 11 + - 10 + - 9 + - 8 + - 7 + - 6 + - 5 + - 4 + - 3 + - 2 + - 1 + - 0 + * .. _MEDIA-BUS-FMT-RGB202020-1X60: + + - MEDIA_BUS_FMT_RGB202020_1X60 + - 0x1026 + - + - + - + - + - + - r\ :sub:`19` + - r\ :sub:`18` + - r\ :sub:`17` + - r\ :sub:`16` + - r\ :sub:`15` + - r\ :sub:`14` + - r\ :sub:`13` + - r\ :sub:`12` + - r\ :sub:`11` + - r\ :sub:`10` + - r\ :sub:`9` + - r\ :sub:`8` + - r\ :sub:`7` + - r\ :sub:`6` + - r\ :sub:`5` + - r\ :sub:`4` + - r\ :sub:`3` + - r\ :sub:`2` + - r\ :sub:`1` + - r\ :sub:`0` + - g\ :sub:`19` + - g\ :sub:`18` + - g\ :sub:`17` + - g\ :sub:`16` + - g\ :sub:`15` + - g\ :sub:`14` + - g\ :sub:`13` + - g\ :sub:`12` + * - + - + - + - g\ :sub:`11` + - g\ :sub:`10` + - g\ :sub:`9` + - g\ :sub:`8` + - g\ :sub:`7` + - g\ :sub:`6` + - g\ :sub:`5` + - g\ :sub:`4` + - g\ :sub:`3` + - g\ :sub:`2` + - g\ :sub:`1` + - g\ :sub:`0` + - b\ :sub:`19` + - b\ :sub:`18` + - b\ :sub:`17` + - b\ :sub:`16` + - b\ :sub:`15` + - b\ :sub:`14` + - b\ :sub:`13` + - b\ :sub:`12` + - b\ :sub:`11` + - b\ :sub:`10` + - b\ :sub:`9` + - b\ :sub:`8` + - b\ :sub:`7` + - b\ :sub:`6` + - b\ :sub:`5` + - b\ :sub:`4` + - b\ :sub:`3` + - b\ :sub:`2` + - b\ :sub:`1` + - b\ :sub:`0` + +.. raw:: latex + + \endgroup + On LVDS buses, usually each sample is transferred serialized in seven time slots per pixel clock, on three (18-bit) or four (24-bit) or five (30-bit) differential data pairs at the same time. The remaining bits are used diff --git a/include/uapi/linux/media-bus-format.h b/include/uapi/linux/media-bus-format.h index ff62056feed5..62ad82fd285a 100644 --- a/include/uapi/linux/media-bus-format.h +++ b/include/uapi/linux/media-bus-format.h @@ -34,7 +34,7 @@ #define MEDIA_BUS_FMT_FIXED 0x0001 -/* RGB - next is 0x1028 */ +/* RGB - next is 0x1029 */ #define MEDIA_BUS_FMT_RGB444_1X12 0x1016 #define MEDIA_BUS_FMT_RGB444_2X8_PADHI_BE 0x1001 #define MEDIA_BUS_FMT_RGB444_2X8_PADHI_LE 0x1002 @@ -74,6 +74,7 @@ #define MEDIA_BUS_FMT_RGB888_1X36_CPADLO 0x1021 #define MEDIA_BUS_FMT_RGB121212_1X36 0x1019 #define MEDIA_BUS_FMT_RGB161616_1X48 0x101a +#define MEDIA_BUS_FMT_RGB202020_1X60 0x1028 /* YUV (including grey) - next is 0x202f */ #define MEDIA_BUS_FMT_Y8_1X8 0x2001 -- cgit v1.2.3 From 2477ab037621632c3ec167187dc9e7afac2ba7f2 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Tue, 11 Nov 2025 16:15:46 +0000 Subject: media: uapi: Add 20-bit bayer formats The Mali-C55 requires input data be in 20-bit format, MSB aligned. Add some new media bus format macros to represent that input format. Reviewed-by: Lad Prabhakar Tested-by: Lad Prabhakar Reviewed-by: Laurent Pinchart Co-developed-by: Jacopo Mondi Signed-off-by: Jacopo Mondi Signed-off-by: Daniel Scally Signed-off-by: Hans Verkuil --- .../userspace-api/media/v4l/subdev-formats.rst | 252 ++++++++++++++++++++- include/uapi/linux/media-bus-format.h | 6 +- 2 files changed, 255 insertions(+), 3 deletions(-) (limited to 'include/uapi/linux') diff --git a/Documentation/userspace-api/media/v4l/subdev-formats.rst b/Documentation/userspace-api/media/v4l/subdev-formats.rst index 894592e15a2b..cf970750dd4c 100644 --- a/Documentation/userspace-api/media/v4l/subdev-formats.rst +++ b/Documentation/userspace-api/media/v4l/subdev-formats.rst @@ -2817,7 +2817,7 @@ organization is given as an example for the first pixel only. \tiny \setlength{\tabcolsep}{2pt} -.. tabularcolumns:: |p{6.0cm}|p{0.7cm}|p{0.3cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}| +.. tabularcolumns:: |p{6.0cm}|p{0.7cm}|p{0.3cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}|p{0.22cm}| .. _v4l2-mbus-pixelcode-bayer: @@ -2830,10 +2830,14 @@ organization is given as an example for the first pixel only. * - Identifier - Code - - - :cspan:`15` Data organization + - :cspan:`19` Data organization * - - - Bit + - 19 + - 18 + - 17 + - 16 - 15 - 14 - 13 @@ -2863,6 +2867,10 @@ organization is given as an example for the first pixel only. - - - + - + - + - + - - b\ :sub:`7` - b\ :sub:`6` - b\ :sub:`5` @@ -2884,6 +2892,10 @@ organization is given as an example for the first pixel only. - - - + - + - + - + - - g\ :sub:`7` - g\ :sub:`6` - g\ :sub:`5` @@ -2905,6 +2917,10 @@ organization is given as an example for the first pixel only. - - - + - + - + - + - - g\ :sub:`7` - g\ :sub:`6` - g\ :sub:`5` @@ -2926,6 +2942,10 @@ organization is given as an example for the first pixel only. - - - + - + - + - + - - r\ :sub:`7` - r\ :sub:`6` - r\ :sub:`5` @@ -2947,6 +2967,10 @@ organization is given as an example for the first pixel only. - - - + - + - + - + - - b\ :sub:`7` - b\ :sub:`6` - b\ :sub:`5` @@ -2968,6 +2992,10 @@ organization is given as an example for the first pixel only. - - - + - + - + - + - - g\ :sub:`7` - g\ :sub:`6` - g\ :sub:`5` @@ -2989,6 +3017,10 @@ organization is given as an example for the first pixel only. - - - + - + - + - + - - g\ :sub:`7` - g\ :sub:`6` - g\ :sub:`5` @@ -3010,6 +3042,10 @@ organization is given as an example for the first pixel only. - - - + - + - + - + - - r\ :sub:`7` - r\ :sub:`6` - r\ :sub:`5` @@ -3031,6 +3067,10 @@ organization is given as an example for the first pixel only. - - - + - + - + - + - - b\ :sub:`7` - b\ :sub:`6` - b\ :sub:`5` @@ -3052,6 +3092,10 @@ organization is given as an example for the first pixel only. - - - + - + - + - + - - g\ :sub:`7` - g\ :sub:`6` - g\ :sub:`5` @@ -3073,6 +3117,10 @@ organization is given as an example for the first pixel only. - - - + - + - + - + - - g\ :sub:`7` - g\ :sub:`6` - g\ :sub:`5` @@ -3094,6 +3142,10 @@ organization is given as an example for the first pixel only. - - - + - + - + - + - - r\ :sub:`7` - r\ :sub:`6` - r\ :sub:`5` @@ -3115,6 +3167,10 @@ organization is given as an example for the first pixel only. - - - + - + - + - + - - 0 - 0 - 0 @@ -3134,6 +3190,10 @@ organization is given as an example for the first pixel only. - - - + - + - + - + - - b\ :sub:`7` - b\ :sub:`6` - b\ :sub:`5` @@ -3155,6 +3215,10 @@ organization is given as an example for the first pixel only. - - - + - + - + - + - - b\ :sub:`7` - b\ :sub:`6` - b\ :sub:`5` @@ -3174,6 +3238,10 @@ organization is given as an example for the first pixel only. - - - + - + - + - + - - 0 - 0 - 0 @@ -3195,6 +3263,10 @@ organization is given as an example for the first pixel only. - - - + - + - + - + - - b\ :sub:`9` - b\ :sub:`8` - b\ :sub:`7` @@ -3214,6 +3286,10 @@ organization is given as an example for the first pixel only. - - - + - + - + - + - - b\ :sub:`1` - b\ :sub:`0` - 0 @@ -3235,6 +3311,10 @@ organization is given as an example for the first pixel only. - - - + - + - + - + - - b\ :sub:`1` - b\ :sub:`0` - 0 @@ -3254,6 +3334,10 @@ organization is given as an example for the first pixel only. - - - + - + - + - + - - b\ :sub:`9` - b\ :sub:`8` - b\ :sub:`7` @@ -3273,6 +3357,10 @@ organization is given as an example for the first pixel only. - - - + - + - + - + - - b\ :sub:`9` - b\ :sub:`8` - b\ :sub:`7` @@ -3294,6 +3382,10 @@ organization is given as an example for the first pixel only. - - - + - + - + - + - - g\ :sub:`9` - g\ :sub:`8` - g\ :sub:`7` @@ -3315,6 +3407,10 @@ organization is given as an example for the first pixel only. - - - + - + - + - + - - g\ :sub:`9` - g\ :sub:`8` - g\ :sub:`7` @@ -3336,6 +3432,10 @@ organization is given as an example for the first pixel only. - - - + - + - + - + - - r\ :sub:`9` - r\ :sub:`8` - r\ :sub:`7` @@ -3355,6 +3455,10 @@ organization is given as an example for the first pixel only. - - - + - + - + - + - - b\ :sub:`11` - b\ :sub:`10` - b\ :sub:`9` @@ -3376,6 +3480,10 @@ organization is given as an example for the first pixel only. - - - + - + - + - + - - g\ :sub:`11` - g\ :sub:`10` - g\ :sub:`9` @@ -3397,6 +3505,10 @@ organization is given as an example for the first pixel only. - - - + - + - + - + - - g\ :sub:`11` - g\ :sub:`10` - g\ :sub:`9` @@ -3418,6 +3530,10 @@ organization is given as an example for the first pixel only. - - - + - + - + - + - - r\ :sub:`11` - r\ :sub:`10` - r\ :sub:`9` @@ -3437,6 +3553,10 @@ organization is given as an example for the first pixel only. - - - + - + - + - + - - b\ :sub:`13` - b\ :sub:`12` - b\ :sub:`11` @@ -3458,6 +3578,10 @@ organization is given as an example for the first pixel only. - - - + - + - + - + - - g\ :sub:`13` - g\ :sub:`12` - g\ :sub:`11` @@ -3479,6 +3603,10 @@ organization is given as an example for the first pixel only. - - - + - + - + - + - - g\ :sub:`13` - g\ :sub:`12` - g\ :sub:`11` @@ -3500,6 +3628,10 @@ organization is given as an example for the first pixel only. - - - + - + - + - + - - r\ :sub:`13` - r\ :sub:`12` - r\ :sub:`11` @@ -3519,6 +3651,10 @@ organization is given as an example for the first pixel only. - MEDIA_BUS_FMT_SBGGR16_1X16 - 0x301d - + - + - + - + - - b\ :sub:`15` - b\ :sub:`14` - b\ :sub:`13` @@ -3540,6 +3676,10 @@ organization is given as an example for the first pixel only. - MEDIA_BUS_FMT_SGBRG16_1X16 - 0x301e - + - + - + - + - - g\ :sub:`15` - g\ :sub:`14` - g\ :sub:`13` @@ -3561,6 +3701,10 @@ organization is given as an example for the first pixel only. - MEDIA_BUS_FMT_SGRBG16_1X16 - 0x301f - + - + - + - + - - g\ :sub:`15` - g\ :sub:`14` - g\ :sub:`13` @@ -3582,6 +3726,110 @@ organization is given as an example for the first pixel only. - MEDIA_BUS_FMT_SRGGB16_1X16 - 0x3020 - + - + - + - + - + - r\ :sub:`15` + - r\ :sub:`14` + - r\ :sub:`13` + - r\ :sub:`12` + - r\ :sub:`11` + - r\ :sub:`10` + - r\ :sub:`9` + - r\ :sub:`8` + - r\ :sub:`7` + - r\ :sub:`6` + - r\ :sub:`5` + - r\ :sub:`4` + - r\ :sub:`3` + - r\ :sub:`2` + - r\ :sub:`1` + - r\ :sub:`0` + * .. _MEDIA-BUS-FMT-SBGGR20-1X20: + + - MEDIA_BUS_FMT_SBGGR20_1X20 + - 0x3021 + - + - b\ :sub:`19` + - b\ :sub:`18` + - b\ :sub:`17` + - b\ :sub:`16` + - b\ :sub:`15` + - b\ :sub:`14` + - b\ :sub:`13` + - b\ :sub:`12` + - b\ :sub:`11` + - b\ :sub:`10` + - b\ :sub:`9` + - b\ :sub:`8` + - b\ :sub:`7` + - b\ :sub:`6` + - b\ :sub:`5` + - b\ :sub:`4` + - b\ :sub:`3` + - b\ :sub:`2` + - b\ :sub:`1` + - b\ :sub:`0` + * .. _MEDIA-BUS-FMT-SGBRG20-1X20: + + - MEDIA_BUS_FMT_SGBRG20_1X20 + - 0x3022 + - + - g\ :sub:`19` + - g\ :sub:`18` + - g\ :sub:`17` + - g\ :sub:`16` + - g\ :sub:`15` + - g\ :sub:`14` + - g\ :sub:`13` + - g\ :sub:`12` + - g\ :sub:`11` + - g\ :sub:`10` + - g\ :sub:`9` + - g\ :sub:`8` + - g\ :sub:`7` + - g\ :sub:`6` + - g\ :sub:`5` + - g\ :sub:`4` + - g\ :sub:`3` + - g\ :sub:`2` + - g\ :sub:`1` + - g\ :sub:`0` + * .. _MEDIA-BUS-FMT-SGRBG20-1X20: + + - MEDIA_BUS_FMT_SGRBG20_1X20 + - 0x3023 + - + - g\ :sub:`19` + - g\ :sub:`18` + - g\ :sub:`17` + - g\ :sub:`16` + - g\ :sub:`15` + - g\ :sub:`14` + - g\ :sub:`13` + - g\ :sub:`12` + - g\ :sub:`11` + - g\ :sub:`10` + - g\ :sub:`9` + - g\ :sub:`8` + - g\ :sub:`7` + - g\ :sub:`6` + - g\ :sub:`5` + - g\ :sub:`4` + - g\ :sub:`3` + - g\ :sub:`2` + - g\ :sub:`1` + - g\ :sub:`0` + * .. _MEDIA-BUS-FMT-SRGGB20-1X20: + + - MEDIA_BUS_FMT_SRGGB20_1X20 + - 0x3024 + - + - r\ :sub:`19` + - r\ :sub:`18` + - r\ :sub:`17` + - r\ :sub:`16` - r\ :sub:`15` - r\ :sub:`14` - r\ :sub:`13` diff --git a/include/uapi/linux/media-bus-format.h b/include/uapi/linux/media-bus-format.h index 62ad82fd285a..6005f033e62c 100644 --- a/include/uapi/linux/media-bus-format.h +++ b/include/uapi/linux/media-bus-format.h @@ -124,7 +124,7 @@ #define MEDIA_BUS_FMT_YUV16_1X48 0x202a #define MEDIA_BUS_FMT_UYYVYY16_0_5X48 0x202b -/* Bayer - next is 0x3021 */ +/* Bayer - next is 0x3025 */ #define MEDIA_BUS_FMT_SBGGR8_1X8 0x3001 #define MEDIA_BUS_FMT_SGBRG8_1X8 0x3013 #define MEDIA_BUS_FMT_SGRBG8_1X8 0x3002 @@ -157,6 +157,10 @@ #define MEDIA_BUS_FMT_SGBRG16_1X16 0x301e #define MEDIA_BUS_FMT_SGRBG16_1X16 0x301f #define MEDIA_BUS_FMT_SRGGB16_1X16 0x3020 +#define MEDIA_BUS_FMT_SBGGR20_1X20 0x3021 +#define MEDIA_BUS_FMT_SGBRG20_1X20 0x3022 +#define MEDIA_BUS_FMT_SGRBG20_1X20 0x3023 +#define MEDIA_BUS_FMT_SRGGB20_1X20 0x3024 /* JPEG compressed formats - next is 0x4002 */ #define MEDIA_BUS_FMT_JPEG_1X8 0x4001 -- cgit v1.2.3 From 8d0bbed21ef737195277c0af8c30511fb72e608b Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Tue, 11 Nov 2025 16:15:48 +0000 Subject: media: uapi: Add controls for Mali-C55 ISP Add definitions and documentation for the custom control that will be needed by the Mali-C55 ISP driver. This will be a read only bitmask of the driver's capabilities, informing userspace of which blocks are fitted and which are absent. Tested-by: Lad Prabhakar Reviewed-by: Lad Prabhakar Reviewed-by: Jacopo Mondi Signed-off-by: Daniel Scally Signed-off-by: Jacopo Mondi Signed-off-by: Hans Verkuil --- .../userspace-api/media/drivers/index.rst | 1 + .../userspace-api/media/drivers/mali-c55.rst | 55 ++++++++++++++++++++++ include/uapi/linux/media/arm/mali-c55-config.h | 26 ++++++++++ include/uapi/linux/v4l2-controls.h | 6 +++ 4 files changed, 88 insertions(+) create mode 100644 Documentation/userspace-api/media/drivers/mali-c55.rst create mode 100644 include/uapi/linux/media/arm/mali-c55-config.h (limited to 'include/uapi/linux') diff --git a/Documentation/userspace-api/media/drivers/index.rst b/Documentation/userspace-api/media/drivers/index.rst index d706cb47b112..02967c9b18d6 100644 --- a/Documentation/userspace-api/media/drivers/index.rst +++ b/Documentation/userspace-api/media/drivers/index.rst @@ -32,6 +32,7 @@ For more details see the file COPYING in the source distribution of Linux. cx2341x-uapi dw100 imx-uapi + mali-c55 max2175 npcm-video omap3isp-uapi diff --git a/Documentation/userspace-api/media/drivers/mali-c55.rst b/Documentation/userspace-api/media/drivers/mali-c55.rst new file mode 100644 index 000000000000..21148b187856 --- /dev/null +++ b/Documentation/userspace-api/media/drivers/mali-c55.rst @@ -0,0 +1,55 @@ +.. SPDX-License-Identifier: GPL-2.0-only + +Arm Mali-C55 ISP driver +======================= + +The Arm Mali-C55 ISP driver implements a single driver-specific control: + +``V4L2_CID_MALI_C55_CAPABILITIES (bitmask)`` + Detail the capabilities of the ISP by giving detail about the fitted blocks. + + .. flat-table:: Bitmask meaning definitions + :header-rows: 1 + :widths: 2 4 8 + + * - Bit + - Macro + - Meaning + * - 0 + - MALI_C55_PONG + - Pong configuration space is fitted in the ISP + * - 1 + - MALI_C55_WDR + - WDR Framestitch, offset and gain is fitted in the ISP + * - 2 + - MALI_C55_COMPRESSION + - Temper compression is fitted in the ISP + * - 3 + - MALI_C55_TEMPER + - Temper is fitted in the ISP + * - 4 + - MALI_C55_SINTER_LITE + - Sinter Lite is fitted in the ISP instead of the full Sinter version + * - 5 + - MALI_C55_SINTER + - Sinter is fitted in the ISP + * - 6 + - MALI_C55_IRIDIX_LTM + - Iridix local tone mappine is fitted in the ISP + * - 7 + - MALI_C55_IRIDIX_GTM + - Iridix global tone mapping is fitted in the ISP + * - 8 + - MALI_C55_CNR + - Colour noise reduction is fitted in the ISP + * - 9 + - MALI_C55_FRSCALER + - The full resolution pipe scaler is fitted in the ISP + * - 10 + - MALI_C55_DS_PIPE + - The downscale pipe is fitted in the ISP + + The Mali-C55 ISP can be configured in a number of ways to include or exclude + blocks which may not be necessary. This control provides a way for the + driver to communicate to userspace which of the blocks are fitted in the + design. \ No newline at end of file diff --git a/include/uapi/linux/media/arm/mali-c55-config.h b/include/uapi/linux/media/arm/mali-c55-config.h new file mode 100644 index 000000000000..7fddece54ada --- /dev/null +++ b/include/uapi/linux/media/arm/mali-c55-config.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * ARM Mali-C55 ISP Driver - Userspace API + * + * Copyright (C) 2023 Ideas on Board Oy + */ + +#ifndef __UAPI_MALI_C55_CONFIG_H +#define __UAPI_MALI_C55_CONFIG_H + +#include + +#define V4L2_CID_MALI_C55_CAPABILITIES (V4L2_CID_USER_MALI_C55_BASE + 0x0) +#define MALI_C55_GPS_PONG (1U << 0) +#define MALI_C55_GPS_WDR (1U << 1) +#define MALI_C55_GPS_COMPRESSION (1U << 2) +#define MALI_C55_GPS_TEMPER (1U << 3) +#define MALI_C55_GPS_SINTER_LITE (1U << 4) +#define MALI_C55_GPS_SINTER (1U << 5) +#define MALI_C55_GPS_IRIDIX_LTM (1U << 6) +#define MALI_C55_GPS_IRIDIX_GTM (1U << 7) +#define MALI_C55_GPS_CNR (1U << 8) +#define MALI_C55_GPS_FRSCALER (1U << 9) +#define MALI_C55_GPS_DS_PIPE (1U << 10) + +#endif /* __UAPI_MALI_C55_CONFIG_H */ diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h index 2d30107e047e..f84ed133a6c9 100644 --- a/include/uapi/linux/v4l2-controls.h +++ b/include/uapi/linux/v4l2-controls.h @@ -228,6 +228,12 @@ enum v4l2_colorfx { */ #define V4L2_CID_USER_RKISP1_BASE (V4L2_CID_USER_BASE + 0x1220) +/* + * The base for the Arm Mali-C55 ISP driver controls. + * We reserve 16 controls for this driver + */ +#define V4L2_CID_USER_MALI_C55_BASE (V4L2_CID_USER_BASE + 0x1230) + /* MPEG-class control IDs */ /* The MPEG controls are applicable to all codec controls * and the 'MPEG' part of the define is historical */ -- cgit v1.2.3 From 4d36f732366aeb32bf3486545e597500a3bf0994 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Tue, 11 Nov 2025 16:15:52 +0000 Subject: media: Add MALI_C55_3A_STATS meta format Add a new meta format for the Mali-C55 ISP's 3A Statistics along with a new descriptor entry. Tested-by: Lad Prabhakar Reviewed-by: Laurent Pinchart Acked-by: Nayden Kanchev Co-developed-by: Jacopo Mondi Signed-off-by: Jacopo Mondi Signed-off-by: Daniel Scally Signed-off-by: Hans Verkuil --- drivers/media/v4l2-core/v4l2-ioctl.c | 1 + include/uapi/linux/videodev2.h | 3 +++ 2 files changed, 4 insertions(+) (limited to 'include/uapi/linux') diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c index 01cf52c3ea33..bfab29938b8f 100644 --- a/drivers/media/v4l2-core/v4l2-ioctl.c +++ b/drivers/media/v4l2-core/v4l2-ioctl.c @@ -1469,6 +1469,7 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt) case V4L2_META_FMT_RK_ISP1_EXT_PARAMS: descr = "Rockchip ISP1 Ext 3A Params"; break; case V4L2_META_FMT_C3ISP_PARAMS: descr = "Amlogic C3 ISP Parameters"; break; case V4L2_META_FMT_C3ISP_STATS: descr = "Amlogic C3 ISP Statistics"; break; + case V4L2_META_FMT_MALI_C55_STATS: descr = "ARM Mali-C55 ISP 3A Statistics"; break; case V4L2_PIX_FMT_NV12_8L128: descr = "NV12 (8x128 Linear)"; break; case V4L2_PIX_FMT_NV12M_8L128: descr = "NV12M (8x128 Linear)"; break; case V4L2_PIX_FMT_NV12_10BE_8L128: descr = "10-bit NV12 (8x128 Linear, BE)"; break; diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h index becd08fdbddb..cba4b1311667 100644 --- a/include/uapi/linux/videodev2.h +++ b/include/uapi/linux/videodev2.h @@ -884,6 +884,9 @@ struct v4l2_pix_format { #define V4L2_META_FMT_RPI_FE_CFG v4l2_fourcc('R', 'P', 'F', 'C') /* PiSP FE configuration */ #define V4L2_META_FMT_RPI_FE_STATS v4l2_fourcc('R', 'P', 'F', 'S') /* PiSP FE stats */ +/* Vendor specific - used for Arm Mali-C55 ISP */ +#define V4L2_META_FMT_MALI_C55_STATS v4l2_fourcc('C', '5', '5', 'S') /* ARM Mali-C55 3A Statistics */ + #ifdef __KERNEL__ /* * Line-based metadata formats. Remember to update v4l_fill_fmtdesc() when -- cgit v1.2.3 From c7f832f6f8129bb666346cb4805805ad056059b7 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Tue, 11 Nov 2025 16:15:53 +0000 Subject: media: uapi: Add 3a stats buffer for mali-c55 Describe the format of the 3A statistics buffers in the userspace API header for the mali-c55 ISP. Tested-by: Lad Prabhakar Reviewed-by: Laurent Pinchart Acked-by: Nayden Kanchev Co-developed-by: Jacopo Mondi Signed-off-by: Jacopo Mondi Signed-off-by: Daniel Scally Signed-off-by: Hans Verkuil --- MAINTAINERS | 1 + include/uapi/linux/media/arm/mali-c55-config.h | 170 +++++++++++++++++++++++++ 2 files changed, 171 insertions(+) (limited to 'include/uapi/linux') diff --git a/MAINTAINERS b/MAINTAINERS index dc3719cb6120..193580ceb9f4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2116,6 +2116,7 @@ F: Documentation/admin-guide/media/mali-c55.rst F: Documentation/devicetree/bindings/media/arm,mali-c55.yaml F: Documentation/userspace-api/media/drivers/mali-c55.rst F: drivers/media/platform/arm/mali-c55/ +F: include/uapi/linux/media/arm/mali-c55-config.h ARM MALI PANTHOR DRM DRIVER M: Boris Brezillon diff --git a/include/uapi/linux/media/arm/mali-c55-config.h b/include/uapi/linux/media/arm/mali-c55-config.h index 7fddece54ada..e31fb8ffa10a 100644 --- a/include/uapi/linux/media/arm/mali-c55-config.h +++ b/include/uapi/linux/media/arm/mali-c55-config.h @@ -8,6 +8,7 @@ #ifndef __UAPI_MALI_C55_CONFIG_H #define __UAPI_MALI_C55_CONFIG_H +#include #include #define V4L2_CID_MALI_C55_CAPABILITIES (V4L2_CID_USER_MALI_C55_BASE + 0x0) @@ -23,4 +24,173 @@ #define MALI_C55_GPS_FRSCALER (1U << 9) #define MALI_C55_GPS_DS_PIPE (1U << 10) +/* + * Frames are split into zones of almost equal width and height - a zone is a + * rectangular tile of a frame. The metering blocks within the ISP collect + * aggregated statistics per zone. A maximum of 15x15 zones can be configured, + * and so the statistics buffer within the hardware is sized to accommodate + * that. + * + * The utilised number of zones is runtime configurable. + */ +#define MALI_C55_MAX_ZONES (15 * 15) + +/** + * struct mali_c55_ae_1024bin_hist - Auto Exposure 1024-bin histogram statistics + * + * @bins: 1024 element array of 16-bit pixel counts. + * + * The 1024-bin histogram module collects image-global but zone-weighted + * intensity distributions of pixels in fixed-width bins. The modules can be + * configured into different "plane modes" which affect the contents of the + * collected statistics. In plane mode 0, pixel intensities are taken regardless + * of colour plane into a single 1024-bin histogram with a bin width of 4. In + * plane mode 1, four 256-bin histograms with a bin width of 16 are collected - + * one for each CFA colour plane. In plane modes 4, 5, 6 and 7 two 512-bin + * histograms with a bin width of 8 are collected - in each mode one of the + * colour planes is collected into the first histogram and all the others are + * combined into the second. The histograms are stored consecutively in the bins + * array. + * + * The 16-bit pixel counts are stored as a 4-bit exponent in the most + * significant bits followed by a 12-bit mantissa. Conversion to a usable + * format can be done according to the following pseudo-code:: + * + * if (e == 0) { + * bin = m * 2; + * } else { + * bin = (m + 4096) * 2^e + * } + * + * where + * e is the exponent value in range 0..15 + * m is the mantissa value in range 0..4095 + * + * The pixels used in calculating the statistics can be masked using three + * methods: + * + * 1. Pixels can be skipped in X and Y directions independently. + * 2. Minimum/Maximum intensities can be configured + * 3. Zones can be differentially weighted, including 0 weighted to mask them + * + * The data for this histogram can be collected from different tap points in the + * ISP depending on configuration - after the white balance or digital gain + * blocks, or immediately after the input crossbar. + */ +struct mali_c55_ae_1024bin_hist { + __u16 bins[1024]; +} __attribute__((packed)); + +/** + * struct mali_c55_ae_5bin_hist - Auto Exposure 5-bin histogram statistics + * + * @hist0: 16-bit normalised pixel count for the 0th intensity bin + * @hist1: 16-bit normalised pixel count for the 1st intensity bin + * @hist3: 16-bit normalised pixel count for the 3rd intensity bin + * @hist4: 16-bit normalised pixel count for the 4th intensity bin + * + * The ISP generates a 5-bin histogram of normalised pixel counts within bins of + * pixel intensity for each of 225 possible zones within a frame. The centre bin + * of the histogram for each zone is not available from the hardware and must be + * calculated by subtracting the values of hist0, hist1, hist3 and hist4 from + * 0xffff as in the following equation: + * + * hist2 = 0xffff - (hist0 + hist1 + hist3 + hist4) + */ +struct mali_c55_ae_5bin_hist { + __u16 hist0; + __u16 hist1; + __u16 hist3; + __u16 hist4; +} __attribute__((packed)); + +/** + * struct mali_c55_awb_average_ratios - Auto White Balance colour ratios + * + * @avg_rg_gr: Average R/G or G/R ratio in Q4.8 format. + * @avg_bg_br: Average B/G or B/R ratio in Q4.8 format. + * @num_pixels: The number of pixels used in the AWB calculation + * + * The ISP calculates and collects average colour ratios for each zone in an + * image and stores them in Q4.8 format (the lowest 8 bits are fractional, with + * bits [11:8] representing the integer). The exact ratios collected (either + * R/G, B/G or G/R, B/R) are configurable through the parameters buffer. The + * value of the 4 high bits is undefined. + */ +struct mali_c55_awb_average_ratios { + __u16 avg_rg_gr; + __u16 avg_bg_br; + __u32 num_pixels; +} __attribute__((packed)); + +/** + * struct mali_c55_af_statistics - Auto Focus edge and intensity statistics + * + * @intensity_stats: Packed mantissa and exponent value for pixel intensity + * @edge_stats: Packed mantissa and exponent values for edge intensity + * + * The ISP collects the squared sum of pixel intensities for each zone within a + * configurable Region of Interest on the frame. Additionally, the same data are + * collected after being passed through a bandpass filter which removes high and + * low frequency components - these are referred to as the edge statistics. + * + * The intensity and edge statistics for a zone can be used to calculate the + * contrast information for a zone + * + * C = E2 / I2 + * + * Where I2 is the intensity statistic for a zone and E2 is the edge statistic + * for that zone. Optimum focus is reached when C is at its maximum. + * + * The intensity and edge statistics are stored packed into a non-standard 16 + * bit floating point format, where the 7 most significant bits represent the + * exponent and the 9 least significant bits the mantissa. This format can be + * unpacked with the following pseudocode:: + * + * if (e == 0) { + * x = m; + * } else { + * x = 2^e-1 * (m + 2^9) + * } + * + * where + * e is the exponent value in range 0..127 + * m is the mantissa value in range 0..511 + */ +struct mali_c55_af_statistics { + __u16 intensity_stats; + __u16 edge_stats; +} __attribute__((packed)); + +/** + * struct mali_c55_stats_buffer - 3A statistics for the mali-c55 ISP + * + * @ae_1024bin_hist: 1024-bin frame-global pixel intensity histogram + * @iridix_1024bin_hist: Post-Iridix block 1024-bin histogram + * @ae_5bin_hists: 5-bin pixel intensity histograms for AEC + * @reserved1: Undefined buffer space + * @awb_ratios: Color balance ratios for Auto White Balance + * @reserved2: Undefined buffer space + * @af_statistics: Pixel intensity statistics for Auto Focus + * @reserved3: Undefined buffer space + * + * This struct describes the metering statistics space in the Mali-C55 ISP's + * hardware in its entirety. The space between each defined area is marked as + * "unknown" and may not be 0, but should not be used. The @ae_5bin_hists, + * @awb_ratios and @af_statistics members are arrays of statistics per-zone. + * The zones are arranged in the array in raster order starting from the top + * left corner of the image. + */ + +struct mali_c55_stats_buffer { + struct mali_c55_ae_1024bin_hist ae_1024bin_hist; + struct mali_c55_ae_1024bin_hist iridix_1024bin_hist; + struct mali_c55_ae_5bin_hist ae_5bin_hists[MALI_C55_MAX_ZONES]; + __u32 reserved1[14]; + struct mali_c55_awb_average_ratios awb_ratios[MALI_C55_MAX_ZONES]; + __u32 reserved2[14]; + struct mali_c55_af_statistics af_statistics[MALI_C55_MAX_ZONES]; + __u32 reserved3[15]; +} __attribute__((packed)); + #endif /* __UAPI_MALI_C55_CONFIG_H */ -- cgit v1.2.3 From 1ab3cb233d61131b2d02650f8ed9e4e077fd4508 Mon Sep 17 00:00:00 2001 From: Jacopo Mondi Date: Tue, 11 Nov 2025 16:15:56 +0000 Subject: media: mali-c55: Add image formats for Mali-C55 parameters buffer Add a new V4L2 meta format code for the Mali-C55 parameters. Tested-by: Lad Prabhakar Reviewed-by: Laurent Pinchart Acked-by: Nayden Kanchev Signed-off-by: Jacopo Mondi Signed-off-by: Daniel Scally Signed-off-by: Hans Verkuil --- drivers/media/v4l2-core/v4l2-ioctl.c | 1 + include/uapi/linux/videodev2.h | 1 + 2 files changed, 2 insertions(+) (limited to 'include/uapi/linux') diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c index bfab29938b8f..98512ea4cc5b 100644 --- a/drivers/media/v4l2-core/v4l2-ioctl.c +++ b/drivers/media/v4l2-core/v4l2-ioctl.c @@ -1469,6 +1469,7 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt) case V4L2_META_FMT_RK_ISP1_EXT_PARAMS: descr = "Rockchip ISP1 Ext 3A Params"; break; case V4L2_META_FMT_C3ISP_PARAMS: descr = "Amlogic C3 ISP Parameters"; break; case V4L2_META_FMT_C3ISP_STATS: descr = "Amlogic C3 ISP Statistics"; break; + case V4L2_META_FMT_MALI_C55_PARAMS: descr = "ARM Mali-C55 ISP Parameters"; break; case V4L2_META_FMT_MALI_C55_STATS: descr = "ARM Mali-C55 ISP 3A Statistics"; break; case V4L2_PIX_FMT_NV12_8L128: descr = "NV12 (8x128 Linear)"; break; case V4L2_PIX_FMT_NV12M_8L128: descr = "NV12M (8x128 Linear)"; break; diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h index cba4b1311667..add08188f068 100644 --- a/include/uapi/linux/videodev2.h +++ b/include/uapi/linux/videodev2.h @@ -885,6 +885,7 @@ struct v4l2_pix_format { #define V4L2_META_FMT_RPI_FE_STATS v4l2_fourcc('R', 'P', 'F', 'S') /* PiSP FE stats */ /* Vendor specific - used for Arm Mali-C55 ISP */ +#define V4L2_META_FMT_MALI_C55_PARAMS v4l2_fourcc('C', '5', '5', 'P') /* ARM Mali-C55 Parameters */ #define V4L2_META_FMT_MALI_C55_STATS v4l2_fourcc('C', '5', '5', 'S') /* ARM Mali-C55 3A Statistics */ #ifdef __KERNEL__ -- cgit v1.2.3 From 08a99369f44eeb63eacc56fe42f4c67a6c7dbc37 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Tue, 11 Nov 2025 16:15:57 +0000 Subject: media: uapi: Add parameters structs to mali-c55-config.h Add structures describing the ISP parameters to mali-c55-config.h Tested-by: Lad Prabhakar Acked-by: Nayden Kanchev Co-developed-by: Jacopo Mondi Signed-off-by: Jacopo Mondi Signed-off-by: Daniel Scally Signed-off-by: Hans Verkuil --- include/uapi/linux/media/arm/mali-c55-config.h | 598 +++++++++++++++++++++++++ 1 file changed, 598 insertions(+) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/media/arm/mali-c55-config.h b/include/uapi/linux/media/arm/mali-c55-config.h index e31fb8ffa10a..109082c5694f 100644 --- a/include/uapi/linux/media/arm/mali-c55-config.h +++ b/include/uapi/linux/media/arm/mali-c55-config.h @@ -10,6 +10,7 @@ #include #include +#include #define V4L2_CID_MALI_C55_CAPABILITIES (V4L2_CID_USER_MALI_C55_BASE + 0x0) #define MALI_C55_GPS_PONG (1U << 0) @@ -193,4 +194,601 @@ struct mali_c55_stats_buffer { __u32 reserved3[15]; } __attribute__((packed)); +/** + * enum mali_c55_param_buffer_version - Mali-C55 parameters block versioning + * + * @MALI_C55_PARAM_BUFFER_V1: First version of Mali-C55 parameters block + */ +enum mali_c55_param_buffer_version { + MALI_C55_PARAM_BUFFER_V1, +}; + +/** + * enum mali_c55_param_block_type - Enumeration of Mali-C55 parameter blocks + * + * This enumeration defines the types of Mali-C55 parameters block. Each block + * configures a specific processing block of the Mali-C55 ISP. The block + * type allows the driver to correctly interpret the parameters block data. + * + * It is the responsibility of userspace to correctly set the type of each + * parameters block. + * + * @MALI_C55_PARAM_BLOCK_SENSOR_OFFS: Sensor pre-shading black level offset + * @MALI_C55_PARAM_BLOCK_AEXP_HIST: Auto-exposure 1024-bin histogram + * configuration + * @MALI_C55_PARAM_BLOCK_AEXP_IHIST: Post-Iridix auto-exposure 1024-bin + * histogram configuration + * @MALI_C55_PARAM_BLOCK_AEXP_HIST_WEIGHTS: Auto-exposure 1024-bin histogram + * weighting + * @MALI_C55_PARAM_BLOCK_AEXP_IHIST_WEIGHTS: Post-Iridix auto-exposure 1024-bin + * histogram weighting + * @MALI_C55_PARAM_BLOCK_DIGITAL_GAIN: Digital gain + * @MALI_C55_PARAM_BLOCK_AWB_GAINS: Auto-white balance gains + * @MALI_C55_PARAM_BLOCK_AWB_CONFIG: Auto-white balance statistics config + * @MALI_C55_PARAM_BLOCK_AWB_GAINS_AEXP: Auto-white balance gains for AEXP-0 tap + * @MALI_C55_PARAM_MESH_SHADING_CONFIG : Mesh shading tables configuration + * @MALI_C55_PARAM_MESH_SHADING_SELECTION: Mesh shading table selection + */ +enum mali_c55_param_block_type { + MALI_C55_PARAM_BLOCK_SENSOR_OFFS, + MALI_C55_PARAM_BLOCK_AEXP_HIST, + MALI_C55_PARAM_BLOCK_AEXP_IHIST, + MALI_C55_PARAM_BLOCK_AEXP_HIST_WEIGHTS, + MALI_C55_PARAM_BLOCK_AEXP_IHIST_WEIGHTS, + MALI_C55_PARAM_BLOCK_DIGITAL_GAIN, + MALI_C55_PARAM_BLOCK_AWB_GAINS, + MALI_C55_PARAM_BLOCK_AWB_CONFIG, + MALI_C55_PARAM_BLOCK_AWB_GAINS_AEXP, + MALI_C55_PARAM_MESH_SHADING_CONFIG, + MALI_C55_PARAM_MESH_SHADING_SELECTION, +}; + +/** + * struct mali_c55_params_sensor_off_preshading - offset subtraction for each + * color channel + * + * Provides removal of the sensor black level from the sensor data. Separate + * offsets are provided for each of the four Bayer component color channels + * which are defaulted to R, Gr, Gb, B. + * + * header.type should be set to MALI_C55_PARAM_BLOCK_SENSOR_OFFS from + * :c:type:`mali_c55_param_block_type` for this block. + * + * @header: The Mali-C55 parameters block header + * @chan00: Offset for color channel 00 (default: R) + * @chan01: Offset for color channel 01 (default: Gr) + * @chan10: Offset for color channel 10 (default: Gb) + * @chan11: Offset for color channel 11 (default: B) + */ +struct mali_c55_params_sensor_off_preshading { + struct v4l2_isp_params_block_header header; + __u32 chan00; + __u32 chan01; + __u32 chan10; + __u32 chan11; +}; + +/** + * enum mali_c55_aexp_hist_tap_points - Tap points for the AEXP histogram + * @MALI_C55_AEXP_HIST_TAP_WB: After static white balance + * @MALI_C55_AEXP_HIST_TAP_FS: After WDR Frame Stitch + * @MALI_C55_AEXP_HIST_TAP_TPG: After the test pattern generator + */ +enum mali_c55_aexp_hist_tap_points { + MALI_C55_AEXP_HIST_TAP_WB = 0, + MALI_C55_AEXP_HIST_TAP_FS, + MALI_C55_AEXP_HIST_TAP_TPG, +}; + +/** + * enum mali_c55_aexp_skip_x - Horizontal pixel skipping + * @MALI_C55_AEXP_SKIP_X_EVERY_2ND: Collect every 2nd pixel horizontally + * @MALI_C55_AEXP_SKIP_X_EVERY_3RD: Collect every 3rd pixel horizontally + * @MALI_C55_AEXP_SKIP_X_EVERY_4TH: Collect every 4th pixel horizontally + * @MALI_C55_AEXP_SKIP_X_EVERY_5TH: Collect every 5th pixel horizontally + * @MALI_C55_AEXP_SKIP_X_EVERY_8TH: Collect every 8th pixel horizontally + * @MALI_C55_AEXP_SKIP_X_EVERY_9TH: Collect every 9th pixel horizontally + */ +enum mali_c55_aexp_skip_x { + MALI_C55_AEXP_SKIP_X_EVERY_2ND, + MALI_C55_AEXP_SKIP_X_EVERY_3RD, + MALI_C55_AEXP_SKIP_X_EVERY_4TH, + MALI_C55_AEXP_SKIP_X_EVERY_5TH, + MALI_C55_AEXP_SKIP_X_EVERY_8TH, + MALI_C55_AEXP_SKIP_X_EVERY_9TH +}; + +/** + * enum mali_c55_aexp_skip_y - Vertical pixel skipping + * @MALI_C55_AEXP_SKIP_Y_ALL: Collect every single pixel vertically + * @MALI_C55_AEXP_SKIP_Y_EVERY_2ND: Collect every 2nd pixel vertically + * @MALI_C55_AEXP_SKIP_Y_EVERY_3RD: Collect every 3rd pixel vertically + * @MALI_C55_AEXP_SKIP_Y_EVERY_4TH: Collect every 4th pixel vertically + * @MALI_C55_AEXP_SKIP_Y_EVERY_5TH: Collect every 5th pixel vertically + * @MALI_C55_AEXP_SKIP_Y_EVERY_8TH: Collect every 8th pixel vertically + * @MALI_C55_AEXP_SKIP_Y_EVERY_9TH: Collect every 9th pixel vertically + */ +enum mali_c55_aexp_skip_y { + MALI_C55_AEXP_SKIP_Y_ALL, + MALI_C55_AEXP_SKIP_Y_EVERY_2ND, + MALI_C55_AEXP_SKIP_Y_EVERY_3RD, + MALI_C55_AEXP_SKIP_Y_EVERY_4TH, + MALI_C55_AEXP_SKIP_Y_EVERY_5TH, + MALI_C55_AEXP_SKIP_Y_EVERY_8TH, + MALI_C55_AEXP_SKIP_Y_EVERY_9TH +}; + +/** + * enum mali_c55_aexp_row_column_offset - Start from the first or second row or + * column + * @MALI_C55_AEXP_FIRST_ROW_OR_COL: Start from the first row / column + * @MALI_C55_AEXP_SECOND_ROW_OR_COL: Start from the second row / column + */ +enum mali_c55_aexp_row_column_offset { + MALI_C55_AEXP_FIRST_ROW_OR_COL = 1, + MALI_C55_AEXP_SECOND_ROW_OR_COL = 2, +}; + +/** + * enum mali_c55_aexp_hist_plane_mode - Mode for the AEXP Histograms + * @MALI_C55_AEXP_HIST_COMBINED: All color planes in one 1024-bin histogram + * @MALI_C55_AEXP_HIST_SEPARATE: Each color plane in one 256-bin histogram with a bin width of 16 + * @MALI_C55_AEXP_HIST_FOCUS_00: Top left plane in the first bank, rest in second bank + * @MALI_C55_AEXP_HIST_FOCUS_01: Top right plane in the first bank, rest in second bank + * @MALI_C55_AEXP_HIST_FOCUS_10: Bottom left plane in the first bank, rest in second bank + * @MALI_C55_AEXP_HIST_FOCUS_11: Bottom right plane in the first bank, rest in second bank + * + * In the "focus" modes statistics are collected into two 512-bin histograms + * with a bin width of 8. One colour plane is in the first histogram with the + * remainder combined into the second. The four options represent which of the + * four positions in a bayer pattern are the focused plane. + */ +enum mali_c55_aexp_hist_plane_mode { + MALI_C55_AEXP_HIST_COMBINED = 0, + MALI_C55_AEXP_HIST_SEPARATE = 1, + MALI_C55_AEXP_HIST_FOCUS_00 = 4, + MALI_C55_AEXP_HIST_FOCUS_01 = 5, + MALI_C55_AEXP_HIST_FOCUS_10 = 6, + MALI_C55_AEXP_HIST_FOCUS_11 = 7, +}; + +/** + * struct mali_c55_params_aexp_hist - configuration for AEXP metering hists + * + * This struct allows users to configure the 1024-bin AEXP histograms. Broadly + * speaking the parameters allow you to mask particular regions of the image and + * to select different kinds of histogram. + * + * The skip_x, offset_x, skip_y and offset_y fields allow users to ignore or + * mask pixels in the frame by their position relative to the top left pixel. + * First, the skip_y, offset_x and offset_y fields define which of the pixels + * within each 2x2 region will be counted in the statistics. + * + * If skip_y == 0 then two pixels from each covered region will be counted. If + * both offset_x and offset_y are zero, then the two left-most pixels in each + * 2x2 pixel region will be counted. Setting offset_x = 1 will discount the top + * left pixel and count the top right pixel. Setting offset_y = 1 will discount + * the bottom left pixel and count the bottom right pixel. + * + * If skip_y != 0 then only a single pixel from each region covered by the + * pattern will be counted. In this case offset_x controls whether the pixel + * that's counted is in the left (if offset_x == 0) or right (if offset_x == 1) + * column and offset_y controls whether the pixel that's counted is in the top + * (if offset_y == 0) or bottom (if offset_y == 1) row. + * + * The skip_x and skip_y fields control how the 2x2 pixel region is repeated + * across the image data. The first instance of the region is always in the top + * left of the image data. The skip_x field controls how many pixels are ignored + * in the x direction before the pixel masking region is repeated. The skip_y + * field controls how many pixels are ignored in the y direction before the + * pixel masking region is repeated. + * + * These fields can be used to reduce the number of pixels counted for the + * statistics, but it's important to be careful to configure them correctly. + * Some combinations of values will result in colour components from the input + * data being ignored entirely, for example in the following configuration: + * + * skip_x = 0 + * offset_x = 0 + * skip_y = 0 + * offset_y = 0 + * + * Only the R and Gb components of RGGB data that was input would be collected. + * Similarly in the following configuration: + * + * skip_x = 0 + * offset_x = 0 + * skip_y = 1 + * offset_y = 1 + * + * Only the Gb component of RGGB data that was input would be collected. To + * correct things such that all 4 colour components were included it would be + * necessary to set the skip_x and skip_y fields in a way that resulted in all + * four colour components being collected: + * + * skip_x = 1 + * offset_x = 0 + * skip_y = 1 + * offset_y = 1 + * + * header.type should be set to one of either MALI_C55_PARAM_BLOCK_AEXP_HIST or + * MALI_C55_PARAM_BLOCK_AEXP_IHIST from :c:type:`mali_c55_param_block_type`. + * + * @header: The Mali-C55 parameters block header + * @skip_x: Horizontal decimation. See enum mali_c55_aexp_skip_x + * @offset_x: Skip the first column, or not. See enum mali_c55_aexp_row_column_offset + * @skip_y: Vertical decimation. See enum mali_c55_aexp_skip_y + * @offset_y: Skip the first row, or not. See enum mali_c55_aexp_row_column_offset + * @scale_bottom: Scale pixels in bottom half of intensity range: 0=1x ,1=2x, 2=4x, 4=8x, 4=16x + * @scale_top: scale pixels in top half of intensity range: 0=1x ,1=2x, 2=4x, 4=8x, 4=16x + * @plane_mode: Plane separation mode. See enum mali_c55_aexp_hist_plane_mode + * @tap_point: Tap point for histogram from enum mali_c55_aexp_hist_tap_points. + * This parameter is unused for the post-Iridix Histogram + */ +struct mali_c55_params_aexp_hist { + struct v4l2_isp_params_block_header header; + __u8 skip_x; + __u8 offset_x; + __u8 skip_y; + __u8 offset_y; + __u8 scale_bottom; + __u8 scale_top; + __u8 plane_mode; + __u8 tap_point; +}; + +/** + * struct mali_c55_params_aexp_weights - Array of weights for AEXP metering + * + * This struct allows users to configure the weighting for both of the 1024-bin + * AEXP histograms. The pixel data collected for each zone is multiplied by the + * corresponding weight from this array, which may be zero if the intention is + * to mask off the zone entirely. + * + * header.type should be set to one of either MALI_C55_PARAM_BLOCK_AEXP_HIST_WEIGHTS + * or MALI_C55_PARAM_BLOCK_AEXP_IHIST_WEIGHTS from :c:type:`mali_c55_param_block_type`. + * + * @header: The Mali-C55 parameters block header + * @nodes_used_horiz: Number of active zones horizontally [0..15] + * @nodes_used_vert: Number of active zones vertically [0..15] + * @zone_weights: Zone weighting. Index is row*col where 0,0 is the top + * left zone continuing in raster order. Each zone can be + * weighted in the range [0..15]. The number of rows and + * columns is defined by @nodes_used_vert and + * @nodes_used_horiz + */ +struct mali_c55_params_aexp_weights { + struct v4l2_isp_params_block_header header; + __u8 nodes_used_horiz; + __u8 nodes_used_vert; + __u8 zone_weights[MALI_C55_MAX_ZONES]; +}; + +/** + * struct mali_c55_params_digital_gain - Digital gain value + * + * This struct carries a digital gain value to set in the ISP. + * + * header.type should be set to MALI_C55_PARAM_BLOCK_DIGITAL_GAIN from + * :c:type:`mali_c55_param_block_type` for this block. + * + * @header: The Mali-C55 parameters block header + * @gain: The digital gain value to apply, in Q5.8 format. + */ +struct mali_c55_params_digital_gain { + struct v4l2_isp_params_block_header header; + __u16 gain; +}; + +/** + * enum mali_c55_awb_stats_mode - Statistics mode for AWB + * @MALI_C55_AWB_MODE_GRBR: Statistics collected as Green/Red and Blue/Red ratios + * @MALI_C55_AWB_MODE_RGBG: Statistics collected as Red/Green and Blue/Green ratios + */ +enum mali_c55_awb_stats_mode { + MALI_C55_AWB_MODE_GRBR = 0, + MALI_C55_AWB_MODE_RGBG, +}; + +/** + * struct mali_c55_params_awb_gains - Gain settings for auto white balance + * + * This struct allows users to configure the gains for auto-white balance. There + * are four gain settings corresponding to each colour channel in the bayer + * domain. Although named generically, the association between the gain applied + * and the colour channel is done automatically within the ISP depending on the + * input format, and so the following mapping always holds true:: + * + * gain00 = R + * gain01 = Gr + * gain10 = Gb + * gain11 = B + * + * All of the gains are stored in Q4.8 format. + * + * header.type should be set to one of either MALI_C55_PARAM_BLOCK_AWB_GAINS or + * MALI_C55_PARAM_BLOCK_AWB_GAINS_AEXP from :c:type:`mali_c55_param_block_type`. + * + * @header: The Mali-C55 parameters block header + * @gain00: Multiplier for colour channel 00 + * @gain01: Multiplier for colour channel 01 + * @gain10: Multiplier for colour channel 10 + * @gain11: Multiplier for colour channel 11 + */ +struct mali_c55_params_awb_gains { + struct v4l2_isp_params_block_header header; + __u16 gain00; + __u16 gain01; + __u16 gain10; + __u16 gain11; +}; + +/** + * enum mali_c55_params_awb_tap_points - Tap points for the AWB statistics + * @MALI_C55_AWB_STATS_TAP_PF: Immediately after the Purple Fringe block + * @MALI_C55_AWB_STATS_TAP_CNR: Immediately after the CNR block + */ +enum mali_c55_params_awb_tap_points { + MALI_C55_AWB_STATS_TAP_PF = 0, + MALI_C55_AWB_STATS_TAP_CNR, +}; + +/** + * struct mali_c55_params_awb_config - Stats settings for auto-white balance + * + * This struct allows the configuration of the statistics generated for auto + * white balance. Pixel intensity limits can be set to exclude overly bright or + * dark regions of an image from the statistics entirely. Colour ratio minima + * and maxima can be set to discount pixels who's ratios fall outside the + * defined boundaries; there are two sets of registers to do this - the + * "min/max" ratios which bound a region and the "high/low" ratios which further + * trim the upper and lower ratios. For example with the boundaries configured + * as follows, only pixels whos colour ratios falls into the region marked "A" + * would be counted:: + * + * cr_high + * 2.0 | | + * | cb_max --> _________________________v_____ + * 1.8 | | \ | + * | | \ | + * 1.6 | | \ | + * | | \ | + * c 1.4 | cb_low -->|\ A \|<-- cb_high + * b | | \ | + * 1.2 | | \ | + * r | | \ | + * a 1.0 | cb_min --> |____\_________________________| + * t | ^ ^ ^ + * i 0.8 | | | | + * o | cr_min | cr_max + * s 0.6 | | + * | cr_low + * 0.4 | + * | + * 0.2 | + * | + * 0.0 |_______________________________________________________________ + * 0.0 0.2 0.4 0.6 0.8 1.0 1.2 1.4 1.6 1.8 2.0 + * cr ratios + * + * header.type should be set to MALI_C55_PARAM_BLOCK_AWB_CONFIG from + * :c:type:`mali_c55_param_block_type` for this block. + * + * @header: The Mali-C55 parameters block header + * @tap_point: The tap point from enum mali_c55_params_awb_tap_points + * @stats_mode: AWB statistics collection mode, see :c:type:`mali_c55_awb_stats_mode` + * @white_level: Upper pixel intensity (I.E. raw pixel values) limit + * @black_level: Lower pixel intensity (I.E. raw pixel values) limit + * @cr_max: Maximum R/G ratio (Q4.8 format) + * @cr_min: Minimum R/G ratio (Q4.8 format) + * @cb_max: Maximum B/G ratio (Q4.8 format) + * @cb_min: Minimum B/G ratio (Q4.8 format) + * @nodes_used_horiz: Number of active zones horizontally [0..15] + * @nodes_used_vert: Number of active zones vertically [0..15] + * @cr_high: R/G ratio trim high (Q4.8 format) + * @cr_low: R/G ratio trim low (Q4.8 format) + * @cb_high: B/G ratio trim high (Q4.8 format) + * @cb_low: B/G ratio trim low (Q4.8 format) + */ +struct mali_c55_params_awb_config { + struct v4l2_isp_params_block_header header; + __u8 tap_point; + __u8 stats_mode; + __u16 white_level; + __u16 black_level; + __u16 cr_max; + __u16 cr_min; + __u16 cb_max; + __u16 cb_min; + __u8 nodes_used_horiz; + __u8 nodes_used_vert; + __u16 cr_high; + __u16 cr_low; + __u16 cb_high; + __u16 cb_low; +}; + +#define MALI_C55_NUM_MESH_SHADING_ELEMENTS 3072 + +/** + * struct mali_c55_params_mesh_shading_config - Mesh shading configuration + * + * The mesh shading correction module allows programming a separate table of + * either 16x16 or 32x32 node coefficients for 3 different light sources. The + * final correction coefficients applied are computed by blending the + * coefficients from two tables together. + * + * A page of 1024 32-bit integers is associated to each colour channel, with + * pages stored consecutively in memory. Each 32-bit integer packs 3 8-bit + * correction coefficients for a single node, one for each of the three light + * sources. The 8 most significant bits are unused. The following table + * describes the layout:: + * + * +----------- Page (Colour Plane) 0 -------------+ + * | @mesh[i] | Mesh Point | Bits | Light Source | + * +-----------+------------+-------+--------------+ + * | 0 | 0,0 | 16,23 | LS2 | + * | | | 08-15 | LS1 | + * | | | 00-07 | LS0 | + * +-----------+------------+-------+--------------+ + * | 1 | 0,1 | 16,23 | LS2 | + * | | | 08-15 | LS1 | + * | | | 00-07 | LS0 | + * +-----------+------------+-------+--------------+ + * | ... | ... | ... | ... | + * +-----------+------------+-------+--------------+ + * | 1023 | 31,31 | 16,23 | LS2 | + * | | | 08-15 | LS1 | + * | | | 00-07 | LS0 | + * +----------- Page (Colour Plane) 1 -------------+ + * | @mesh[i] | Mesh Point | Bits | Light Source | + * +-----------+------------+-------+--------------+ + * | 1024 | 0,0 | 16,23 | LS2 | + * | | | 08-15 | LS1 | + * | | | 00-07 | LS0 | + * +-----------+------------+-------+--------------+ + * | 1025 | 0,1 | 16,23 | LS2 | + * | | | 08-15 | LS1 | + * | | | 00-07 | LS0 | + * +-----------+------------+-------+--------------+ + * | ... | ... | ... | ... | + * +-----------+------------+-------+--------------+ + * | 2047 | 31,31 | 16,23 | LS2 | + * | | | 08-15 | LS1 | + * | | | 00-07 | LS0 | + * +----------- Page (Colour Plane) 2 -------------+ + * | @mesh[i] | Mesh Point | Bits | Light Source | + * +-----------+------------+-------+--------------+ + * | 2048 | 0,0 | 16,23 | LS2 | + * | | | 08-15 | LS1 | + * | | | 00-07 | LS0 | + * +-----------+------------+-------+--------------+ + * | 2049 | 0,1 | 16,23 | LS2 | + * | | | 08-15 | LS1 | + * | | | 00-07 | LS0 | + * +-----------+------------+-------+--------------+ + * | ... | ... | ... | ... | + * +-----------+------------+-------+--------------+ + * | 3071 | 31,31 | 16,23 | LS2 | + * | | | 08-15 | LS1 | + * | | | 00-07 | LS0 | + * +-----------+------------+-------+--------------+ + * + * The @mesh_scale member determines the precision and minimum and maximum gain. + * For example if @mesh_scale is 0 and therefore selects 0 - 2x gain, a value of + * 0 in a coefficient means 0.0 gain, a value of 128 means 1.0 gain and 255 + * means 2.0 gain. + * + * header.type should be set to MALI_C55_PARAM_MESH_SHADING_CONFIG from + * :c:type:`mali_c55_param_block_type` for this block. + * + * @header: The Mali-C55 parameters block header + * @mesh_show: Output the mesh data rather than image data + * @mesh_scale: Set the precision and maximum gain range of mesh shading + * - 0 = 0-2x gain + * - 1 = 0-4x gain + * - 2 = 0-8x gain + * - 3 = 0-16x gain + * - 4 = 1-2x gain + * - 5 = 1-3x gain + * - 6 = 1-5x gain + * - 7 = 1-9x gain + * @mesh_page_r: Mesh page select for red colour plane [0..2] + * @mesh_page_g: Mesh page select for green colour plane [0..2] + * @mesh_page_b: Mesh page select for blue colour plane [0..2] + * @mesh_width: Number of horizontal nodes minus 1 [15,31] + * @mesh_height: Number of vertical nodes minus 1 [15,31] + * @mesh: Mesh shading correction tables + */ +struct mali_c55_params_mesh_shading_config { + struct v4l2_isp_params_block_header header; + __u8 mesh_show; + __u8 mesh_scale; + __u8 mesh_page_r; + __u8 mesh_page_g; + __u8 mesh_page_b; + __u8 mesh_width; + __u8 mesh_height; + __u32 mesh[MALI_C55_NUM_MESH_SHADING_ELEMENTS]; +}; + +/** enum mali_c55_params_mesh_alpha_bank - Mesh shading table bank selection + * @MALI_C55_MESH_ALPHA_BANK_LS0_AND_LS1 - Select Light Sources 0 and 1 + * @MALI_C55_MESH_ALPHA_BANK_LS1_AND_LS2 - Select Light Sources 1 and 2 + * @MALI_C55_MESH_ALPHA_BANK_LS0_AND_LS2 - Select Light Sources 0 and 2 + */ +enum mali_c55_params_mesh_alpha_bank { + MALI_C55_MESH_ALPHA_BANK_LS0_AND_LS1 = 0, + MALI_C55_MESH_ALPHA_BANK_LS1_AND_LS2 = 1, + MALI_C55_MESH_ALPHA_BANK_LS0_AND_LS2 = 4 +}; + +/** + * struct mali_c55_params_mesh_shading_selection - Mesh table selection + * + * The module computes the final correction coefficients by blending the ones + * from two light source tables, which are selected (independently for each + * colour channel) by the @mesh_alpha_bank_r/g/b fields. + * + * The final blended coefficients for each node are calculated using the + * following equation: + * + * Final coefficient = (a * LS\ :sub:`b`\ + (256 - a) * LS\ :sub:`a`\) / 256 + * + * Where a is the @mesh_alpha_r/g/b value, and LS\ :sub:`a`\ and LS\ :sub:`b`\ + * are the node cofficients for the two tables selected by the + * @mesh_alpha_bank_r/g/b value. + * + * The scale of the applied correction may also be controlled by tuning the + * @mesh_strength member. This is a modifier to the final coefficients which can + * be used to globally reduce the gains applied. + * + * header.type should be set to MALI_C55_PARAM_MESH_SHADING_SELECTION from + * :c:type:`mali_c55_param_block_type` for this block. + * + * @header: The Mali-C55 parameters block header + * @mesh_alpha_bank_r: Red mesh table select (c:type:`enum mali_c55_params_mesh_alpha_bank`) + * @mesh_alpha_bank_g: Green mesh table select (c:type:`enum mali_c55_params_mesh_alpha_bank`) + * @mesh_alpha_bank_b: Blue mesh table select (c:type:`enum mali_c55_params_mesh_alpha_bank`) + * @mesh_alpha_r: Blend coefficient for R [0..255] + * @mesh_alpha_g: Blend coefficient for G [0..255] + * @mesh_alpha_b: Blend coefficient for B [0..255] + * @mesh_strength: Mesh strength in Q4.12 format [0..4096] + */ +struct mali_c55_params_mesh_shading_selection { + struct v4l2_isp_params_block_header header; + __u8 mesh_alpha_bank_r; + __u8 mesh_alpha_bank_g; + __u8 mesh_alpha_bank_b; + __u8 mesh_alpha_r; + __u8 mesh_alpha_g; + __u8 mesh_alpha_b; + __u16 mesh_strength; +}; + +/** + * define MALI_C55_PARAMS_MAX_SIZE - Maximum size of all Mali C55 Parameters + * + * Though the parameters for the Mali-C55 are passed as optional blocks, the + * driver still needs to know the absolute maximum size so that it can allocate + * a buffer sized appropriately to accommodate userspace attempting to set all + * possible parameters in a single frame. + * + * Some structs are in this list multiple times. Where that's the case, it just + * reflects the fact that the same struct can be used with multiple different + * header types from :c:type:`mali_c55_param_block_type`. + */ +#define MALI_C55_PARAMS_MAX_SIZE \ + (sizeof(struct mali_c55_params_sensor_off_preshading) + \ + sizeof(struct mali_c55_params_aexp_hist) + \ + sizeof(struct mali_c55_params_aexp_weights) + \ + sizeof(struct mali_c55_params_aexp_hist) + \ + sizeof(struct mali_c55_params_aexp_weights) + \ + sizeof(struct mali_c55_params_digital_gain) + \ + sizeof(struct mali_c55_params_awb_gains) + \ + sizeof(struct mali_c55_params_awb_config) + \ + sizeof(struct mali_c55_params_awb_gains) + \ + sizeof(struct mali_c55_params_mesh_shading_config) + \ + sizeof(struct mali_c55_params_mesh_shading_selection)) + #endif /* __UAPI_MALI_C55_CONFIG_H */ -- cgit v1.2.3 From f91bc8f61abf0e1d23108ae9871c60d7612a09b2 Mon Sep 17 00:00:00 2001 From: Magnus Kulke Date: Thu, 6 Nov 2025 14:13:31 -0800 Subject: mshv: Allow mappings that overlap in uaddr Currently the MSHV driver rejects mappings that would overlap in userspace. Some VMMs require the same memory to be mapped to different parts of the guest's address space, and so working around this restriction is difficult. The hypervisor itself doesn't prohibit mappings that overlap in uaddr, (really in SPA; system physical addresses), so supporting this in the driver doesn't require any extra work: only the checks need to be removed. Since no userspace code until now has been able to overlap regions in userspace, relaxing this constraint can't break any existing code. Signed-off-by: Magnus Kulke Signed-off-by: Nuno Das Neves Reviewed-by: Michael Kelley Signed-off-by: Wei Liu --- drivers/hv/mshv_root_main.c | 8 ++------ include/uapi/linux/mshv.h | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c index a8f3c5f3ce34..3f73c468e975 100644 --- a/drivers/hv/mshv_root_main.c +++ b/drivers/hv/mshv_root_main.c @@ -1188,12 +1188,8 @@ static int mshv_partition_create_region(struct mshv_partition *partition, /* Reject overlapping regions */ hlist_for_each_entry(rg, &partition->pt_mem_regions, hnode) { - u64 rg_size = rg->nr_pages << HV_HYP_PAGE_SHIFT; - - if ((mem->guest_pfn + nr_pages <= rg->start_gfn || - rg->start_gfn + rg->nr_pages <= mem->guest_pfn) && - (mem->userspace_addr + mem->size <= rg->start_uaddr || - rg->start_uaddr + rg_size <= mem->userspace_addr)) + if (mem->guest_pfn + nr_pages <= rg->start_gfn || + rg->start_gfn + rg->nr_pages <= mem->guest_pfn) continue; return -EEXIST; diff --git a/include/uapi/linux/mshv.h b/include/uapi/linux/mshv.h index 876bfe4e4227..374f75e198bc 100644 --- a/include/uapi/linux/mshv.h +++ b/include/uapi/linux/mshv.h @@ -89,7 +89,7 @@ enum { * @rsvd: MBZ * * Map or unmap a region of userspace memory to Guest Physical Addresses (GPA). - * Mappings can't overlap in GPA space or userspace. + * Mappings can't overlap in GPA space. * To unmap, these fields must match an existing mapping. */ struct mshv_user_mem_region { -- cgit v1.2.3 From c91fe5f162f278d4aa960d06d2dbc42f9857593a Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Thu, 13 Nov 2025 11:45:33 -0800 Subject: mshv: Extend create partition ioctl to support cpu features The existing mshv create partition ioctl does not provide a way to specify which cpu features are enabled in the guest. Instead, it attempts to enable all features and those that are not supported are silently disabled by the hypervisor. This was done to reduce unnecessary complexity and is sufficient for many cases. However, new scenarios require fine-grained control over these features. Define a new mshv_create_partition_v2 structure which supports passing the disabled processor and xsave feature bits through to the create partition hypercall directly. Introduce a new flag MSHV_PT_BIT_CPU_AND_XSAVE_FEATURES which enables the new structure. If unset, the original mshv_create_partition struct is used, with the old behavior of enabling all features. Co-developed-by: Jinank Jain Signed-off-by: Jinank Jain Signed-off-by: Muminul Islam Signed-off-by: Nuno Das Neves Reviewed-by: Michael Kelley Signed-off-by: Wei Liu --- drivers/hv/mshv_root_main.c | 118 ++++++++++++++++++++++++++++++++++++-------- include/uapi/linux/mshv.h | 34 +++++++++++++ 2 files changed, 131 insertions(+), 21 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c index 3f73c468e975..45c7a5fea1cf 100644 --- a/drivers/hv/mshv_root_main.c +++ b/drivers/hv/mshv_root_main.c @@ -1855,43 +1855,119 @@ add_partition(struct mshv_partition *partition) return 0; } -static long -mshv_ioctl_create_partition(void __user *user_arg, struct device *module_dev) +static_assert(MSHV_NUM_CPU_FEATURES_BANKS == + HV_PARTITION_PROCESSOR_FEATURES_BANKS); + +static long mshv_ioctl_process_pt_flags(void __user *user_arg, u64 *pt_flags, + struct hv_partition_creation_properties *cr_props, + union hv_partition_isolation_properties *isol_props) { - struct mshv_create_partition args; - u64 creation_flags; - struct hv_partition_creation_properties creation_properties = {}; - union hv_partition_isolation_properties isolation_properties = {}; - struct mshv_partition *partition; - struct file *file; - int fd; - long ret; + int i; + struct mshv_create_partition_v2 args; + union hv_partition_processor_features *disabled_procs; + union hv_partition_processor_xsave_features *disabled_xsave; - if (copy_from_user(&args, user_arg, sizeof(args))) + /* First, copy v1 struct in case user is on previous versions */ + if (copy_from_user(&args, user_arg, + sizeof(struct mshv_create_partition))) return -EFAULT; if ((args.pt_flags & ~MSHV_PT_FLAGS_MASK) || args.pt_isolation >= MSHV_PT_ISOLATION_COUNT) return -EINVAL; + disabled_procs = &cr_props->disabled_processor_features; + disabled_xsave = &cr_props->disabled_processor_xsave_features; + + /* Check if user provided newer struct with feature fields */ + if (args.pt_flags & BIT_ULL(MSHV_PT_BIT_CPU_AND_XSAVE_FEATURES)) { + if (copy_from_user(&args, user_arg, sizeof(args))) + return -EFAULT; + + /* Re-validate v1 fields after second copy_from_user() */ + if ((args.pt_flags & ~MSHV_PT_FLAGS_MASK) || + args.pt_isolation >= MSHV_PT_ISOLATION_COUNT) + return -EINVAL; + + if (args.pt_num_cpu_fbanks != MSHV_NUM_CPU_FEATURES_BANKS || + mshv_field_nonzero(args, pt_rsvd) || + mshv_field_nonzero(args, pt_rsvd1)) + return -EINVAL; + + /* + * Note this assumes MSHV_NUM_CPU_FEATURES_BANKS will never + * change and equals HV_PARTITION_PROCESSOR_FEATURES_BANKS + * (i.e. 2). + * + * Further banks (index >= 2) will be modifiable as 'early' + * properties via the set partition property hypercall. + */ + for (i = 0; i < HV_PARTITION_PROCESSOR_FEATURES_BANKS; i++) + disabled_procs->as_uint64[i] = args.pt_cpu_fbanks[i]; + +#if IS_ENABLED(CONFIG_X86_64) + disabled_xsave->as_uint64 = args.pt_disabled_xsave; +#else + /* + * In practice this field is ignored on arm64, but safer to + * zero it in case it is ever used. + */ + disabled_xsave->as_uint64 = 0; + + if (mshv_field_nonzero(args, pt_rsvd2)) + return -EINVAL; +#endif + } else { + /* + * v1 behavior: try to enable everything. The hypervisor will + * disable features that are not supported. The banks can be + * queried via the get partition property hypercall. + */ + for (i = 0; i < HV_PARTITION_PROCESSOR_FEATURES_BANKS; i++) + disabled_procs->as_uint64[i] = 0; + + disabled_xsave->as_uint64 = 0; + } + /* Only support EXO partitions */ - creation_flags = HV_PARTITION_CREATION_FLAG_EXO_PARTITION | - HV_PARTITION_CREATION_FLAG_INTERCEPT_MESSAGE_PAGE_ENABLED; + *pt_flags = HV_PARTITION_CREATION_FLAG_EXO_PARTITION | + HV_PARTITION_CREATION_FLAG_INTERCEPT_MESSAGE_PAGE_ENABLED; + + if (args.pt_flags & BIT_ULL(MSHV_PT_BIT_LAPIC)) + *pt_flags |= HV_PARTITION_CREATION_FLAG_LAPIC_ENABLED; + if (args.pt_flags & BIT_ULL(MSHV_PT_BIT_X2APIC)) + *pt_flags |= HV_PARTITION_CREATION_FLAG_X2APIC_CAPABLE; + if (args.pt_flags & BIT_ULL(MSHV_PT_BIT_GPA_SUPER_PAGES)) + *pt_flags |= HV_PARTITION_CREATION_FLAG_GPA_SUPER_PAGES_ENABLED; - if (args.pt_flags & BIT(MSHV_PT_BIT_LAPIC)) - creation_flags |= HV_PARTITION_CREATION_FLAG_LAPIC_ENABLED; - if (args.pt_flags & BIT(MSHV_PT_BIT_X2APIC)) - creation_flags |= HV_PARTITION_CREATION_FLAG_X2APIC_CAPABLE; - if (args.pt_flags & BIT(MSHV_PT_BIT_GPA_SUPER_PAGES)) - creation_flags |= HV_PARTITION_CREATION_FLAG_GPA_SUPER_PAGES_ENABLED; + isol_props->as_uint64 = 0; switch (args.pt_isolation) { case MSHV_PT_ISOLATION_NONE: - isolation_properties.isolation_type = - HV_PARTITION_ISOLATION_TYPE_NONE; + isol_props->isolation_type = HV_PARTITION_ISOLATION_TYPE_NONE; break; } + return 0; +} + +static long +mshv_ioctl_create_partition(void __user *user_arg, struct device *module_dev) +{ + u64 creation_flags; + struct hv_partition_creation_properties creation_properties; + union hv_partition_isolation_properties isolation_properties; + struct mshv_partition *partition; + struct file *file; + int fd; + long ret; + + ret = mshv_ioctl_process_pt_flags(user_arg, &creation_flags, + &creation_properties, + &isolation_properties); + if (ret) + return ret; + partition = kzalloc(sizeof(*partition), GFP_KERNEL); if (!partition) return -ENOMEM; diff --git a/include/uapi/linux/mshv.h b/include/uapi/linux/mshv.h index 374f75e198bc..b645d17cc531 100644 --- a/include/uapi/linux/mshv.h +++ b/include/uapi/linux/mshv.h @@ -26,6 +26,7 @@ enum { MSHV_PT_BIT_LAPIC, MSHV_PT_BIT_X2APIC, MSHV_PT_BIT_GPA_SUPER_PAGES, + MSHV_PT_BIT_CPU_AND_XSAVE_FEATURES, MSHV_PT_BIT_COUNT, }; @@ -41,6 +42,8 @@ enum { * @pt_flags: Bitmask of 1 << MSHV_PT_BIT_* * @pt_isolation: MSHV_PT_ISOLATION_* * + * This is the initial/v1 version for backward compatibility. + * * Returns a file descriptor to act as a handle to a guest partition. * At this point the partition is not yet initialized in the hypervisor. * Some operations must be done with the partition in this state, e.g. setting @@ -52,6 +55,37 @@ struct mshv_create_partition { __u64 pt_isolation; }; +#define MSHV_NUM_CPU_FEATURES_BANKS 2 + +/** + * struct mshv_create_partition_v2 + * + * This is extended version of the above initial MSHV_CREATE_PARTITION + * ioctl and allows for following additional parameters: + * + * @pt_num_cpu_fbanks: Must be set to MSHV_NUM_CPU_FEATURES_BANKS. + * @pt_cpu_fbanks: Disabled processor feature banks array. + * @pt_disabled_xsave: Disabled xsave feature bits. + * + * pt_cpu_fbanks and pt_disabled_xsave are passed through as-is to the create + * partition hypercall. + * + * Returns : same as above original mshv_create_partition + */ +struct mshv_create_partition_v2 { + __u64 pt_flags; + __u64 pt_isolation; + __u16 pt_num_cpu_fbanks; + __u8 pt_rsvd[6]; /* MBZ */ + __u64 pt_cpu_fbanks[MSHV_NUM_CPU_FEATURES_BANKS]; + __u64 pt_rsvd1[2]; /* MBZ */ +#if defined(__x86_64__) + __u64 pt_disabled_xsave; +#else + __u64 pt_rsvd2; /* MBZ */ +#endif +} __packed; + /* /dev/mshv */ #define MSHV_CREATE_PARTITION _IOW(MSHV_IOCTL, 0x00, struct mshv_create_partition) -- cgit v1.2.3 From 491c5dc98b848c4781addd514caed95039e5366c Mon Sep 17 00:00:00 2001 From: Yael Chemla Date: Wed, 19 Nov 2025 22:48:15 +0200 Subject: net: ethtool: Add support for 1600Gbps speed Add support for 1600Gbps link modes based on 200Gbps per lane [1]. This includes the adopted IEEE 802.3dj copper and optical PMDs that use 200G/lane signaling [2]. Add the following PMD types: - KR8 (backplane) - CR8 (copper cable) - DR8 (SMF 500m) - DR8-2 (SMF 2km) These modes are defined in the 802.3dj specifications. References: [1] https://www.ieee802.org/3/dj/public/23_03/opsasnick_3dj_01a_2303.pdf [2] https://www.ieee802.org/3/dj/projdoc/objectives_P802d3dj_240314.pdf Signed-off-by: Yael Chemla Reviewed-by: Shahar Shitrit Signed-off-by: Tariq Toukan Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/1763585297-1243980-2-git-send-email-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/phy/phy-caps.h | 1 + drivers/net/phy/phy-core.c | 4 +++- drivers/net/phy/phy_caps.c | 2 ++ include/uapi/linux/ethtool.h | 5 +++++ net/ethtool/common.c | 8 ++++++++ 5 files changed, 19 insertions(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/drivers/net/phy/phy-caps.h b/drivers/net/phy/phy-caps.h index b7f0c6a3037a..4951a39f3828 100644 --- a/drivers/net/phy/phy-caps.h +++ b/drivers/net/phy/phy-caps.h @@ -29,6 +29,7 @@ enum { LINK_CAPA_200000FD, LINK_CAPA_400000FD, LINK_CAPA_800000FD, + LINK_CAPA_1600000FD, __LINK_CAPA_MAX, }; diff --git a/drivers/net/phy/phy-core.c b/drivers/net/phy/phy-core.c index 0c63e6ba2cb0..277c034bc32f 100644 --- a/drivers/net/phy/phy-core.c +++ b/drivers/net/phy/phy-core.c @@ -17,7 +17,7 @@ */ const char *phy_speed_to_str(int speed) { - BUILD_BUG_ON_MSG(__ETHTOOL_LINK_MODE_MASK_NBITS != 121, + BUILD_BUG_ON_MSG(__ETHTOOL_LINK_MODE_MASK_NBITS != 125, "Enum ethtool_link_mode_bit_indices and phylib are out of sync. " "If a speed or mode has been added please update phy_speed_to_str " "and the PHY settings array.\n"); @@ -55,6 +55,8 @@ const char *phy_speed_to_str(int speed) return "400Gbps"; case SPEED_800000: return "800Gbps"; + case SPEED_1600000: + return "1600Gbps"; case SPEED_UNKNOWN: return "Unknown"; default: diff --git a/drivers/net/phy/phy_caps.c b/drivers/net/phy/phy_caps.c index 23c808b59b6f..3a05982b39bf 100644 --- a/drivers/net/phy/phy_caps.c +++ b/drivers/net/phy/phy_caps.c @@ -25,6 +25,7 @@ static struct link_capabilities link_caps[__LINK_CAPA_MAX] __ro_after_init = { { SPEED_200000, DUPLEX_FULL, {0} }, /* LINK_CAPA_200000FD */ { SPEED_400000, DUPLEX_FULL, {0} }, /* LINK_CAPA_400000FD */ { SPEED_800000, DUPLEX_FULL, {0} }, /* LINK_CAPA_800000FD */ + { SPEED_1600000, DUPLEX_FULL, {0} }, /* LINK_CAPA_1600000FD */ }; static int speed_duplex_to_capa(int speed, unsigned int duplex) @@ -52,6 +53,7 @@ static int speed_duplex_to_capa(int speed, unsigned int duplex) case SPEED_200000: return LINK_CAPA_200000FD; case SPEED_400000: return LINK_CAPA_400000FD; case SPEED_800000: return LINK_CAPA_800000FD; + case SPEED_1600000: return LINK_CAPA_1600000FD; } return -EINVAL; diff --git a/include/uapi/linux/ethtool.h b/include/uapi/linux/ethtool.h index 8bd5ea5469d9..eb7ff2602fbb 100644 --- a/include/uapi/linux/ethtool.h +++ b/include/uapi/linux/ethtool.h @@ -2077,6 +2077,10 @@ enum ethtool_link_mode_bit_indices { ETHTOOL_LINK_MODE_800000baseDR4_2_Full_BIT = 118, ETHTOOL_LINK_MODE_800000baseSR4_Full_BIT = 119, ETHTOOL_LINK_MODE_800000baseVR4_Full_BIT = 120, + ETHTOOL_LINK_MODE_1600000baseCR8_Full_BIT = 121, + ETHTOOL_LINK_MODE_1600000baseKR8_Full_BIT = 122, + ETHTOOL_LINK_MODE_1600000baseDR8_Full_BIT = 123, + ETHTOOL_LINK_MODE_1600000baseDR8_2_Full_BIT = 124, /* must be last entry */ __ETHTOOL_LINK_MODE_MASK_NBITS @@ -2190,6 +2194,7 @@ enum ethtool_link_mode_bit_indices { #define SPEED_200000 200000 #define SPEED_400000 400000 #define SPEED_800000 800000 +#define SPEED_1600000 1600000 #define SPEED_UNKNOWN -1 diff --git a/net/ethtool/common.c b/net/ethtool/common.c index 55223ebc2a7e..369c05cf8163 100644 --- a/net/ethtool/common.c +++ b/net/ethtool/common.c @@ -233,6 +233,10 @@ const char link_mode_names[][ETH_GSTRING_LEN] = { __DEFINE_LINK_MODE_NAME(800000, DR4_2, Full), __DEFINE_LINK_MODE_NAME(800000, SR4, Full), __DEFINE_LINK_MODE_NAME(800000, VR4, Full), + __DEFINE_LINK_MODE_NAME(1600000, CR8, Full), + __DEFINE_LINK_MODE_NAME(1600000, KR8, Full), + __DEFINE_LINK_MODE_NAME(1600000, DR8, Full), + __DEFINE_LINK_MODE_NAME(1600000, DR8_2, Full), }; static_assert(ARRAY_SIZE(link_mode_names) == __ETHTOOL_LINK_MODE_MASK_NBITS); @@ -422,6 +426,10 @@ const struct link_mode_info link_mode_params[] = { __DEFINE_LINK_MODE_PARAMS(800000, DR4_2, Full), __DEFINE_LINK_MODE_PARAMS(800000, SR4, Full), __DEFINE_LINK_MODE_PARAMS(800000, VR4, Full), + __DEFINE_LINK_MODE_PARAMS(1600000, CR8, Full), + __DEFINE_LINK_MODE_PARAMS(1600000, KR8, Full), + __DEFINE_LINK_MODE_PARAMS(1600000, DR8, Full), + __DEFINE_LINK_MODE_PARAMS(1600000, DR8_2, Full), }; static_assert(ARRAY_SIZE(link_mode_params) == __ETHTOOL_LINK_MODE_MASK_NBITS); EXPORT_SYMBOL_GPL(link_mode_params); -- cgit v1.2.3 From 2a367002ed321e884276c3d7232a362ddd1bf7d6 Mon Sep 17 00:00:00 2001 From: Daniel Zahka Date: Tue, 18 Nov 2025 18:50:33 -0800 Subject: devlink: support default values for param-get and param-set Support querying and resetting to default param values. Introduce two new devlink netlink attrs: DEVLINK_ATTR_PARAM_VALUE_DEFAULT and DEVLINK_ATTR_PARAM_RESET_DEFAULT. The former is used to contain an optional parameter value inside of the param_value nested attribute. The latter is used in param-set requests from userspace to indicate that the driver should reset the param to its default value. To implement this, two new functions are added to the devlink driver api: devlink_param::get_default() and devlink_param::reset_default(). These callbacks allow drivers to implement default param actions for runtime and permanent cmodes. For driverinit params, the core latches the last value set by a driver via devl_param_driverinit_value_set(), and uses that as the default value for a param. Because default parameter values are optional, it would be impossible to discern whether or not a param of type bool has default value of false or not provided if the default value is encoded using a netlink flag type. For this reason, when a DEVLINK_PARAM_TYPE_BOOL has an associated default value, the default value is encoded using a u8 type. Signed-off-by: Daniel Zahka Link: https://patch.msgid.link/20251119025038.651131-4-daniel.zahka@gmail.com Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/devlink.yaml | 9 ++ .../networking/devlink/devlink-params.rst | 10 ++ include/net/devlink.h | 42 +++++++++ include/uapi/linux/devlink.h | 3 + net/devlink/netlink_gen.c | 5 +- net/devlink/param.c | 105 ++++++++++++++++++--- 6 files changed, 160 insertions(+), 14 deletions(-) (limited to 'include/uapi/linux') diff --git a/Documentation/netlink/specs/devlink.yaml b/Documentation/netlink/specs/devlink.yaml index 426d5aa7d955..837112da6738 100644 --- a/Documentation/netlink/specs/devlink.yaml +++ b/Documentation/netlink/specs/devlink.yaml @@ -859,6 +859,14 @@ attribute-sets: name: health-reporter-burst-period type: u64 doc: Time (in msec) for recoveries before starting the grace period. + + # TODO: fill in the attributes in between + + - + name: param-reset-default + type: flag + doc: Request restoring parameter to its default value. + value: 183 - name: dl-dev-stats subset-of: devlink @@ -1793,6 +1801,7 @@ operations: - param-type # param-value-data is missing here as the type is variable - param-value-cmode + - param-reset-default - name: region-get diff --git a/Documentation/networking/devlink/devlink-params.rst b/Documentation/networking/devlink/devlink-params.rst index c0597d456641..ea17756dcda6 100644 --- a/Documentation/networking/devlink/devlink-params.rst +++ b/Documentation/networking/devlink/devlink-params.rst @@ -41,6 +41,16 @@ In order for ``driverinit`` parameters to take effect, the driver must support reloading via the ``devlink-reload`` command. This command will request a reload of the device driver. +Default parameter values +========================= + +Drivers may optionally export default values for parameters of cmode +``runtime`` and ``permanent``. For ``driverinit`` parameters, the last +value set by the driver will be used as the default value. Drivers can +also support resetting params with cmode ``runtime`` and ``permanent`` +to their default values. Resetting ``driverinit`` params is supported +by devlink core without additional driver support needed. + .. _devlink_params_generic: Generic configuration parameters diff --git a/include/net/devlink.h b/include/net/devlink.h index 5f479227144d..cb839e0435a1 100644 --- a/include/net/devlink.h +++ b/include/net/devlink.h @@ -479,6 +479,10 @@ struct devlink_flash_notify { * @set: set parameter value, used for runtime and permanent * configuration modes * @validate: validate input value is applicable (within value range, etc.) + * @get_default: get parameter default value, used for runtime and permanent + * configuration modes + * @reset_default: reset parameter to default value, used for runtime and permanent + * configuration modes * * This struct should be used by the driver to fill the data for * a parameter it registers. @@ -498,6 +502,12 @@ struct devlink_param { int (*validate)(struct devlink *devlink, u32 id, union devlink_param_value val, struct netlink_ext_ack *extack); + int (*get_default)(struct devlink *devlink, u32 id, + struct devlink_param_gset_ctx *ctx, + struct netlink_ext_ack *extack); + int (*reset_default)(struct devlink *devlink, u32 id, + enum devlink_param_cmode cmode, + struct netlink_ext_ack *extack); }; struct devlink_param_item { @@ -509,6 +519,7 @@ struct devlink_param_item { * until reload. */ bool driverinit_value_new_valid; + union devlink_param_value driverinit_default; }; enum devlink_param_generic_id { @@ -630,6 +641,37 @@ enum devlink_param_generic_id { .validate = _validate, \ } +#define DEVLINK_PARAM_GENERIC_WITH_DEFAULTS(_id, _cmodes, _get, _set, \ + _validate, _get_default, \ + _reset_default) \ +{ \ + .id = DEVLINK_PARAM_GENERIC_ID_##_id, \ + .name = DEVLINK_PARAM_GENERIC_##_id##_NAME, \ + .type = DEVLINK_PARAM_GENERIC_##_id##_TYPE, \ + .generic = true, \ + .supported_cmodes = _cmodes, \ + .get = _get, \ + .set = _set, \ + .validate = _validate, \ + .get_default = _get_default, \ + .reset_default = _reset_default, \ +} + +#define DEVLINK_PARAM_DRIVER_WITH_DEFAULTS(_id, _name, _type, _cmodes, \ + _get, _set, _validate, \ + _get_default, _reset_default) \ +{ \ + .id = _id, \ + .name = _name, \ + .type = _type, \ + .supported_cmodes = _cmodes, \ + .get = _get, \ + .set = _set, \ + .validate = _validate, \ + .get_default = _get_default, \ + .reset_default = _reset_default, \ +} + /* Identifier of board design */ #define DEVLINK_INFO_VERSION_GENERIC_BOARD_ID "board.id" /* Revision of board design */ diff --git a/include/uapi/linux/devlink.h b/include/uapi/linux/devlink.h index 157f11d3fb72..e7d6b6d13470 100644 --- a/include/uapi/linux/devlink.h +++ b/include/uapi/linux/devlink.h @@ -639,6 +639,9 @@ enum devlink_attr { DEVLINK_ATTR_HEALTH_REPORTER_BURST_PERIOD, /* u64 */ + DEVLINK_ATTR_PARAM_VALUE_DEFAULT, /* dynamic */ + DEVLINK_ATTR_PARAM_RESET_DEFAULT, /* flag */ + /* Add new attributes above here, update the spec in * Documentation/netlink/specs/devlink.yaml and re-generate * net/devlink/netlink_gen.c. diff --git a/net/devlink/netlink_gen.c b/net/devlink/netlink_gen.c index 5ad435aee29d..580985025f49 100644 --- a/net/devlink/netlink_gen.c +++ b/net/devlink/netlink_gen.c @@ -301,12 +301,13 @@ static const struct nla_policy devlink_param_get_dump_nl_policy[DEVLINK_ATTR_DEV }; /* DEVLINK_CMD_PARAM_SET - do */ -static const struct nla_policy devlink_param_set_nl_policy[DEVLINK_ATTR_PARAM_VALUE_CMODE + 1] = { +static const struct nla_policy devlink_param_set_nl_policy[DEVLINK_ATTR_PARAM_RESET_DEFAULT + 1] = { [DEVLINK_ATTR_BUS_NAME] = { .type = NLA_NUL_STRING, }, [DEVLINK_ATTR_DEV_NAME] = { .type = NLA_NUL_STRING, }, [DEVLINK_ATTR_PARAM_NAME] = { .type = NLA_NUL_STRING, }, [DEVLINK_ATTR_PARAM_TYPE] = NLA_POLICY_VALIDATE_FN(NLA_U8, &devlink_attr_param_type_validate), [DEVLINK_ATTR_PARAM_VALUE_CMODE] = NLA_POLICY_MAX(NLA_U8, 2), + [DEVLINK_ATTR_PARAM_RESET_DEFAULT] = { .type = NLA_FLAG, }, }; /* DEVLINK_CMD_REGION_GET - do */ @@ -919,7 +920,7 @@ const struct genl_split_ops devlink_nl_ops[74] = { .doit = devlink_nl_param_set_doit, .post_doit = devlink_nl_post_doit, .policy = devlink_param_set_nl_policy, - .maxattr = DEVLINK_ATTR_PARAM_VALUE_CMODE, + .maxattr = DEVLINK_ATTR_PARAM_RESET_DEFAULT, .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, }, { diff --git a/net/devlink/param.c b/net/devlink/param.c index 3aa14ef345f0..e0ea93eded43 100644 --- a/net/devlink/param.c +++ b/net/devlink/param.c @@ -192,9 +192,32 @@ static int devlink_param_set(struct devlink *devlink, return param->set(devlink, param->id, ctx, extack); } +static int devlink_param_get_default(struct devlink *devlink, + const struct devlink_param *param, + struct devlink_param_gset_ctx *ctx, + struct netlink_ext_ack *extack) +{ + if (!param->get_default) + return -EOPNOTSUPP; + + return param->get_default(devlink, param->id, ctx, extack); +} + +static int devlink_param_reset_default(struct devlink *devlink, + const struct devlink_param *param, + enum devlink_param_cmode cmode, + struct netlink_ext_ack *extack) +{ + if (!param->reset_default) + return -EOPNOTSUPP; + + return param->reset_default(devlink, param->id, cmode, extack); +} + static int devlink_nl_param_value_put(struct sk_buff *msg, enum devlink_param_type type, - int nla_type, union devlink_param_value val) + int nla_type, union devlink_param_value val, + bool flag_as_u8) { switch (type) { case DEVLINK_PARAM_TYPE_U8: @@ -218,8 +241,16 @@ devlink_nl_param_value_put(struct sk_buff *msg, enum devlink_param_type type, return -EMSGSIZE; break; case DEVLINK_PARAM_TYPE_BOOL: - if (val.vbool && nla_put_flag(msg, nla_type)) - return -EMSGSIZE; + /* default values of type bool are encoded with u8, so that + * false can be distinguished from not present + */ + if (flag_as_u8) { + if (nla_put_u8(msg, nla_type, val.vbool)) + return -EMSGSIZE; + } else { + if (val.vbool && nla_put_flag(msg, nla_type)) + return -EMSGSIZE; + } break; } return 0; @@ -229,7 +260,9 @@ static int devlink_nl_param_value_fill_one(struct sk_buff *msg, enum devlink_param_type type, enum devlink_param_cmode cmode, - union devlink_param_value val) + union devlink_param_value val, + union devlink_param_value default_val, + bool has_default) { struct nlattr *param_value_attr; int err = -EMSGSIZE; @@ -243,10 +276,19 @@ devlink_nl_param_value_fill_one(struct sk_buff *msg, goto value_nest_cancel; err = devlink_nl_param_value_put(msg, type, - DEVLINK_ATTR_PARAM_VALUE_DATA, val); + DEVLINK_ATTR_PARAM_VALUE_DATA, + val, false); if (err) goto value_nest_cancel; + if (has_default) { + err = devlink_nl_param_value_put(msg, type, + DEVLINK_ATTR_PARAM_VALUE_DEFAULT, + default_val, true); + if (err) + goto value_nest_cancel; + } + nla_nest_end(msg, param_value_attr); return 0; @@ -262,7 +304,9 @@ static int devlink_nl_param_fill(struct sk_buff *msg, struct devlink *devlink, u32 portid, u32 seq, int flags, struct netlink_ext_ack *extack) { + union devlink_param_value default_value[DEVLINK_PARAM_CMODE_MAX + 1]; union devlink_param_value param_value[DEVLINK_PARAM_CMODE_MAX + 1]; + bool default_value_set[DEVLINK_PARAM_CMODE_MAX + 1] = {}; bool param_value_set[DEVLINK_PARAM_CMODE_MAX + 1] = {}; const struct devlink_param *param = param_item->param; struct devlink_param_gset_ctx ctx; @@ -283,12 +327,26 @@ static int devlink_nl_param_fill(struct sk_buff *msg, struct devlink *devlink, param_value[i] = param_item->driverinit_value; else return -EOPNOTSUPP; + + if (param_item->driverinit_value_valid) { + default_value[i] = param_item->driverinit_default; + default_value_set[i] = true; + } } else { ctx.cmode = i; err = devlink_param_get(devlink, param, &ctx, extack); if (err) return err; param_value[i] = ctx.val; + + err = devlink_param_get_default(devlink, param, &ctx, + extack); + if (!err) { + default_value[i] = ctx.val; + default_value_set[i] = true; + } else if (err != -EOPNOTSUPP) { + return err; + } } param_value_set[i] = true; } @@ -325,7 +383,9 @@ static int devlink_nl_param_fill(struct sk_buff *msg, struct devlink *devlink, if (!param_value_set[i]) continue; err = devlink_nl_param_value_fill_one(msg, param->type, - i, param_value[i]); + i, param_value[i], + default_value[i], + default_value_set[i]); if (err) goto values_list_nest_cancel; } @@ -542,6 +602,7 @@ static int __devlink_nl_cmd_param_set_doit(struct devlink *devlink, struct devlink_param_item *param_item; const struct devlink_param *param; union devlink_param_value value; + bool reset_default; int err = 0; param_item = devlink_param_get_from_info(params, info); @@ -553,13 +614,18 @@ static int __devlink_nl_cmd_param_set_doit(struct devlink *devlink, return err; if (param_type != param->type) return -EINVAL; - err = devlink_param_value_get_from_info(param, info, &value); - if (err) - return err; - if (param->validate) { - err = param->validate(devlink, param->id, value, info->extack); + + reset_default = info->attrs[DEVLINK_ATTR_PARAM_RESET_DEFAULT]; + if (!reset_default) { + err = devlink_param_value_get_from_info(param, info, &value); if (err) return err; + if (param->validate) { + err = param->validate(devlink, param->id, value, + info->extack); + if (err) + return err; + } } if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_PARAM_VALUE_CMODE)) @@ -569,6 +635,15 @@ static int __devlink_nl_cmd_param_set_doit(struct devlink *devlink, return -EOPNOTSUPP; if (cmode == DEVLINK_PARAM_CMODE_DRIVERINIT) { + if (reset_default) { + if (!param_item->driverinit_value_valid) { + NL_SET_ERR_MSG(info->extack, + "Default value not available"); + return -EOPNOTSUPP; + } + value = param_item->driverinit_default; + } + param_item->driverinit_value_new = value; param_item->driverinit_value_new_valid = true; } else { @@ -576,7 +651,12 @@ static int __devlink_nl_cmd_param_set_doit(struct devlink *devlink, return -EOPNOTSUPP; ctx.val = value; ctx.cmode = cmode; - err = devlink_param_set(devlink, param, &ctx, info->extack); + if (reset_default) + err = devlink_param_reset_default(devlink, param, cmode, + info->extack); + else + err = devlink_param_set(devlink, param, &ctx, + info->extack); if (err) return err; } @@ -824,6 +904,7 @@ void devl_param_driverinit_value_set(struct devlink *devlink, u32 param_id, param_item->driverinit_value = init_val; param_item->driverinit_value_valid = true; + param_item->driverinit_default = init_val; devlink_param_notify(devlink, 0, param_item, DEVLINK_CMD_PARAM_NEW); } -- cgit v1.2.3 From 5d74781ebc86c5fa9e9d6934024c505412de9b52 Mon Sep 17 00:00:00 2001 From: Leon Romanovsky Date: Thu, 20 Nov 2025 11:28:29 +0200 Subject: vfio/pci: Add dma-buf export support for MMIO regions Add support for exporting PCI device MMIO regions through dma-buf, enabling safe sharing of non-struct page memory with controlled lifetime management. This allows RDMA and other subsystems to import dma-buf FDs and build them into memory regions for PCI P2P operations. The implementation provides a revocable attachment mechanism using dma-buf move operations. MMIO regions are normally pinned as BARs don't change physical addresses, but access is revoked when the VFIO device is closed or a PCI reset is issued. This ensures kernel self-defense against potentially hostile userspace. Currently VFIO can take MMIO regions from the device's BAR and map them into a PFNMAP VMA with special PTEs. This mapping type ensures the memory cannot be used with things like pin_user_pages(), hmm, and so on. In practice only the user process CPU and KVM can safely make use of these VMA. When VFIO shuts down these VMAs are cleaned by unmap_mapping_range() to prevent any UAF of the MMIO beyond driver unbind. However, VFIO type 1 has an insecure behavior where it uses follow_pfnmap_*() to fish a MMIO PFN out of a VMA and program it back into the IOMMU. This has a long history of enabling P2P DMA inside VMs, but has serious lifetime problems by allowing a UAF of the MMIO after the VFIO driver has been unbound. Introduce DMABUF as a new safe way to export a FD based handle for the MMIO regions. This can be consumed by existing DMABUF importers like RDMA or DRM without opening an UAF. A following series will add an importer to iommufd to obsolete the type 1 code and allow safe UAF-free MMIO P2P in VM cases. DMABUF has a built in synchronous invalidation mechanism called move_notify. VFIO keeps track of all drivers importing its MMIO and can invoke a synchronous invalidation callback to tell the importing drivers to DMA unmap and forget about the MMIO pfns. This process is being called revoke. This synchronous invalidation fully prevents any lifecycle problems. VFIO will do this before unbinding its driver ensuring there is no UAF of the MMIO beyond the driver lifecycle. Further, VFIO has additional behavior to block access to the MMIO during things like Function Level Reset. This is because some poor platforms may experience a MCE type crash when touching MMIO of a PCI device that is undergoing a reset. Today this is done by using unmap_mapping_range() on the VMAs. Extend that into the DMABUF world and temporarily revoke the MMIO from the DMABUF importers during FLR as well. This will more robustly prevent an errant P2P from possibly upsetting the platform. A DMABUF FD is a preferred handle for MMIO compared to using something like a pgmap because: - VFIO is supported, including its P2P feature, on archs that don't support pgmap - PCI devices have all sorts of BAR sizes, including ones smaller than a section so a pgmap cannot always be created - It is undesirable to waste a lot of memory for struct pages, especially for a case like a GPU with ~100GB of BAR size - We want a synchronous revoke semantic to support FLR with light hardware requirements Use the P2P subsystem to help generate the DMA mapping. This is a significant upgrade over the abuse of dma_map_resource() that has historically been used by DMABUF exporters. Experience with an OOT version of this patch shows that real systems do need this. This approach deals with all the P2P scenarios: - Non-zero PCI bus_offset - ACS flags routing traffic to the IOMMU - ACS flags that bypass the IOMMU - though vfio noiommu is required to hit this. There will be further work to formalize the revoke semantic in DMABUF. For now this acts like a move_notify dynamic exporter where importer fault handling will get a failure when they attempt to map. This means that only fully restartable fault capable importers can import the VFIO DMABUFs. A future revoke semantic should open this up to more HW as the HW only needs to invalidate, not handle restartable faults. Signed-off-by: Jason Gunthorpe Signed-off-by: Vivek Kasireddy Reviewed-by: Kevin Tian Signed-off-by: Leon Romanovsky Acked-by: Ankit Agrawal Link: https://lore.kernel.org/r/20251120-dmabuf-vfio-v9-10-d7f71607f371@nvidia.com Signed-off-by: Alex Williamson --- drivers/vfio/pci/Kconfig | 3 + drivers/vfio/pci/Makefile | 1 + drivers/vfio/pci/vfio_pci.c | 5 + drivers/vfio/pci/vfio_pci_config.c | 22 ++- drivers/vfio/pci/vfio_pci_core.c | 18 ++- drivers/vfio/pci/vfio_pci_dmabuf.c | 316 +++++++++++++++++++++++++++++++++++++ drivers/vfio/pci/vfio_pci_priv.h | 23 +++ include/linux/vfio_pci_core.h | 42 +++++ include/uapi/linux/vfio.h | 28 ++++ 9 files changed, 453 insertions(+), 5 deletions(-) create mode 100644 drivers/vfio/pci/vfio_pci_dmabuf.c (limited to 'include/uapi/linux') diff --git a/drivers/vfio/pci/Kconfig b/drivers/vfio/pci/Kconfig index 2b0172f54665..2b9fca00e9e8 100644 --- a/drivers/vfio/pci/Kconfig +++ b/drivers/vfio/pci/Kconfig @@ -55,6 +55,9 @@ config VFIO_PCI_ZDEV_KVM To enable s390x KVM vfio-pci extensions, say Y. +config VFIO_PCI_DMABUF + def_bool y if VFIO_PCI_CORE && PCI_P2PDMA && DMA_SHARED_BUFFER + source "drivers/vfio/pci/mlx5/Kconfig" source "drivers/vfio/pci/hisilicon/Kconfig" diff --git a/drivers/vfio/pci/Makefile b/drivers/vfio/pci/Makefile index cf00c0a7e55c..53f59226ae01 100644 --- a/drivers/vfio/pci/Makefile +++ b/drivers/vfio/pci/Makefile @@ -2,6 +2,7 @@ vfio-pci-core-y := vfio_pci_core.o vfio_pci_intrs.o vfio_pci_rdwr.o vfio_pci_config.o vfio-pci-core-$(CONFIG_VFIO_PCI_ZDEV_KVM) += vfio_pci_zdev.o +vfio-pci-core-$(CONFIG_VFIO_PCI_DMABUF) += vfio_pci_dmabuf.o obj-$(CONFIG_VFIO_PCI_CORE) += vfio-pci-core.o vfio-pci-y := vfio_pci.o diff --git a/drivers/vfio/pci/vfio_pci.c b/drivers/vfio/pci/vfio_pci.c index ac10f14417f2..6d41cf26b539 100644 --- a/drivers/vfio/pci/vfio_pci.c +++ b/drivers/vfio/pci/vfio_pci.c @@ -147,6 +147,10 @@ static const struct vfio_device_ops vfio_pci_ops = { .pasid_detach_ioas = vfio_iommufd_physical_pasid_detach_ioas, }; +static const struct vfio_pci_device_ops vfio_pci_dev_ops = { + .get_dmabuf_phys = vfio_pci_core_get_dmabuf_phys, +}; + static int vfio_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) { struct vfio_pci_core_device *vdev; @@ -161,6 +165,7 @@ static int vfio_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) return PTR_ERR(vdev); dev_set_drvdata(&pdev->dev, vdev); + vdev->pci_ops = &vfio_pci_dev_ops; ret = vfio_pci_core_register_device(vdev); if (ret) goto out_put_vdev; diff --git a/drivers/vfio/pci/vfio_pci_config.c b/drivers/vfio/pci/vfio_pci_config.c index 8f02f236b5b4..1f6008eabf23 100644 --- a/drivers/vfio/pci/vfio_pci_config.c +++ b/drivers/vfio/pci/vfio_pci_config.c @@ -589,10 +589,12 @@ static int vfio_basic_config_write(struct vfio_pci_core_device *vdev, int pos, virt_mem = !!(le16_to_cpu(*virt_cmd) & PCI_COMMAND_MEMORY); new_mem = !!(new_cmd & PCI_COMMAND_MEMORY); - if (!new_mem) + if (!new_mem) { vfio_pci_zap_and_down_write_memory_lock(vdev); - else + vfio_pci_dma_buf_move(vdev, true); + } else { down_write(&vdev->memory_lock); + } /* * If the user is writing mem/io enable (new_mem/io) and we @@ -627,6 +629,8 @@ static int vfio_basic_config_write(struct vfio_pci_core_device *vdev, int pos, *virt_cmd &= cpu_to_le16(~mask); *virt_cmd |= cpu_to_le16(new_cmd & mask); + if (__vfio_pci_memory_enabled(vdev)) + vfio_pci_dma_buf_move(vdev, false); up_write(&vdev->memory_lock); } @@ -707,12 +711,16 @@ static int __init init_pci_cap_basic_perm(struct perm_bits *perm) static void vfio_lock_and_set_power_state(struct vfio_pci_core_device *vdev, pci_power_t state) { - if (state >= PCI_D3hot) + if (state >= PCI_D3hot) { vfio_pci_zap_and_down_write_memory_lock(vdev); - else + vfio_pci_dma_buf_move(vdev, true); + } else { down_write(&vdev->memory_lock); + } vfio_pci_set_power_state(vdev, state); + if (__vfio_pci_memory_enabled(vdev)) + vfio_pci_dma_buf_move(vdev, false); up_write(&vdev->memory_lock); } @@ -900,7 +908,10 @@ static int vfio_exp_config_write(struct vfio_pci_core_device *vdev, int pos, if (!ret && (cap & PCI_EXP_DEVCAP_FLR)) { vfio_pci_zap_and_down_write_memory_lock(vdev); + vfio_pci_dma_buf_move(vdev, true); pci_try_reset_function(vdev->pdev); + if (__vfio_pci_memory_enabled(vdev)) + vfio_pci_dma_buf_move(vdev, false); up_write(&vdev->memory_lock); } } @@ -982,7 +993,10 @@ static int vfio_af_config_write(struct vfio_pci_core_device *vdev, int pos, if (!ret && (cap & PCI_AF_CAP_FLR) && (cap & PCI_AF_CAP_TP)) { vfio_pci_zap_and_down_write_memory_lock(vdev); + vfio_pci_dma_buf_move(vdev, true); pci_try_reset_function(vdev->pdev); + if (__vfio_pci_memory_enabled(vdev)) + vfio_pci_dma_buf_move(vdev, false); up_write(&vdev->memory_lock); } } diff --git a/drivers/vfio/pci/vfio_pci_core.c b/drivers/vfio/pci/vfio_pci_core.c index 142b84b3f225..9449cf44c18a 100644 --- a/drivers/vfio/pci/vfio_pci_core.c +++ b/drivers/vfio/pci/vfio_pci_core.c @@ -287,6 +287,8 @@ static int vfio_pci_runtime_pm_entry(struct vfio_pci_core_device *vdev, * semaphore. */ vfio_pci_zap_and_down_write_memory_lock(vdev); + vfio_pci_dma_buf_move(vdev, true); + if (vdev->pm_runtime_engaged) { up_write(&vdev->memory_lock); return -EINVAL; @@ -370,6 +372,8 @@ static void vfio_pci_runtime_pm_exit(struct vfio_pci_core_device *vdev) */ down_write(&vdev->memory_lock); __vfio_pci_runtime_pm_exit(vdev); + if (__vfio_pci_memory_enabled(vdev)) + vfio_pci_dma_buf_move(vdev, false); up_write(&vdev->memory_lock); } @@ -690,6 +694,8 @@ void vfio_pci_core_close_device(struct vfio_device *core_vdev) #endif vfio_pci_core_disable(vdev); + vfio_pci_dma_buf_cleanup(vdev); + mutex_lock(&vdev->igate); if (vdev->err_trigger) { eventfd_ctx_put(vdev->err_trigger); @@ -1222,7 +1228,10 @@ static int vfio_pci_ioctl_reset(struct vfio_pci_core_device *vdev, */ vfio_pci_set_power_state(vdev, PCI_D0); + vfio_pci_dma_buf_move(vdev, true); ret = pci_try_reset_function(vdev->pdev); + if (__vfio_pci_memory_enabled(vdev)) + vfio_pci_dma_buf_move(vdev, false); up_write(&vdev->memory_lock); return ret; @@ -1511,6 +1520,8 @@ int vfio_pci_core_ioctl_feature(struct vfio_device *device, u32 flags, return vfio_pci_core_pm_exit(vdev, flags, arg, argsz); case VFIO_DEVICE_FEATURE_PCI_VF_TOKEN: return vfio_pci_core_feature_token(vdev, flags, arg, argsz); + case VFIO_DEVICE_FEATURE_DMA_BUF: + return vfio_pci_core_feature_dma_buf(vdev, flags, arg, argsz); default: return -ENOTTY; } @@ -2095,6 +2106,7 @@ int vfio_pci_core_init_dev(struct vfio_device *core_vdev) ret = pcim_p2pdma_init(vdev->pdev); if (ret && ret != -EOPNOTSUPP) return ret; + INIT_LIST_HEAD(&vdev->dmabufs); init_rwsem(&vdev->memory_lock); xa_init(&vdev->ctx); @@ -2459,6 +2471,7 @@ static int vfio_pci_dev_set_hot_reset(struct vfio_device_set *dev_set, break; } + vfio_pci_dma_buf_move(vdev, true); vfio_pci_zap_bars(vdev); } @@ -2487,8 +2500,11 @@ static int vfio_pci_dev_set_hot_reset(struct vfio_device_set *dev_set, err_undo: list_for_each_entry_from_reverse(vdev, &dev_set->device_list, - vdev.dev_set_list) + vdev.dev_set_list) { + if (vdev->vdev.open_count && __vfio_pci_memory_enabled(vdev)) + vfio_pci_dma_buf_move(vdev, false); up_write(&vdev->memory_lock); + } list_for_each_entry(vdev, &dev_set->device_list, vdev.dev_set_list) pm_runtime_put(&vdev->pdev->dev); diff --git a/drivers/vfio/pci/vfio_pci_dmabuf.c b/drivers/vfio/pci/vfio_pci_dmabuf.c new file mode 100644 index 000000000000..6698f540bdac --- /dev/null +++ b/drivers/vfio/pci/vfio_pci_dmabuf.c @@ -0,0 +1,316 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. + */ +#include +#include +#include + +#include "vfio_pci_priv.h" + +MODULE_IMPORT_NS("DMA_BUF"); + +struct vfio_pci_dma_buf { + struct dma_buf *dmabuf; + struct vfio_pci_core_device *vdev; + struct list_head dmabufs_elm; + size_t size; + struct dma_buf_phys_vec *phys_vec; + struct p2pdma_provider *provider; + u32 nr_ranges; + u8 revoked : 1; +}; + +static int vfio_pci_dma_buf_attach(struct dma_buf *dmabuf, + struct dma_buf_attachment *attachment) +{ + struct vfio_pci_dma_buf *priv = dmabuf->priv; + + if (!attachment->peer2peer) + return -EOPNOTSUPP; + + if (priv->revoked) + return -ENODEV; + + return 0; +} + +static struct sg_table * +vfio_pci_dma_buf_map(struct dma_buf_attachment *attachment, + enum dma_data_direction dir) +{ + struct vfio_pci_dma_buf *priv = attachment->dmabuf->priv; + + dma_resv_assert_held(priv->dmabuf->resv); + + if (priv->revoked) + return ERR_PTR(-ENODEV); + + return dma_buf_phys_vec_to_sgt(attachment, priv->provider, + priv->phys_vec, priv->nr_ranges, + priv->size, dir); +} + +static void vfio_pci_dma_buf_unmap(struct dma_buf_attachment *attachment, + struct sg_table *sgt, + enum dma_data_direction dir) +{ + dma_buf_free_sgt(attachment, sgt, dir); +} + +static void vfio_pci_dma_buf_release(struct dma_buf *dmabuf) +{ + struct vfio_pci_dma_buf *priv = dmabuf->priv; + + /* + * Either this or vfio_pci_dma_buf_cleanup() will remove from the list. + * The refcount prevents both. + */ + if (priv->vdev) { + down_write(&priv->vdev->memory_lock); + list_del_init(&priv->dmabufs_elm); + up_write(&priv->vdev->memory_lock); + vfio_device_put_registration(&priv->vdev->vdev); + } + kfree(priv->phys_vec); + kfree(priv); +} + +static const struct dma_buf_ops vfio_pci_dmabuf_ops = { + .attach = vfio_pci_dma_buf_attach, + .map_dma_buf = vfio_pci_dma_buf_map, + .unmap_dma_buf = vfio_pci_dma_buf_unmap, + .release = vfio_pci_dma_buf_release, +}; + +int vfio_pci_core_fill_phys_vec(struct dma_buf_phys_vec *phys_vec, + struct vfio_region_dma_range *dma_ranges, + size_t nr_ranges, phys_addr_t start, + phys_addr_t len) +{ + phys_addr_t max_addr; + unsigned int i; + + max_addr = start + len; + for (i = 0; i < nr_ranges; i++) { + phys_addr_t end; + + if (!dma_ranges[i].length) + return -EINVAL; + + if (check_add_overflow(start, dma_ranges[i].offset, + &phys_vec[i].paddr) || + check_add_overflow(phys_vec[i].paddr, + dma_ranges[i].length, &end)) + return -EOVERFLOW; + if (end > max_addr) + return -EINVAL; + + phys_vec[i].len = dma_ranges[i].length; + } + return 0; +} +EXPORT_SYMBOL_GPL(vfio_pci_core_fill_phys_vec); + +int vfio_pci_core_get_dmabuf_phys(struct vfio_pci_core_device *vdev, + struct p2pdma_provider **provider, + unsigned int region_index, + struct dma_buf_phys_vec *phys_vec, + struct vfio_region_dma_range *dma_ranges, + size_t nr_ranges) +{ + struct pci_dev *pdev = vdev->pdev; + + *provider = pcim_p2pdma_provider(pdev, region_index); + if (!*provider) + return -EINVAL; + + return vfio_pci_core_fill_phys_vec( + phys_vec, dma_ranges, nr_ranges, + pci_resource_start(pdev, region_index), + pci_resource_len(pdev, region_index)); +} +EXPORT_SYMBOL_GPL(vfio_pci_core_get_dmabuf_phys); + +static int validate_dmabuf_input(struct vfio_device_feature_dma_buf *dma_buf, + struct vfio_region_dma_range *dma_ranges, + size_t *lengthp) +{ + size_t length = 0; + u32 i; + + for (i = 0; i < dma_buf->nr_ranges; i++) { + u64 offset = dma_ranges[i].offset; + u64 len = dma_ranges[i].length; + + if (!len || !PAGE_ALIGNED(offset) || !PAGE_ALIGNED(len)) + return -EINVAL; + + if (check_add_overflow(length, len, &length)) + return -EINVAL; + } + + /* + * dma_iova_try_alloc() will WARN on if userspace proposes a size that + * is too big, eg with lots of ranges. + */ + if ((u64)(length) & DMA_IOVA_USE_SWIOTLB) + return -EINVAL; + + *lengthp = length; + return 0; +} + +int vfio_pci_core_feature_dma_buf(struct vfio_pci_core_device *vdev, u32 flags, + struct vfio_device_feature_dma_buf __user *arg, + size_t argsz) +{ + struct vfio_device_feature_dma_buf get_dma_buf = {}; + struct vfio_region_dma_range *dma_ranges; + DEFINE_DMA_BUF_EXPORT_INFO(exp_info); + struct vfio_pci_dma_buf *priv; + size_t length; + int ret; + + if (!vdev->pci_ops || !vdev->pci_ops->get_dmabuf_phys) + return -EOPNOTSUPP; + + ret = vfio_check_feature(flags, argsz, VFIO_DEVICE_FEATURE_GET, + sizeof(get_dma_buf)); + if (ret != 1) + return ret; + + if (copy_from_user(&get_dma_buf, arg, sizeof(get_dma_buf))) + return -EFAULT; + + if (!get_dma_buf.nr_ranges || get_dma_buf.flags) + return -EINVAL; + + /* + * For PCI the region_index is the BAR number like everything else. + */ + if (get_dma_buf.region_index >= VFIO_PCI_ROM_REGION_INDEX) + return -ENODEV; + + dma_ranges = memdup_array_user(&arg->dma_ranges, get_dma_buf.nr_ranges, + sizeof(*dma_ranges)); + if (IS_ERR(dma_ranges)) + return PTR_ERR(dma_ranges); + + ret = validate_dmabuf_input(&get_dma_buf, dma_ranges, &length); + if (ret) + goto err_free_ranges; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) { + ret = -ENOMEM; + goto err_free_ranges; + } + priv->phys_vec = kcalloc(get_dma_buf.nr_ranges, sizeof(*priv->phys_vec), + GFP_KERNEL); + if (!priv->phys_vec) { + ret = -ENOMEM; + goto err_free_priv; + } + + priv->vdev = vdev; + priv->nr_ranges = get_dma_buf.nr_ranges; + priv->size = length; + ret = vdev->pci_ops->get_dmabuf_phys(vdev, &priv->provider, + get_dma_buf.region_index, + priv->phys_vec, dma_ranges, + priv->nr_ranges); + if (ret) + goto err_free_phys; + + kfree(dma_ranges); + dma_ranges = NULL; + + if (!vfio_device_try_get_registration(&vdev->vdev)) { + ret = -ENODEV; + goto err_free_phys; + } + + exp_info.ops = &vfio_pci_dmabuf_ops; + exp_info.size = priv->size; + exp_info.flags = get_dma_buf.open_flags; + exp_info.priv = priv; + + priv->dmabuf = dma_buf_export(&exp_info); + if (IS_ERR(priv->dmabuf)) { + ret = PTR_ERR(priv->dmabuf); + goto err_dev_put; + } + + /* dma_buf_put() now frees priv */ + INIT_LIST_HEAD(&priv->dmabufs_elm); + down_write(&vdev->memory_lock); + dma_resv_lock(priv->dmabuf->resv, NULL); + priv->revoked = !__vfio_pci_memory_enabled(vdev); + list_add_tail(&priv->dmabufs_elm, &vdev->dmabufs); + dma_resv_unlock(priv->dmabuf->resv); + up_write(&vdev->memory_lock); + + /* + * dma_buf_fd() consumes the reference, when the file closes the dmabuf + * will be released. + */ + ret = dma_buf_fd(priv->dmabuf, get_dma_buf.open_flags); + if (ret < 0) + goto err_dma_buf; + return ret; + +err_dma_buf: + dma_buf_put(priv->dmabuf); +err_dev_put: + vfio_device_put_registration(&vdev->vdev); +err_free_phys: + kfree(priv->phys_vec); +err_free_priv: + kfree(priv); +err_free_ranges: + kfree(dma_ranges); + return ret; +} + +void vfio_pci_dma_buf_move(struct vfio_pci_core_device *vdev, bool revoked) +{ + struct vfio_pci_dma_buf *priv; + struct vfio_pci_dma_buf *tmp; + + lockdep_assert_held_write(&vdev->memory_lock); + + list_for_each_entry_safe(priv, tmp, &vdev->dmabufs, dmabufs_elm) { + if (!get_file_active(&priv->dmabuf->file)) + continue; + + if (priv->revoked != revoked) { + dma_resv_lock(priv->dmabuf->resv, NULL); + priv->revoked = revoked; + dma_buf_move_notify(priv->dmabuf); + dma_resv_unlock(priv->dmabuf->resv); + } + fput(priv->dmabuf->file); + } +} + +void vfio_pci_dma_buf_cleanup(struct vfio_pci_core_device *vdev) +{ + struct vfio_pci_dma_buf *priv; + struct vfio_pci_dma_buf *tmp; + + down_write(&vdev->memory_lock); + list_for_each_entry_safe(priv, tmp, &vdev->dmabufs, dmabufs_elm) { + if (!get_file_active(&priv->dmabuf->file)) + continue; + + dma_resv_lock(priv->dmabuf->resv, NULL); + list_del_init(&priv->dmabufs_elm); + priv->vdev = NULL; + priv->revoked = true; + dma_buf_move_notify(priv->dmabuf); + dma_resv_unlock(priv->dmabuf->resv); + vfio_device_put_registration(&vdev->vdev); + fput(priv->dmabuf->file); + } + up_write(&vdev->memory_lock); +} diff --git a/drivers/vfio/pci/vfio_pci_priv.h b/drivers/vfio/pci/vfio_pci_priv.h index a9972eacb293..28a405f8b97c 100644 --- a/drivers/vfio/pci/vfio_pci_priv.h +++ b/drivers/vfio/pci/vfio_pci_priv.h @@ -107,4 +107,27 @@ static inline bool vfio_pci_is_vga(struct pci_dev *pdev) return (pdev->class >> 8) == PCI_CLASS_DISPLAY_VGA; } +#ifdef CONFIG_VFIO_PCI_DMABUF +int vfio_pci_core_feature_dma_buf(struct vfio_pci_core_device *vdev, u32 flags, + struct vfio_device_feature_dma_buf __user *arg, + size_t argsz); +void vfio_pci_dma_buf_cleanup(struct vfio_pci_core_device *vdev); +void vfio_pci_dma_buf_move(struct vfio_pci_core_device *vdev, bool revoked); +#else +static inline int +vfio_pci_core_feature_dma_buf(struct vfio_pci_core_device *vdev, u32 flags, + struct vfio_device_feature_dma_buf __user *arg, + size_t argsz) +{ + return -ENOTTY; +} +static inline void vfio_pci_dma_buf_cleanup(struct vfio_pci_core_device *vdev) +{ +} +static inline void vfio_pci_dma_buf_move(struct vfio_pci_core_device *vdev, + bool revoked) +{ +} +#endif + #endif diff --git a/include/linux/vfio_pci_core.h b/include/linux/vfio_pci_core.h index f541044e42a2..c9466ba323fa 100644 --- a/include/linux/vfio_pci_core.h +++ b/include/linux/vfio_pci_core.h @@ -26,6 +26,8 @@ struct vfio_pci_core_device; struct vfio_pci_region; +struct p2pdma_provider; +struct dma_buf_phys_vec; struct vfio_pci_regops { ssize_t (*rw)(struct vfio_pci_core_device *vdev, char __user *buf, @@ -49,9 +51,48 @@ struct vfio_pci_region { u32 flags; }; +struct vfio_pci_device_ops { + int (*get_dmabuf_phys)(struct vfio_pci_core_device *vdev, + struct p2pdma_provider **provider, + unsigned int region_index, + struct dma_buf_phys_vec *phys_vec, + struct vfio_region_dma_range *dma_ranges, + size_t nr_ranges); +}; + +#if IS_ENABLED(CONFIG_VFIO_PCI_DMABUF) +int vfio_pci_core_fill_phys_vec(struct dma_buf_phys_vec *phys_vec, + struct vfio_region_dma_range *dma_ranges, + size_t nr_ranges, phys_addr_t start, + phys_addr_t len); +int vfio_pci_core_get_dmabuf_phys(struct vfio_pci_core_device *vdev, + struct p2pdma_provider **provider, + unsigned int region_index, + struct dma_buf_phys_vec *phys_vec, + struct vfio_region_dma_range *dma_ranges, + size_t nr_ranges); +#else +static inline int +vfio_pci_core_fill_phys_vec(struct dma_buf_phys_vec *phys_vec, + struct vfio_region_dma_range *dma_ranges, + size_t nr_ranges, phys_addr_t start, + phys_addr_t len) +{ + return -EINVAL; +} +static inline int vfio_pci_core_get_dmabuf_phys( + struct vfio_pci_core_device *vdev, struct p2pdma_provider **provider, + unsigned int region_index, struct dma_buf_phys_vec *phys_vec, + struct vfio_region_dma_range *dma_ranges, size_t nr_ranges) +{ + return -EOPNOTSUPP; +} +#endif + struct vfio_pci_core_device { struct vfio_device vdev; struct pci_dev *pdev; + const struct vfio_pci_device_ops *pci_ops; void __iomem *barmap[PCI_STD_NUM_BARS]; bool bar_mmap_supported[PCI_STD_NUM_BARS]; u8 *pci_config_map; @@ -94,6 +135,7 @@ struct vfio_pci_core_device { struct vfio_pci_core_device *sriov_pf_core_dev; struct notifier_block nb; struct rw_semaphore memory_lock; + struct list_head dmabufs; }; /* Will be exported for vfio pci drivers usage */ diff --git a/include/uapi/linux/vfio.h b/include/uapi/linux/vfio.h index 75100bf009ba..ac2329f24141 100644 --- a/include/uapi/linux/vfio.h +++ b/include/uapi/linux/vfio.h @@ -14,6 +14,7 @@ #include #include +#include #define VFIO_API_VERSION 0 @@ -1478,6 +1479,33 @@ struct vfio_device_feature_bus_master { }; #define VFIO_DEVICE_FEATURE_BUS_MASTER 10 +/** + * Upon VFIO_DEVICE_FEATURE_GET create a dma_buf fd for the + * regions selected. + * + * open_flags are the typical flags passed to open(2), eg O_RDWR, O_CLOEXEC, + * etc. offset/length specify a slice of the region to create the dmabuf from. + * nr_ranges is the total number of (P2P DMA) ranges that comprise the dmabuf. + * + * flags should be 0. + * + * Return: The fd number on success, -1 and errno is set on failure. + */ +#define VFIO_DEVICE_FEATURE_DMA_BUF 11 + +struct vfio_region_dma_range { + __u64 offset; + __u64 length; +}; + +struct vfio_device_feature_dma_buf { + __u32 region_index; + __u32 open_flags; + __u32 flags; + __u32 nr_ranges; + struct vfio_region_dma_range dma_ranges[] __counted_by(nr_ranges); +}; + /* -------- API for Type1 VFIO IOMMU -------- */ /** -- cgit v1.2.3 From 8e8678e740ecde2ae4a0404fd9b4ed2b726e236d Mon Sep 17 00:00:00 2001 From: Janosch Frank Date: Tue, 8 Jul 2025 12:57:57 +0000 Subject: KVM: s390: Add capability that forwards operation exceptions Setting KVM_CAP_S390_USER_OPEREXEC will forward all operation exceptions to user space. This also includes the 0x0000 instructions managed by KVM_CAP_S390_USER_INSTR0. It's helpful if user space wants to emulate instructions which do not (yet) have an opcode. While we're at it refine the documentation for KVM_CAP_S390_USER_INSTR0. Signed-off-by: Janosch Frank Reviewed-by: Claudio Imbrenda Acked-by: Christian Borntraeger Signed-off-by: Janosch Frank --- Documentation/virt/kvm/api.rst | 17 ++- arch/s390/include/asm/kvm_host.h | 1 + arch/s390/kvm/intercept.c | 3 + arch/s390/kvm/kvm-s390.c | 7 ++ include/uapi/linux/kvm.h | 1 + tools/testing/selftests/kvm/Makefile.kvm | 1 + tools/testing/selftests/kvm/s390/user_operexec.c | 140 +++++++++++++++++++++++ 7 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/kvm/s390/user_operexec.c (limited to 'include/uapi/linux') diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst index 72b2fae99a83..1bc2a84c59ee 100644 --- a/Documentation/virt/kvm/api.rst +++ b/Documentation/virt/kvm/api.rst @@ -7820,7 +7820,7 @@ where 0xff represents CPUs 0-7 in cluster 0. :Architectures: s390 :Parameters: none -With this capability enabled, all illegal instructions 0x0000 (2 bytes) will +With this capability enabled, the illegal instruction 0x0000 (2 bytes) will be intercepted and forwarded to user space. User space can use this mechanism e.g. to realize 2-byte software breakpoints. The kernel will not inject an operating exception for these instructions, user space has @@ -8703,6 +8703,21 @@ This capability indicate to the userspace whether a PFNMAP memory region can be safely mapped as cacheable. This relies on the presence of force write back (FWB) feature support on the hardware. +7.45 KVM_CAP_S390_USER_OPEREXEC +------------------------------- + +:Architectures: s390 +:Parameters: none + +When this capability is enabled KVM forwards all operation exceptions +that it doesn't handle itself to user space. This also includes the +0x0000 instructions managed by KVM_CAP_S390_USER_INSTR0. This is +helpful if user space wants to emulate instructions which are not +(yet) implemented in hardware. + +This capability can be enabled dynamically even if VCPUs were already +created and are running. + 8. Other capabilities. ====================== diff --git a/arch/s390/include/asm/kvm_host.h b/arch/s390/include/asm/kvm_host.h index 22cedcaea475..1e4829c70216 100644 --- a/arch/s390/include/asm/kvm_host.h +++ b/arch/s390/include/asm/kvm_host.h @@ -648,6 +648,7 @@ struct kvm_arch { int user_sigp; int user_stsi; int user_instr0; + int user_operexec; struct s390_io_adapter *adapters[MAX_S390_IO_ADAPTERS]; wait_queue_head_t ipte_wq; int ipte_lock_count; diff --git a/arch/s390/kvm/intercept.c b/arch/s390/kvm/intercept.c index c7908950c1f4..420ae62977e2 100644 --- a/arch/s390/kvm/intercept.c +++ b/arch/s390/kvm/intercept.c @@ -471,6 +471,9 @@ static int handle_operexc(struct kvm_vcpu *vcpu) if (vcpu->arch.sie_block->ipa == 0xb256) return handle_sthyi(vcpu); + if (vcpu->kvm->arch.user_operexec) + return -EOPNOTSUPP; + if (vcpu->arch.sie_block->ipa == 0 && vcpu->kvm->arch.user_instr0) return -EOPNOTSUPP; rc = read_guest_lc(vcpu, __LC_PGM_NEW_PSW, &newpsw, sizeof(psw_t)); diff --git a/arch/s390/kvm/kvm-s390.c b/arch/s390/kvm/kvm-s390.c index 70ebc54b1bb1..56d4730b7c41 100644 --- a/arch/s390/kvm/kvm-s390.c +++ b/arch/s390/kvm/kvm-s390.c @@ -606,6 +606,7 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext) case KVM_CAP_SET_GUEST_DEBUG: case KVM_CAP_S390_DIAG318: case KVM_CAP_IRQFD_RESAMPLE: + case KVM_CAP_S390_USER_OPEREXEC: r = 1; break; case KVM_CAP_SET_GUEST_DEBUG2: @@ -921,6 +922,12 @@ int kvm_vm_ioctl_enable_cap(struct kvm *kvm, struct kvm_enable_cap *cap) VM_EVENT(kvm, 3, "ENABLE: CAP_S390_CPU_TOPOLOGY %s", r ? "(not available)" : "(success)"); break; + case KVM_CAP_S390_USER_OPEREXEC: + VM_EVENT(kvm, 3, "%s", "ENABLE: CAP_S390_USER_OPEREXEC"); + kvm->arch.user_operexec = 1; + icpt_operexc_on_all_vcpus(kvm); + r = 0; + break; default: r = -EINVAL; break; diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h index 52f6000ab020..8ab07396ce3b 100644 --- a/include/uapi/linux/kvm.h +++ b/include/uapi/linux/kvm.h @@ -963,6 +963,7 @@ struct kvm_enable_cap { #define KVM_CAP_RISCV_MP_STATE_RESET 242 #define KVM_CAP_ARM_CACHEABLE_PFNMAP_SUPPORTED 243 #define KVM_CAP_GUEST_MEMFD_FLAGS 244 +#define KVM_CAP_S390_USER_OPEREXEC 245 struct kvm_irq_routing_irqchip { __u32 irqchip; diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm index 148d427ff24b..87e429206bb8 100644 --- a/tools/testing/selftests/kvm/Makefile.kvm +++ b/tools/testing/selftests/kvm/Makefile.kvm @@ -194,6 +194,7 @@ TEST_GEN_PROGS_s390 += s390/debug_test TEST_GEN_PROGS_s390 += s390/cpumodel_subfuncs_test TEST_GEN_PROGS_s390 += s390/shared_zeropage_test TEST_GEN_PROGS_s390 += s390/ucontrol_test +TEST_GEN_PROGS_s390 += s390/user_operexec TEST_GEN_PROGS_s390 += rseq_test TEST_GEN_PROGS_riscv = $(TEST_GEN_PROGS_COMMON) diff --git a/tools/testing/selftests/kvm/s390/user_operexec.c b/tools/testing/selftests/kvm/s390/user_operexec.c new file mode 100644 index 000000000000..714906c1d12a --- /dev/null +++ b/tools/testing/selftests/kvm/s390/user_operexec.c @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Test operation exception forwarding. + * + * Copyright IBM Corp. 2025 + * + * Authors: + * Janosch Frank + */ +#include "kselftest.h" +#include "kvm_util.h" +#include "test_util.h" +#include "sie.h" + +#include + +static void guest_code_instr0(void) +{ + asm(".word 0x0000"); +} + +static void test_user_instr0(void) +{ + struct kvm_vcpu *vcpu; + struct kvm_vm *vm; + int rc; + + vm = vm_create_with_one_vcpu(&vcpu, guest_code_instr0); + rc = __vm_enable_cap(vm, KVM_CAP_S390_USER_INSTR0, 0); + TEST_ASSERT_EQ(0, rc); + + vcpu_run(vcpu); + TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_S390_SIEIC); + TEST_ASSERT_EQ(vcpu->run->s390_sieic.icptcode, ICPT_OPEREXC); + TEST_ASSERT_EQ(vcpu->run->s390_sieic.ipa, 0); + + kvm_vm_free(vm); +} + +static void guest_code_user_operexec(void) +{ + asm(".word 0x0807"); +} + +static void test_user_operexec(void) +{ + struct kvm_vcpu *vcpu; + struct kvm_vm *vm; + int rc; + + vm = vm_create_with_one_vcpu(&vcpu, guest_code_user_operexec); + rc = __vm_enable_cap(vm, KVM_CAP_S390_USER_OPEREXEC, 0); + TEST_ASSERT_EQ(0, rc); + + vcpu_run(vcpu); + TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_S390_SIEIC); + TEST_ASSERT_EQ(vcpu->run->s390_sieic.icptcode, ICPT_OPEREXC); + TEST_ASSERT_EQ(vcpu->run->s390_sieic.ipa, 0x0807); + + kvm_vm_free(vm); + + /* + * Since user_operexec is the superset it can be used for the + * 0 instruction. + */ + vm = vm_create_with_one_vcpu(&vcpu, guest_code_instr0); + rc = __vm_enable_cap(vm, KVM_CAP_S390_USER_OPEREXEC, 0); + TEST_ASSERT_EQ(0, rc); + + vcpu_run(vcpu); + TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_S390_SIEIC); + TEST_ASSERT_EQ(vcpu->run->s390_sieic.icptcode, ICPT_OPEREXC); + TEST_ASSERT_EQ(vcpu->run->s390_sieic.ipa, 0); + + kvm_vm_free(vm); +} + +/* combine user_instr0 and user_operexec */ +static void test_user_operexec_combined(void) +{ + struct kvm_vcpu *vcpu; + struct kvm_vm *vm; + int rc; + + vm = vm_create_with_one_vcpu(&vcpu, guest_code_user_operexec); + rc = __vm_enable_cap(vm, KVM_CAP_S390_USER_INSTR0, 0); + TEST_ASSERT_EQ(0, rc); + rc = __vm_enable_cap(vm, KVM_CAP_S390_USER_OPEREXEC, 0); + TEST_ASSERT_EQ(0, rc); + + vcpu_run(vcpu); + TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_S390_SIEIC); + TEST_ASSERT_EQ(vcpu->run->s390_sieic.icptcode, ICPT_OPEREXC); + TEST_ASSERT_EQ(vcpu->run->s390_sieic.ipa, 0x0807); + + kvm_vm_free(vm); + + /* Reverse enablement order */ + vm = vm_create_with_one_vcpu(&vcpu, guest_code_user_operexec); + rc = __vm_enable_cap(vm, KVM_CAP_S390_USER_OPEREXEC, 0); + TEST_ASSERT_EQ(0, rc); + rc = __vm_enable_cap(vm, KVM_CAP_S390_USER_INSTR0, 0); + TEST_ASSERT_EQ(0, rc); + + vcpu_run(vcpu); + TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_S390_SIEIC); + TEST_ASSERT_EQ(vcpu->run->s390_sieic.icptcode, ICPT_OPEREXC); + TEST_ASSERT_EQ(vcpu->run->s390_sieic.ipa, 0x0807); + + kvm_vm_free(vm); +} + +/* + * Run all tests above. + * + * Enablement after VCPU has been added is automatically tested since + * we enable the capability after VCPU creation. + */ +static struct testdef { + const char *name; + void (*test)(void); +} testlist[] = { + { "instr0", test_user_instr0 }, + { "operexec", test_user_operexec }, + { "operexec_combined", test_user_operexec_combined}, +}; + +int main(int argc, char *argv[]) +{ + int idx; + + TEST_REQUIRE(kvm_has_cap(KVM_CAP_S390_USER_INSTR0)); + + ksft_print_header(); + ksft_set_plan(ARRAY_SIZE(testlist)); + for (idx = 0; idx < ARRAY_SIZE(testlist); idx++) { + testlist[idx].test(); + ksft_test_result_pass("%s\n", testlist[idx].name); + } + ksft_finished(); +} -- cgit v1.2.3 From a67df6d1b939ca98e1ad403f53e3ee57299b8c44 Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Tue, 11 Nov 2025 14:46:10 +0100 Subject: uapi: cdc.h: cleanly provide for more interfaces and countries The spec requires at least one interface respectively country. It allows multiple ones. This needs to be clearly said in the UAPI. This is subject to sanity checking in cdc_parse_cdc_header(), thus we can trust the length. Signed-off-by: Oliver Neukum Link: https://patch.msgid.link/20251111134641.4118827-1-oneukum@suse.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/cdc-acm.c | 2 +- include/uapi/linux/usb/cdc.h | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index 73f9476774ae..54be4aa1dcb2 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -1475,7 +1475,7 @@ made_compressed_probe: if (!acm->country_codes) goto skip_countries; acm->country_code_size = cfd->bLength - 4; - memcpy(acm->country_codes, (u8 *)&cfd->wCountyCode0, + memcpy(acm->country_codes, cfd->wCountryCodes, cfd->bLength - 4); acm->country_rel_date = cfd->iCountryCodeRelDate; diff --git a/include/uapi/linux/usb/cdc.h b/include/uapi/linux/usb/cdc.h index 1924cf665448..7bd5d12d8b26 100644 --- a/include/uapi/linux/usb/cdc.h +++ b/include/uapi/linux/usb/cdc.h @@ -104,8 +104,10 @@ struct usb_cdc_union_desc { __u8 bDescriptorSubType; __u8 bMasterInterface0; - __u8 bSlaveInterface0; - /* ... and there could be other slave interfaces */ + union { + __u8 bSlaveInterface0; + __DECLARE_FLEX_ARRAY(__u8, bSlaveInterfaces); + }; } __attribute__ ((packed)); /* "Country Selection Functional Descriptor" from CDC spec 5.2.3.9 */ @@ -115,8 +117,10 @@ struct usb_cdc_country_functional_desc { __u8 bDescriptorSubType; __u8 iCountryCodeRelDate; - __le16 wCountyCode0; - /* ... and there can be a lot of country codes */ + union { + __le16 wCountryCode0; + __DECLARE_FLEX_ARRAY(__le16, wCountryCodes); + }; } __attribute__ ((packed)); /* "Network Channel Terminal Functional Descriptor" from CDC spec 5.2.3.11 */ -- cgit v1.2.3 From cbbfba4847b8a5299d36e002bf864b21bb83295d Mon Sep 17 00:00:00 2001 From: James Clark Date: Tue, 11 Nov 2025 11:37:55 +0000 Subject: perf: Add perf_event_attr::config4 Arm FEAT_SPE_FDS adds the ability to filter on the data source of a packet using another 64-bits of event filtering control. As the existing perf_event_attr::configN fields are all used up for SPE PMU, an additional field is needed. Add a new 'config4' field. Reviewed-by: Leo Yan Tested-by: Leo Yan Reviewed-by: Ian Rogers Acked-by: Peter Zijlstra (Intel) Signed-off-by: James Clark Signed-off-by: Will Deacon --- include/uapi/linux/perf_event.h | 2 ++ 1 file changed, 2 insertions(+) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/perf_event.h b/include/uapi/linux/perf_event.h index 78a362b80027..0d0ed85ad8cb 100644 --- a/include/uapi/linux/perf_event.h +++ b/include/uapi/linux/perf_event.h @@ -382,6 +382,7 @@ enum perf_event_read_format { #define PERF_ATTR_SIZE_VER6 120 /* Add: aux_sample_size */ #define PERF_ATTR_SIZE_VER7 128 /* Add: sig_data */ #define PERF_ATTR_SIZE_VER8 136 /* Add: config3 */ +#define PERF_ATTR_SIZE_VER9 144 /* add: config4 */ /* * 'struct perf_event_attr' contains various attributes that define @@ -543,6 +544,7 @@ struct perf_event_attr { __u64 sig_data; __u64 config3; /* extension of config2 */ + __u64 config4; /* extension of config3 */ }; /* -- cgit v1.2.3 From e6ab504633e4c06e35377ecf3c8cbc304de79858 Mon Sep 17 00:00:00 2001 From: Dave Penkler Date: Mon, 17 Nov 2025 15:40:21 +0100 Subject: staging: gpib: Destage gpib Move the gpib drivers out of staging and into the "real" part of the kernel. This entails: - Remove the gpib Kconfig menu and Makefile build rule from staging. - Remove gpib/uapi from the header file search path in subdir-ccflags of the gpib Makefile - move the gpib/uapi files to include/uapi/linux - Move the gpib tree out of staging to drivers. - Remove the word "Linux" from the gpib Kconfig file. - Add the gpib Kconfig menu and Makefile build rule to drivers Signed-off-by: Dave Penkler Link: https://patch.msgid.link/20251117144021.23569-5-dpenkler@gmail.com Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 4 +- drivers/Kconfig | 2 + drivers/Makefile | 1 + drivers/gpib/Kconfig | 255 ++ drivers/gpib/Makefile | 20 + drivers/gpib/TODO | 10 + drivers/gpib/agilent_82350b/Makefile | 2 + drivers/gpib/agilent_82350b/agilent_82350b.c | 896 +++++++ drivers/gpib/agilent_82350b/agilent_82350b.h | 157 ++ drivers/gpib/agilent_82357a/Makefile | 4 + drivers/gpib/agilent_82357a/agilent_82357a.c | 1691 ++++++++++++ drivers/gpib/agilent_82357a/agilent_82357a.h | 182 ++ drivers/gpib/cb7210/Makefile | 3 + drivers/gpib/cb7210/cb7210.c | 1598 ++++++++++++ drivers/gpib/cb7210/cb7210.h | 203 ++ drivers/gpib/cec/Makefile | 3 + drivers/gpib/cec/cec.h | 20 + drivers/gpib/cec/cec_gpib.c | 393 +++ drivers/gpib/common/Makefile | 6 + drivers/gpib/common/gpib_os.c | 2271 +++++++++++++++++ drivers/gpib/common/iblib.c | 717 ++++++ drivers/gpib/common/ibsys.h | 34 + drivers/gpib/eastwood/Makefile | 3 + drivers/gpib/eastwood/fluke_gpib.c | 1180 +++++++++ drivers/gpib/eastwood/fluke_gpib.h | 146 ++ drivers/gpib/fmh_gpib/Makefile | 2 + drivers/gpib/fmh_gpib/fmh_gpib.c | 1754 +++++++++++++ drivers/gpib/fmh_gpib/fmh_gpib.h | 177 ++ drivers/gpib/gpio/Makefile | 4 + drivers/gpib/gpio/gpib_bitbang.c | 1469 +++++++++++ drivers/gpib/hp_82335/Makefile | 4 + drivers/gpib/hp_82335/hp82335.c | 371 +++ drivers/gpib/hp_82335/hp82335.h | 52 + drivers/gpib/hp_82341/Makefile | 2 + drivers/gpib/hp_82341/hp_82341.c | 907 +++++++ drivers/gpib/hp_82341/hp_82341.h | 165 ++ drivers/gpib/include/amcc5920.h | 49 + drivers/gpib/include/amccs5933.h | 59 + drivers/gpib/include/gpibP.h | 41 + drivers/gpib/include/gpib_cmd.h | 112 + drivers/gpib/include/gpib_pci_ids.h | 23 + drivers/gpib/include/gpib_proto.h | 49 + drivers/gpib/include/gpib_state_machines.h | 23 + drivers/gpib/include/gpib_types.h | 381 +++ drivers/gpib/include/nec7210.h | 141 ++ drivers/gpib/include/nec7210_registers.h | 218 ++ drivers/gpib/include/plx9050.h | 72 + drivers/gpib/include/quancom_pci.h | 22 + drivers/gpib/include/tms9914.h | 280 ++ drivers/gpib/include/tnt4882_registers.h | 192 ++ drivers/gpib/ines/Makefile | 3 + drivers/gpib/ines/ines.h | 165 ++ drivers/gpib/ines/ines_gpib.c | 1500 +++++++++++ drivers/gpib/lpvo_usb_gpib/Makefile | 3 + drivers/gpib/lpvo_usb_gpib/lpvo_usb_gpib.c | 2025 +++++++++++++++ drivers/gpib/nec7210/Makefile | 4 + drivers/gpib/nec7210/board.h | 19 + drivers/gpib/nec7210/nec7210.c | 1121 ++++++++ drivers/gpib/ni_usb/Makefile | 4 + drivers/gpib/ni_usb/ni_usb_gpib.c | 2678 ++++++++++++++++++++ drivers/gpib/ni_usb/ni_usb_gpib.h | 226 ++ drivers/gpib/pc2/Makefile | 5 + drivers/gpib/pc2/pc2_gpib.c | 684 +++++ drivers/gpib/tms9914/Makefile | 6 + drivers/gpib/tms9914/tms9914.c | 914 +++++++ drivers/gpib/tnt4882/Makefile | 6 + drivers/gpib/tnt4882/mite.c | 133 + drivers/gpib/tnt4882/mite.h | 234 ++ drivers/gpib/tnt4882/tnt4882_gpib.c | 1838 ++++++++++++++ drivers/staging/Kconfig | 2 - drivers/staging/Makefile | 1 - drivers/staging/gpib/Kconfig | 255 -- drivers/staging/gpib/Makefile | 20 - drivers/staging/gpib/TODO | 10 - drivers/staging/gpib/agilent_82350b/Makefile | 2 - .../staging/gpib/agilent_82350b/agilent_82350b.c | 896 ------- .../staging/gpib/agilent_82350b/agilent_82350b.h | 157 -- drivers/staging/gpib/agilent_82357a/Makefile | 4 - .../staging/gpib/agilent_82357a/agilent_82357a.c | 1691 ------------ .../staging/gpib/agilent_82357a/agilent_82357a.h | 182 -- drivers/staging/gpib/cb7210/Makefile | 3 - drivers/staging/gpib/cb7210/cb7210.c | 1598 ------------ drivers/staging/gpib/cb7210/cb7210.h | 203 -- drivers/staging/gpib/cec/Makefile | 3 - drivers/staging/gpib/cec/cec.h | 20 - drivers/staging/gpib/cec/cec_gpib.c | 393 --- drivers/staging/gpib/common/Makefile | 6 - drivers/staging/gpib/common/gpib_os.c | 2271 ----------------- drivers/staging/gpib/common/iblib.c | 717 ------ drivers/staging/gpib/common/ibsys.h | 34 - drivers/staging/gpib/eastwood/Makefile | 3 - drivers/staging/gpib/eastwood/fluke_gpib.c | 1180 --------- drivers/staging/gpib/eastwood/fluke_gpib.h | 146 -- drivers/staging/gpib/fmh_gpib/Makefile | 2 - drivers/staging/gpib/fmh_gpib/fmh_gpib.c | 1754 ------------- drivers/staging/gpib/fmh_gpib/fmh_gpib.h | 177 -- drivers/staging/gpib/gpio/Makefile | 4 - drivers/staging/gpib/gpio/gpib_bitbang.c | 1469 ----------- drivers/staging/gpib/hp_82335/Makefile | 4 - drivers/staging/gpib/hp_82335/hp82335.c | 371 --- drivers/staging/gpib/hp_82335/hp82335.h | 52 - drivers/staging/gpib/hp_82341/Makefile | 2 - drivers/staging/gpib/hp_82341/hp_82341.c | 907 ------- drivers/staging/gpib/hp_82341/hp_82341.h | 165 -- drivers/staging/gpib/include/amcc5920.h | 49 - drivers/staging/gpib/include/amccs5933.h | 59 - drivers/staging/gpib/include/gpibP.h | 41 - drivers/staging/gpib/include/gpib_cmd.h | 112 - drivers/staging/gpib/include/gpib_pci_ids.h | 23 - drivers/staging/gpib/include/gpib_proto.h | 49 - drivers/staging/gpib/include/gpib_state_machines.h | 23 - drivers/staging/gpib/include/gpib_types.h | 381 --- drivers/staging/gpib/include/nec7210.h | 141 -- drivers/staging/gpib/include/nec7210_registers.h | 218 -- drivers/staging/gpib/include/plx9050.h | 72 - drivers/staging/gpib/include/quancom_pci.h | 22 - drivers/staging/gpib/include/tms9914.h | 280 -- drivers/staging/gpib/include/tnt4882_registers.h | 192 -- drivers/staging/gpib/ines/Makefile | 3 - drivers/staging/gpib/ines/ines.h | 165 -- drivers/staging/gpib/ines/ines_gpib.c | 1500 ----------- drivers/staging/gpib/lpvo_usb_gpib/Makefile | 3 - drivers/staging/gpib/lpvo_usb_gpib/lpvo_usb_gpib.c | 2025 --------------- drivers/staging/gpib/nec7210/Makefile | 4 - drivers/staging/gpib/nec7210/board.h | 19 - drivers/staging/gpib/nec7210/nec7210.c | 1121 -------- drivers/staging/gpib/ni_usb/Makefile | 4 - drivers/staging/gpib/ni_usb/ni_usb_gpib.c | 2678 -------------------- drivers/staging/gpib/ni_usb/ni_usb_gpib.h | 226 -- drivers/staging/gpib/pc2/Makefile | 5 - drivers/staging/gpib/pc2/pc2_gpib.c | 684 ----- drivers/staging/gpib/tms9914/Makefile | 6 - drivers/staging/gpib/tms9914/tms9914.c | 914 ------- drivers/staging/gpib/tnt4882/Makefile | 6 - drivers/staging/gpib/tnt4882/mite.c | 133 - drivers/staging/gpib/tnt4882/mite.h | 234 -- drivers/staging/gpib/tnt4882/tnt4882_gpib.c | 1838 -------------- drivers/staging/gpib/uapi/gpib.h | 104 - drivers/staging/gpib/uapi/gpib_ioctl.h | 167 -- include/uapi/linux/gpib.h | 104 + include/uapi/linux/gpib_ioctl.h | 167 ++ 141 files changed, 28208 insertions(+), 28206 deletions(-) create mode 100644 drivers/gpib/Kconfig create mode 100644 drivers/gpib/Makefile create mode 100644 drivers/gpib/TODO create mode 100644 drivers/gpib/agilent_82350b/Makefile create mode 100644 drivers/gpib/agilent_82350b/agilent_82350b.c create mode 100644 drivers/gpib/agilent_82350b/agilent_82350b.h create mode 100644 drivers/gpib/agilent_82357a/Makefile create mode 100644 drivers/gpib/agilent_82357a/agilent_82357a.c create mode 100644 drivers/gpib/agilent_82357a/agilent_82357a.h create mode 100644 drivers/gpib/cb7210/Makefile create mode 100644 drivers/gpib/cb7210/cb7210.c create mode 100644 drivers/gpib/cb7210/cb7210.h create mode 100644 drivers/gpib/cec/Makefile create mode 100644 drivers/gpib/cec/cec.h create mode 100644 drivers/gpib/cec/cec_gpib.c create mode 100644 drivers/gpib/common/Makefile create mode 100644 drivers/gpib/common/gpib_os.c create mode 100644 drivers/gpib/common/iblib.c create mode 100644 drivers/gpib/common/ibsys.h create mode 100644 drivers/gpib/eastwood/Makefile create mode 100644 drivers/gpib/eastwood/fluke_gpib.c create mode 100644 drivers/gpib/eastwood/fluke_gpib.h create mode 100644 drivers/gpib/fmh_gpib/Makefile create mode 100644 drivers/gpib/fmh_gpib/fmh_gpib.c create mode 100644 drivers/gpib/fmh_gpib/fmh_gpib.h create mode 100644 drivers/gpib/gpio/Makefile create mode 100644 drivers/gpib/gpio/gpib_bitbang.c create mode 100644 drivers/gpib/hp_82335/Makefile create mode 100644 drivers/gpib/hp_82335/hp82335.c create mode 100644 drivers/gpib/hp_82335/hp82335.h create mode 100644 drivers/gpib/hp_82341/Makefile create mode 100644 drivers/gpib/hp_82341/hp_82341.c create mode 100644 drivers/gpib/hp_82341/hp_82341.h create mode 100644 drivers/gpib/include/amcc5920.h create mode 100644 drivers/gpib/include/amccs5933.h create mode 100644 drivers/gpib/include/gpibP.h create mode 100644 drivers/gpib/include/gpib_cmd.h create mode 100644 drivers/gpib/include/gpib_pci_ids.h create mode 100644 drivers/gpib/include/gpib_proto.h create mode 100644 drivers/gpib/include/gpib_state_machines.h create mode 100644 drivers/gpib/include/gpib_types.h create mode 100644 drivers/gpib/include/nec7210.h create mode 100644 drivers/gpib/include/nec7210_registers.h create mode 100644 drivers/gpib/include/plx9050.h create mode 100644 drivers/gpib/include/quancom_pci.h create mode 100644 drivers/gpib/include/tms9914.h create mode 100644 drivers/gpib/include/tnt4882_registers.h create mode 100644 drivers/gpib/ines/Makefile create mode 100644 drivers/gpib/ines/ines.h create mode 100644 drivers/gpib/ines/ines_gpib.c create mode 100644 drivers/gpib/lpvo_usb_gpib/Makefile create mode 100644 drivers/gpib/lpvo_usb_gpib/lpvo_usb_gpib.c create mode 100644 drivers/gpib/nec7210/Makefile create mode 100644 drivers/gpib/nec7210/board.h create mode 100644 drivers/gpib/nec7210/nec7210.c create mode 100644 drivers/gpib/ni_usb/Makefile create mode 100644 drivers/gpib/ni_usb/ni_usb_gpib.c create mode 100644 drivers/gpib/ni_usb/ni_usb_gpib.h create mode 100644 drivers/gpib/pc2/Makefile create mode 100644 drivers/gpib/pc2/pc2_gpib.c create mode 100644 drivers/gpib/tms9914/Makefile create mode 100644 drivers/gpib/tms9914/tms9914.c create mode 100644 drivers/gpib/tnt4882/Makefile create mode 100644 drivers/gpib/tnt4882/mite.c create mode 100644 drivers/gpib/tnt4882/mite.h create mode 100644 drivers/gpib/tnt4882/tnt4882_gpib.c delete mode 100644 drivers/staging/gpib/Kconfig delete mode 100644 drivers/staging/gpib/Makefile delete mode 100644 drivers/staging/gpib/TODO delete mode 100644 drivers/staging/gpib/agilent_82350b/Makefile delete mode 100644 drivers/staging/gpib/agilent_82350b/agilent_82350b.c delete mode 100644 drivers/staging/gpib/agilent_82350b/agilent_82350b.h delete mode 100644 drivers/staging/gpib/agilent_82357a/Makefile delete mode 100644 drivers/staging/gpib/agilent_82357a/agilent_82357a.c delete mode 100644 drivers/staging/gpib/agilent_82357a/agilent_82357a.h delete mode 100644 drivers/staging/gpib/cb7210/Makefile delete mode 100644 drivers/staging/gpib/cb7210/cb7210.c delete mode 100644 drivers/staging/gpib/cb7210/cb7210.h delete mode 100644 drivers/staging/gpib/cec/Makefile delete mode 100644 drivers/staging/gpib/cec/cec.h delete mode 100644 drivers/staging/gpib/cec/cec_gpib.c delete mode 100644 drivers/staging/gpib/common/Makefile delete mode 100644 drivers/staging/gpib/common/gpib_os.c delete mode 100644 drivers/staging/gpib/common/iblib.c delete mode 100644 drivers/staging/gpib/common/ibsys.h delete mode 100644 drivers/staging/gpib/eastwood/Makefile delete mode 100644 drivers/staging/gpib/eastwood/fluke_gpib.c delete mode 100644 drivers/staging/gpib/eastwood/fluke_gpib.h delete mode 100644 drivers/staging/gpib/fmh_gpib/Makefile delete mode 100644 drivers/staging/gpib/fmh_gpib/fmh_gpib.c delete mode 100644 drivers/staging/gpib/fmh_gpib/fmh_gpib.h delete mode 100644 drivers/staging/gpib/gpio/Makefile delete mode 100644 drivers/staging/gpib/gpio/gpib_bitbang.c delete mode 100644 drivers/staging/gpib/hp_82335/Makefile delete mode 100644 drivers/staging/gpib/hp_82335/hp82335.c delete mode 100644 drivers/staging/gpib/hp_82335/hp82335.h delete mode 100644 drivers/staging/gpib/hp_82341/Makefile delete mode 100644 drivers/staging/gpib/hp_82341/hp_82341.c delete mode 100644 drivers/staging/gpib/hp_82341/hp_82341.h delete mode 100644 drivers/staging/gpib/include/amcc5920.h delete mode 100644 drivers/staging/gpib/include/amccs5933.h delete mode 100644 drivers/staging/gpib/include/gpibP.h delete mode 100644 drivers/staging/gpib/include/gpib_cmd.h delete mode 100644 drivers/staging/gpib/include/gpib_pci_ids.h delete mode 100644 drivers/staging/gpib/include/gpib_proto.h delete mode 100644 drivers/staging/gpib/include/gpib_state_machines.h delete mode 100644 drivers/staging/gpib/include/gpib_types.h delete mode 100644 drivers/staging/gpib/include/nec7210.h delete mode 100644 drivers/staging/gpib/include/nec7210_registers.h delete mode 100644 drivers/staging/gpib/include/plx9050.h delete mode 100644 drivers/staging/gpib/include/quancom_pci.h delete mode 100644 drivers/staging/gpib/include/tms9914.h delete mode 100644 drivers/staging/gpib/include/tnt4882_registers.h delete mode 100644 drivers/staging/gpib/ines/Makefile delete mode 100644 drivers/staging/gpib/ines/ines.h delete mode 100644 drivers/staging/gpib/ines/ines_gpib.c delete mode 100644 drivers/staging/gpib/lpvo_usb_gpib/Makefile delete mode 100644 drivers/staging/gpib/lpvo_usb_gpib/lpvo_usb_gpib.c delete mode 100644 drivers/staging/gpib/nec7210/Makefile delete mode 100644 drivers/staging/gpib/nec7210/board.h delete mode 100644 drivers/staging/gpib/nec7210/nec7210.c delete mode 100644 drivers/staging/gpib/ni_usb/Makefile delete mode 100644 drivers/staging/gpib/ni_usb/ni_usb_gpib.c delete mode 100644 drivers/staging/gpib/ni_usb/ni_usb_gpib.h delete mode 100644 drivers/staging/gpib/pc2/Makefile delete mode 100644 drivers/staging/gpib/pc2/pc2_gpib.c delete mode 100644 drivers/staging/gpib/tms9914/Makefile delete mode 100644 drivers/staging/gpib/tms9914/tms9914.c delete mode 100644 drivers/staging/gpib/tnt4882/Makefile delete mode 100644 drivers/staging/gpib/tnt4882/mite.c delete mode 100644 drivers/staging/gpib/tnt4882/mite.h delete mode 100644 drivers/staging/gpib/tnt4882/tnt4882_gpib.c delete mode 100644 drivers/staging/gpib/uapi/gpib.h delete mode 100644 drivers/staging/gpib/uapi/gpib_ioctl.h create mode 100644 include/uapi/linux/gpib.h create mode 100644 include/uapi/linux/gpib_ioctl.h (limited to 'include/uapi/linux') diff --git a/MAINTAINERS b/MAINTAINERS index df07d1a3c28d..a6055a910be6 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10628,7 +10628,9 @@ F: drivers/platform/x86/gpd-pocket-fan.c GPIB DRIVERS M: Dave Penkler S: Maintained -F: drivers/staging/gpib/ +F: drivers/gpib/ +F: include/uapi/linux/gpib.h +F: include/uapi/linux/gpib_ioctl.h GPIO ACPI SUPPORT M: Mika Westerberg diff --git a/drivers/Kconfig b/drivers/Kconfig index 4915a63866b0..01602581b880 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -161,6 +161,8 @@ source "drivers/greybus/Kconfig" source "drivers/comedi/Kconfig" +source "drivers/gpib/Kconfig" + source "drivers/staging/Kconfig" source "drivers/platform/Kconfig" diff --git a/drivers/Makefile b/drivers/Makefile index 8e1ffa4358d5..d275b1526cdd 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -150,6 +150,7 @@ obj-$(CONFIG_VHOST_IOTLB) += vhost/ obj-$(CONFIG_VHOST) += vhost/ obj-$(CONFIG_GREYBUS) += greybus/ obj-$(CONFIG_COMEDI) += comedi/ +obj-$(CONFIG_GPIB) += gpib/ obj-$(CONFIG_STAGING) += staging/ obj-y += platform/ diff --git a/drivers/gpib/Kconfig b/drivers/gpib/Kconfig new file mode 100644 index 000000000000..eeb50956ce85 --- /dev/null +++ b/drivers/gpib/Kconfig @@ -0,0 +1,255 @@ +# SPDX-License-Identifier: GPL-2.0 +menuconfig GPIB + tristate "GPIB drivers" + help + Enable support for GPIB cards and dongles. GPIB is the + General Purpose Interface Bus which conforms to the IEEE488 + standard. + + This set of drivers can be used with the corresponding user + space library that can be found on Sourceforge under linux-gpib. + Select the drivers for your hardware from the list. + +if GPIB + +config GPIB_COMMON + tristate "GPIB core" + help + + Core common driver for all GPIB drivers. It provides the + interface for the userland library + + To compile this driver as a module, choose M here: the module will be + called gpib_common + +config GPIB_AGILENT_82350B + tristate "Agilent 8235xx PCI(e) adapters" + depends on PCI + select GPIB_COMMON + select GPIB_TMS9914 + help + Enable support for HP/Agilent/Keysight boards + 82350A + 82350B + 82351A + + To compile this driver as a module, choose M here: the module will be + called agilent_82350b. + +config GPIB_AGILENT_82357A + tristate "Agilent 82357a/b USB dongles" + select GPIB_COMMON + depends on USB + help + Enable support for Agilent/Keysight 82357x USB dongles. + + To compile this driver as a module, choose M here: the module will be + called agilent_82357a. + +config GPIB_CEC_PCI + tristate "CEC PCI board" + depends on PCI + depends on HAS_IOPORT + select GPIB_COMMON + select GPIB_NEC7210 + help + Enable support for Capital Equipment Corporation PCI-488 + and Keithly KPCI-488 boards. + + To compile this driver as a module, choose M here: the module will be + called cec_gpib. + +config GPIB_NI_PCI_ISA + tristate "NI PCI/ISA compatible boards" + depends on ISA_BUS || PCI || PCMCIA + depends on HAS_IOPORT + depends on PCMCIA || !PCMCIA + depends on HAS_IOPORT_MAP + select GPIB_COMMON + select GPIB_NEC7210 + help + Enable support for National Instruments boards based + on TNT4882 chips: + AT-GPIB (with NAT4882 chip) + AT-GPIB (with NEC7210 chip) + AT-GPIB/TNT + PCI-GPIB + PCIe-GPIB + PCI-GPIB+ + PCM-GPIB + PXI-GPIB + PCMCIA-GPIB + and Capital Equipment Corporation CEC-488 board. + + To compile this driver as a module, choose M here: the module will be + called tnt4882. + +config GPIB_CB7210 + tristate "Measurement Computing compatible boards" + depends on HAS_IOPORT + depends on ISA_BUS || PCI || PCMCIA + depends on PCMCIA || !PCMCIA + select GPIB_COMMON + select GPIB_NEC7210 + help + Enable support for Measurement Computing (Computer Boards): + CPCI_GPIB, ISA-GPIB, ISA-GPIB/LC, PCI-GPIB/1M, PCI-GPIB/300K and + PCMCIA-GPIB + Quancom PCIGPIB-1 with MC cb7210 chip + + To compile this driver as a module, choose M here: the module will be + +config GPIB_NI_USB + tristate "NI USB dongles" + select GPIB_COMMON + depends on USB + help + Enable support for National Instruments + GPIB-USB-B + GPIB-USB-HS + GPIB-USB-HS+ + Keithly + KUSB-488 + KUSB-488A + Measurement Computing (Computer Boards) + USB-488 + + To compile this driver as a module, choose M here: the module will be + called ni_usb. + +config GPIB_FLUKE + tristate "Fluke" + depends on OF + select GPIB_COMMON + select GPIB_NEC7210 + help + GPIB driver for Fluke based cda devices. + + To compile this driver as a module, choose M here: the module will be + called fluke_gpib + +config GPIB_FMH + tristate "FMH FPGA based devices" + select GPIB_COMMON + select GPIB_NEC7210 + depends on !PPC + depends on OF && PCI + help + GPIB driver for fmhess FPGA based devices + + To compile this driver as a module, choose M here: the module will be + called fmh_gpib + +config GPIB_GPIO + tristate "RPi GPIO bitbang" + depends on ARCH_BCM2835 || COMPILE_TEST + select GPIB_COMMON + help + GPIB bitbang driver Raspberry Pi GPIO adapters + + To compile this driver as a module, choose M here: the module will be + called gpib_bitbang + +config GPIB_HP82335 + tristate "HP82335/HP27209" + depends on ISA_BUS + select GPIB_COMMON + select GPIB_TMS9914 + help + GPIB driver for HP82335 and HP27209 boards + + To compile this driver as a module, choose M here: the module will be + called hp82335 + + +config GPIB_HP82341 + tristate "HP82341x" + select GPIB_COMMON + select GPIB_TMS9914 + depends on ISA_BUS || EISA + help + GPIB driver for HP82341 A/B/C/D boards + + To compile this driver as a module, choose M here: the module will be + called hp82341 + +config GPIB_INES + tristate "INES" + depends on PCI || ISA_BUS || PCMCIA + depends on PCMCIA || !PCMCIA + depends on HAS_IOPORT + select GPIB_COMMON + select GPIB_NEC7210 + help + GPIB driver for Ines compatible boards + Ines + GPIB-HS-NT + GPIB for Compact PCI + GPIB for PCI + GPIB for PCMCIA + GPIB PC/104 + Hameg + HO80-2 + Quancom + PCIGPIB-1 based on Ines iGPIB 72010 chip + + To compile this driver as a module, choose M here: the module will be + called ines_gpib + called cb7210. + +config GPIB_PCMCIA + def_bool y + depends on PCMCIA && (GPIB_NI_PCI_ISA || GPIB_CB7210 || GPIB_INES) + help + Enable PCMCIA/CArdbus support for National Instruments, + measurement computing boards and Ines boards. + +config GPIB_LPVO + tristate "LPVO DIY USB GPIB" + select GPIB_COMMON + depends on USB + help + Enable support for LPVO Self-made usb-gpib adapter + + To compile this driver as a module, choose M here: the module will be + called lpvo_usb_gpib + +config GPIB_PC2 + tristate "PC2 PC2a" + depends on ISA_BUS + depends on HAS_IOPORT + select GPIB_COMMON + select GPIB_NEC7210 + help + Enable support for pc2 and pc2a compatible adapters + Capital Equipment Corporation PC-488 + CONTEC GP-IB(PC) + Hameg HO80 + Iotech GP488B + Keithly MBC-488 + Measurement Computing ISA-GPIB-PCA2 + National Instruments PCII, PCIIa and PCII/IIa + + To compile this driver as a module, choose M here: the module will be + called pc2_gpib + + +config GPIB_TMS9914 + tristate + select GPIB_COMMON + help + Enable support for TMS 9914 chip. + + To compile this driver as a module, choose M here: the module will be + called tms9914 + +config GPIB_NEC7210 + tristate + select GPIB_COMMON + help + Enable support for NEC 7210 compatible chips. + + To compile this driver as a module, choose M here: the module will be + called nec7210 + +endif # GPIB diff --git a/drivers/gpib/Makefile b/drivers/gpib/Makefile new file mode 100644 index 000000000000..2d44fed2a743 --- /dev/null +++ b/drivers/gpib/Makefile @@ -0,0 +1,20 @@ + +subdir-ccflags-y += -I$(src)/include + +obj-$(CONFIG_GPIB_AGILENT_82350B) += agilent_82350b/ +obj-$(CONFIG_GPIB_AGILENT_82357A) += agilent_82357a/ +obj-$(CONFIG_GPIB_CB7210) += cb7210/ +obj-$(CONFIG_GPIB_CEC_PCI) += cec/ +obj-$(CONFIG_GPIB_COMMON) += common/ +obj-$(CONFIG_GPIB_FLUKE) += eastwood/ +obj-$(CONFIG_GPIB_FMH) += fmh_gpib/ +obj-$(CONFIG_GPIB_GPIO) += gpio/ +obj-$(CONFIG_GPIB_HP82335) += hp_82335/ +obj-$(CONFIG_GPIB_HP82341) += hp_82341/ +obj-$(CONFIG_GPIB_INES) += ines/ +obj-$(CONFIG_GPIB_LPVO) += lpvo_usb_gpib/ +obj-$(CONFIG_GPIB_NEC7210) += nec7210/ +obj-$(CONFIG_GPIB_NI_USB) += ni_usb/ +obj-$(CONFIG_GPIB_PC2) += pc2/ +obj-$(CONFIG_GPIB_TMS9914) += tms9914/ +obj-$(CONFIG_GPIB_NI_PCI_ISA) += tnt4882/ diff --git a/drivers/gpib/TODO b/drivers/gpib/TODO new file mode 100644 index 000000000000..ac07dd90b4ef --- /dev/null +++ b/drivers/gpib/TODO @@ -0,0 +1,10 @@ +TODO: +- checkpatch.pl fixes + These checks should be ignored: + CHECK:ALLOC_SIZEOF_STRUCT: Prefer kmalloc(sizeof(*board->private_data)...) over kmalloc(sizeof(struct xxx_priv)...) + ./gpio/gpib_bitbang.c:50: ERROR:COMPLEX_MACRO: Macros with complex values should be enclosed in parenthese + This warning will be addressed later: WARNING:UNDOCUMENTED_DT_STRING: DT compatible string +- resolve XXX notes where possible +- fix FIXME notes +- clean-up commented-out code +- fix typos diff --git a/drivers/gpib/agilent_82350b/Makefile b/drivers/gpib/agilent_82350b/Makefile new file mode 100644 index 000000000000..f24e1e713a63 --- /dev/null +++ b/drivers/gpib/agilent_82350b/Makefile @@ -0,0 +1,2 @@ + +obj-$(CONFIG_GPIB_AGILENT_82350B) += agilent_82350b.o diff --git a/drivers/gpib/agilent_82350b/agilent_82350b.c b/drivers/gpib/agilent_82350b/agilent_82350b.c new file mode 100644 index 000000000000..01a5bb43cd2d --- /dev/null +++ b/drivers/gpib/agilent_82350b/agilent_82350b.c @@ -0,0 +1,896 @@ +// SPDX-License-Identifier: GPL-2.0 + +/*************************************************************************** + * copyright : (C) 2002, 2004 by Frank Mori Hess * + ***************************************************************************/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#define dev_fmt pr_fmt +#define DRV_NAME KBUILD_MODNAME + +#include "agilent_82350b.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("GPIB driver for Agilent 82350b"); + +static int read_transfer_counter(struct agilent_82350b_priv *a_priv); +static unsigned short read_and_clear_event_status(struct gpib_board *board); +static void set_transfer_counter(struct agilent_82350b_priv *a_priv, int count); +static int agilent_82350b_write(struct gpib_board *board, u8 *buffer, + size_t length, int send_eoi, size_t *bytes_written); + +static int agilent_82350b_accel_read(struct gpib_board *board, u8 *buffer, + size_t length, int *end, size_t *bytes_read) + +{ + struct agilent_82350b_priv *a_priv = board->private_data; + struct tms9914_priv *tms_priv = &a_priv->tms9914_priv; + int retval = 0; + unsigned short event_status; + int i, num_fifo_bytes; + /* hardware doesn't support checking for end-of-string character when using fifo */ + if (tms_priv->eos_flags & REOS) + return tms9914_read(board, tms_priv, buffer, length, end, bytes_read); + + clear_bit(DEV_CLEAR_BN, &tms_priv->state); + + read_and_clear_event_status(board); + *end = 0; + *bytes_read = 0; + if (length == 0) + return 0; + /* disable fifo for the moment */ + writeb(DIRECTION_GPIB_TO_HOST, a_priv->gpib_base + SRAM_ACCESS_CONTROL_REG); + /* handle corner case of board not in holdoff and one byte might slip in early */ + if (tms_priv->holdoff_active == 0 && length > 1) { + size_t num_bytes; + + retval = tms9914_read(board, tms_priv, buffer, 1, end, &num_bytes); + *bytes_read += num_bytes; + if (retval < 0 || *end) + return retval; + ++buffer; + --length; + } + tms9914_set_holdoff_mode(tms_priv, TMS9914_HOLDOFF_EOI); + tms9914_release_holdoff(tms_priv); + i = 0; + num_fifo_bytes = length - 1; + /* disable BI interrupts */ + write_byte(tms_priv, tms_priv->imr0_bits & ~HR_BIIE, IMR0); + while (i < num_fifo_bytes && *end == 0) { + int block_size; + int j; + int count; + + block_size = min(num_fifo_bytes - i, agilent_82350b_fifo_size); + set_transfer_counter(a_priv, block_size); + writeb(ENABLE_TI_TO_SRAM | DIRECTION_GPIB_TO_HOST, + a_priv->gpib_base + SRAM_ACCESS_CONTROL_REG); + if (agilent_82350b_fifo_is_halted(a_priv)) + writeb(RESTART_STREAM_BIT, a_priv->gpib_base + STREAM_STATUS_REG); + + clear_bit(READ_READY_BN, &tms_priv->state); + + retval = wait_event_interruptible(board->wait, + ((event_status = + read_and_clear_event_status(board)) & + (TERM_COUNT_STATUS_BIT | + BUFFER_END_STATUS_BIT)) || + test_bit(DEV_CLEAR_BN, &tms_priv->state) || + test_bit(TIMO_NUM, &board->status)); + if (retval) { + retval = -ERESTARTSYS; + break; + } + count = block_size - read_transfer_counter(a_priv); + for (j = 0; j < count && i < num_fifo_bytes; ++j) + buffer[i++] = readb(a_priv->sram_base + j); + if (event_status & BUFFER_END_STATUS_BIT) { + clear_bit(RECEIVED_END_BN, &tms_priv->state); + + tms_priv->holdoff_active = 1; + *end = 1; + } + if (test_bit(TIMO_NUM, &board->status)) { + retval = -ETIMEDOUT; + break; + } + if (test_bit(DEV_CLEAR_BN, &tms_priv->state)) { + retval = -EINTR; + break; + } + } + /* re-enable BI interrupts */ + write_byte(tms_priv, tms_priv->imr0_bits, IMR0); + *bytes_read += i; + buffer += i; + length -= i; + writeb(DIRECTION_GPIB_TO_HOST, a_priv->gpib_base + SRAM_ACCESS_CONTROL_REG); + if (retval < 0) + return retval; + /* read last bytes if we havn't received an END yet */ + if (*end == 0) { + size_t num_bytes; + /* try to make sure we holdoff after last byte read */ + retval = tms9914_read(board, tms_priv, buffer, length, end, &num_bytes); + *bytes_read += num_bytes; + if (retval < 0) + return retval; + } + return 0; +} + +static int translate_wait_return_value(struct gpib_board *board, int retval) + +{ + struct agilent_82350b_priv *a_priv = board->private_data; + struct tms9914_priv *tms_priv = &a_priv->tms9914_priv; + + if (retval) + return -ERESTARTSYS; + if (test_bit(TIMO_NUM, &board->status)) + return -ETIMEDOUT; + if (test_bit(DEV_CLEAR_BN, &tms_priv->state)) + return -EINTR; + return 0; +} + +static int agilent_82350b_accel_write(struct gpib_board *board, u8 *buffer, + size_t length, int send_eoi, + size_t *bytes_written) +{ + struct agilent_82350b_priv *a_priv = board->private_data; + struct tms9914_priv *tms_priv = &a_priv->tms9914_priv; + int i, j; + unsigned short event_status; + int retval = 0; + int fifotransferlength = length; + int block_size = 0; + size_t num_bytes; + + *bytes_written = 0; + if (send_eoi) + --fifotransferlength; + + clear_bit(DEV_CLEAR_BN, &tms_priv->state); + + writeb(0, a_priv->gpib_base + SRAM_ACCESS_CONTROL_REG); + + event_status = read_and_clear_event_status(board); + +#ifdef EXPERIMENTAL + /* wait for previous BO to complete if any */ + retval = wait_event_interruptible(board->wait, + test_bit(DEV_CLEAR_BN, &tms_priv->state) || + test_bit(WRITE_READY_BN, &tms_priv->state) || + test_bit(TIMO_NUM, &board->status)); + retval = translate_wait_return_value(board, retval); + + if (retval) + return retval; +#endif + + if (fifotransferlength > 0) { + retval = agilent_82350b_write(board, buffer, 1, 0, &num_bytes); + *bytes_written += num_bytes; + if (retval < 0) + return retval; + } + + write_byte(tms_priv, tms_priv->imr0_bits & ~HR_BOIE, IMR0); + for (i = 1; i < fifotransferlength;) { + clear_bit(WRITE_READY_BN, &tms_priv->state); + + block_size = min(fifotransferlength - i, agilent_82350b_fifo_size); + set_transfer_counter(a_priv, block_size); + for (j = 0; j < block_size; ++j, ++i) { + /* load data into board's sram */ + writeb(buffer[i], a_priv->sram_base + j); + } + writeb(ENABLE_TI_TO_SRAM, a_priv->gpib_base + SRAM_ACCESS_CONTROL_REG); + + if (agilent_82350b_fifo_is_halted(a_priv)) + writeb(RESTART_STREAM_BIT, a_priv->gpib_base + STREAM_STATUS_REG); + + retval = wait_event_interruptible(board->wait, + ((event_status = + read_and_clear_event_status(board)) & + TERM_COUNT_STATUS_BIT) || + test_bit(DEV_CLEAR_BN, &tms_priv->state) || + test_bit(TIMO_NUM, &board->status)); + writeb(0, a_priv->gpib_base + SRAM_ACCESS_CONTROL_REG); + num_bytes = block_size - read_transfer_counter(a_priv); + + *bytes_written += num_bytes; + retval = translate_wait_return_value(board, retval); + if (retval) + break; + } + write_byte(tms_priv, tms_priv->imr0_bits, IMR0); + if (retval < 0) + return retval; + + if (send_eoi) { + retval = agilent_82350b_write(board, buffer + fifotransferlength, 1, send_eoi, + &num_bytes); + *bytes_written += num_bytes; + if (retval < 0) + return retval; + } + return 0; +} + +static unsigned short read_and_clear_event_status(struct gpib_board *board) +{ + struct agilent_82350b_priv *a_priv = board->private_data; + unsigned long flags; + unsigned short status; + + spin_lock_irqsave(&board->spinlock, flags); + status = a_priv->event_status_bits; + a_priv->event_status_bits = 0; + spin_unlock_irqrestore(&board->spinlock, flags); + return status; +} + +static irqreturn_t agilent_82350b_interrupt(int irq, void *arg) + +{ + int tms9914_status1 = 0, tms9914_status2 = 0; + int event_status; + struct gpib_board *board = arg; + struct agilent_82350b_priv *a_priv = board->private_data; + unsigned long flags; + irqreturn_t retval = IRQ_NONE; + + spin_lock_irqsave(&board->spinlock, flags); + event_status = readb(a_priv->gpib_base + EVENT_STATUS_REG); + if (event_status & IRQ_STATUS_BIT) + retval = IRQ_HANDLED; + + if (event_status & TMS9914_IRQ_STATUS_BIT) { + tms9914_status1 = read_byte(&a_priv->tms9914_priv, ISR0); + tms9914_status2 = read_byte(&a_priv->tms9914_priv, ISR1); + tms9914_interrupt_have_status(board, &a_priv->tms9914_priv, tms9914_status1, + tms9914_status2); + } + /* write-clear status bits */ + if (event_status & (BUFFER_END_STATUS_BIT | TERM_COUNT_STATUS_BIT)) { + writeb(event_status & (BUFFER_END_STATUS_BIT | TERM_COUNT_STATUS_BIT), + a_priv->gpib_base + EVENT_STATUS_REG); + a_priv->event_status_bits |= event_status; + wake_up_interruptible(&board->wait); + } + spin_unlock_irqrestore(&board->spinlock, flags); + return retval; +} + +static void agilent_82350b_detach(struct gpib_board *board); + +static int read_transfer_counter(struct agilent_82350b_priv *a_priv) +{ + int lo, mid, value; + + lo = readb(a_priv->gpib_base + XFER_COUNT_LO_REG); + mid = readb(a_priv->gpib_base + XFER_COUNT_MID_REG); + value = (lo & 0xff) | ((mid << 8) & 0x7f00); + value = ~(value - 1) & 0x7fff; + return value; +} + +static void set_transfer_counter(struct agilent_82350b_priv *a_priv, int count) +{ + int complement = -count; + + writeb(complement & 0xff, a_priv->gpib_base + XFER_COUNT_LO_REG); + writeb((complement >> 8) & 0xff, a_priv->gpib_base + XFER_COUNT_MID_REG); + /* I don't think the hi count reg is even used, but oh well */ + writeb((complement >> 16) & 0xf, a_priv->gpib_base + XFER_COUNT_HI_REG); +} + +/* wrappers for interface functions */ +static int agilent_82350b_read(struct gpib_board *board, u8 *buffer, + size_t length, int *end, size_t *bytes_read) +{ + struct agilent_82350b_priv *priv = board->private_data; + + return tms9914_read(board, &priv->tms9914_priv, buffer, length, end, bytes_read); +} + +static int agilent_82350b_write(struct gpib_board *board, u8 *buffer, + size_t length, int send_eoi, size_t *bytes_written) + +{ + struct agilent_82350b_priv *priv = board->private_data; + + return tms9914_write(board, &priv->tms9914_priv, buffer, length, send_eoi, bytes_written); +} + +static int agilent_82350b_command(struct gpib_board *board, u8 *buffer, + size_t length, size_t *bytes_written) + +{ + struct agilent_82350b_priv *priv = board->private_data; + + return tms9914_command(board, &priv->tms9914_priv, buffer, length, bytes_written); +} + +static int agilent_82350b_take_control(struct gpib_board *board, int synchronous) + +{ + struct agilent_82350b_priv *priv = board->private_data; + + return tms9914_take_control_workaround(board, &priv->tms9914_priv, synchronous); +} + +static int agilent_82350b_go_to_standby(struct gpib_board *board) + +{ + struct agilent_82350b_priv *priv = board->private_data; + + return tms9914_go_to_standby(board, &priv->tms9914_priv); +} + +static int agilent_82350b_request_system_control(struct gpib_board *board, int request_control) +{ + struct agilent_82350b_priv *a_priv = board->private_data; + + if (request_control) { + a_priv->card_mode_bits |= CM_SYSTEM_CONTROLLER_BIT; + if (a_priv->model != MODEL_82350A) + writeb(IC_SYSTEM_CONTROLLER_BIT, a_priv->gpib_base + INTERNAL_CONFIG_REG); + } else { + a_priv->card_mode_bits &= ~CM_SYSTEM_CONTROLLER_BIT; + if (a_priv->model != MODEL_82350A) + writeb(0, a_priv->gpib_base + INTERNAL_CONFIG_REG); + } + writeb(a_priv->card_mode_bits, a_priv->gpib_base + CARD_MODE_REG); + return tms9914_request_system_control(board, &a_priv->tms9914_priv, request_control); +} + +static void agilent_82350b_interface_clear(struct gpib_board *board, int assert) + +{ + struct agilent_82350b_priv *priv = board->private_data; + + tms9914_interface_clear(board, &priv->tms9914_priv, assert); +} + +static void agilent_82350b_remote_enable(struct gpib_board *board, int enable) +{ + struct agilent_82350b_priv *priv = board->private_data; + + tms9914_remote_enable(board, &priv->tms9914_priv, enable); +} + +static int agilent_82350b_enable_eos(struct gpib_board *board, u8 eos_byte, + int compare_8_bits) +{ + struct agilent_82350b_priv *priv = board->private_data; + + return tms9914_enable_eos(board, &priv->tms9914_priv, eos_byte, compare_8_bits); +} + +static void agilent_82350b_disable_eos(struct gpib_board *board) +{ + struct agilent_82350b_priv *priv = board->private_data; + + tms9914_disable_eos(board, &priv->tms9914_priv); +} + +static unsigned int agilent_82350b_update_status(struct gpib_board *board, + unsigned int clear_mask) +{ + struct agilent_82350b_priv *priv = board->private_data; + + return tms9914_update_status(board, &priv->tms9914_priv, clear_mask); +} + +static int agilent_82350b_primary_address(struct gpib_board *board, + unsigned int address) +{ + struct agilent_82350b_priv *priv = board->private_data; + + return tms9914_primary_address(board, &priv->tms9914_priv, address); +} + +static int agilent_82350b_secondary_address(struct gpib_board *board, + unsigned int address, int enable) +{ + struct agilent_82350b_priv *priv = board->private_data; + + return tms9914_secondary_address(board, &priv->tms9914_priv, address, enable); +} + +static int agilent_82350b_parallel_poll(struct gpib_board *board, u8 *result) +{ + struct agilent_82350b_priv *priv = board->private_data; + + return tms9914_parallel_poll(board, &priv->tms9914_priv, result); +} + +static void agilent_82350b_parallel_poll_configure(struct gpib_board *board, + u8 config) +{ + struct agilent_82350b_priv *priv = board->private_data; + + tms9914_parallel_poll_configure(board, &priv->tms9914_priv, config); +} + +static void agilent_82350b_parallel_poll_response(struct gpib_board *board, int ist) +{ + struct agilent_82350b_priv *priv = board->private_data; + + tms9914_parallel_poll_response(board, &priv->tms9914_priv, ist); +} + +static void agilent_82350b_serial_poll_response(struct gpib_board *board, u8 status) +{ + struct agilent_82350b_priv *priv = board->private_data; + + tms9914_serial_poll_response(board, &priv->tms9914_priv, status); +} + +static u8 agilent_82350b_serial_poll_status(struct gpib_board *board) +{ + struct agilent_82350b_priv *priv = board->private_data; + + return tms9914_serial_poll_status(board, &priv->tms9914_priv); +} + +static int agilent_82350b_line_status(const struct gpib_board *board) +{ + struct agilent_82350b_priv *priv = board->private_data; + + return tms9914_line_status(board, &priv->tms9914_priv); +} + +static int agilent_82350b_t1_delay(struct gpib_board *board, unsigned int nanosec) +{ + struct agilent_82350b_priv *a_priv = board->private_data; + static const int nanosec_per_clock = 30; + unsigned int value; + + tms9914_t1_delay(board, &a_priv->tms9914_priv, nanosec); + + value = (nanosec + nanosec_per_clock - 1) / nanosec_per_clock; + if (value > 0xff) + value = 0xff; + writeb(value, a_priv->gpib_base + T1_DELAY_REG); + return value * nanosec_per_clock; +} + +static void agilent_82350b_return_to_local(struct gpib_board *board) +{ + struct agilent_82350b_priv *priv = board->private_data; + + tms9914_return_to_local(board, &priv->tms9914_priv); +} + +static int agilent_82350b_allocate_private(struct gpib_board *board) +{ + board->private_data = kzalloc(sizeof(struct agilent_82350b_priv), GFP_KERNEL); + if (!board->private_data) + return -ENOMEM; + return 0; +} + +static void agilent_82350b_free_private(struct gpib_board *board) +{ + kfree(board->private_data); + board->private_data = NULL; +} + +static int init_82350a_hardware(struct gpib_board *board, + const struct gpib_board_config *config) +{ + struct agilent_82350b_priv *a_priv = board->private_data; + static const unsigned int firmware_length = 5302; + unsigned int borg_status; + static const unsigned int timeout = 1000; + int i, j; + const char *firmware_data = config->init_data; + const unsigned int plx_cntrl_static_bits = PLX9050_WAITO_NOT_USER0_SELECT_BIT | + PLX9050_USER0_OUTPUT_BIT | + PLX9050_LLOCK_NOT_USER1_SELECT_BIT | + PLX9050_USER1_OUTPUT_BIT | + PLX9050_USER2_OUTPUT_BIT | + PLX9050_USER3_OUTPUT_BIT | + PLX9050_PCI_READ_MODE_BIT | + PLX9050_PCI_WRITE_MODE_BIT | + PLX9050_PCI_RETRY_DELAY_BITS(64) | + PLX9050_DIRECT_SLAVE_LOCK_ENABLE_BIT; + + /* load borg data */ + borg_status = readb(a_priv->borg_base); + if ((borg_status & BORG_DONE_BIT)) + return 0; + /* need to programme borg */ + if (!config->init_data || config->init_data_length != firmware_length) { + dev_err(board->gpib_dev, "the 82350A board requires firmware after powering on.\n"); + return -EIO; + } + dev_dbg(board->gpib_dev, "Loading firmware...\n"); + + /* tickle the borg */ + writel(plx_cntrl_static_bits | PLX9050_USER3_DATA_BIT, + a_priv->plx_base + PLX9050_CNTRL_REG); + usleep_range(1000, 2000); + writel(plx_cntrl_static_bits, a_priv->plx_base + PLX9050_CNTRL_REG); + usleep_range(1000, 2000); + writel(plx_cntrl_static_bits | PLX9050_USER3_DATA_BIT, + a_priv->plx_base + PLX9050_CNTRL_REG); + usleep_range(1000, 2000); + + for (i = 0; i < config->init_data_length; ++i) { + for (j = 0; j < timeout && (readb(a_priv->borg_base) & BORG_READY_BIT) == 0; ++j) { + if (need_resched()) + schedule(); + usleep_range(10, 20); + } + if (j == timeout) { + dev_err(board->gpib_dev, "timed out loading firmware.\n"); + return -ETIMEDOUT; + } + writeb(firmware_data[i], a_priv->gpib_base + CONFIG_DATA_REG); + } + for (j = 0; j < timeout && (readb(a_priv->borg_base) & BORG_DONE_BIT) == 0; ++j) { + if (need_resched()) + schedule(); + usleep_range(10, 20); + } + if (j == timeout) { + dev_err(board->gpib_dev, "timed out waiting for firmware load to complete.\n"); + return -ETIMEDOUT; + } + dev_dbg(board->gpib_dev, " ...done.\n"); + return 0; +} + +static int test_sram(struct gpib_board *board) + +{ + struct agilent_82350b_priv *a_priv = board->private_data; + unsigned int i; + const unsigned int sram_length = pci_resource_len(a_priv->pci_device, SRAM_82350A_REGION); + /* test SRAM */ + const unsigned int byte_mask = 0xff; + + for (i = 0; i < sram_length; ++i) { + writeb(i & byte_mask, a_priv->sram_base + i); + if (need_resched()) + schedule(); + } + for (i = 0; i < sram_length; ++i) { + unsigned int read_value = readb(a_priv->sram_base + i); + + if ((i & byte_mask) != read_value) { + dev_err(board->gpib_dev, "SRAM test failed at %d wanted %d got %d\n", + i, (i & byte_mask), read_value); + return -EIO; + } + if (need_resched()) + schedule(); + } + dev_dbg(board->gpib_dev, "SRAM test passed 0x%x bytes checked\n", sram_length); + return 0; +} + +static int agilent_82350b_generic_attach(struct gpib_board *board, + const struct gpib_board_config *config, + int use_fifos) + +{ + struct agilent_82350b_priv *a_priv; + struct tms9914_priv *tms_priv; + int retval; + + board->status = 0; + + if (agilent_82350b_allocate_private(board)) + return -ENOMEM; + a_priv = board->private_data; + a_priv->using_fifos = use_fifos; + tms_priv = &a_priv->tms9914_priv; + tms_priv->read_byte = tms9914_iomem_read_byte; + tms_priv->write_byte = tms9914_iomem_write_byte; + tms_priv->offset = 1; + + /* find board */ + a_priv->pci_device = gpib_pci_get_device(config, PCI_VENDOR_ID_AGILENT, + PCI_DEVICE_ID_82350B, NULL); + if (a_priv->pci_device) { + a_priv->model = MODEL_82350B; + dev_dbg(board->gpib_dev, "Agilent 82350B board found\n"); + + } else { + a_priv->pci_device = gpib_pci_get_device(config, PCI_VENDOR_ID_AGILENT, + PCI_DEVICE_ID_82351A, NULL); + if (a_priv->pci_device) { + a_priv->model = MODEL_82351A; + dev_dbg(board->gpib_dev, "Agilent 82351B board found\n"); + + } else { + a_priv->pci_device = gpib_pci_get_subsys(config, PCI_VENDOR_ID_PLX, + PCI_DEVICE_ID_PLX_9050, + PCI_VENDOR_ID_HP, + PCI_SUBDEVICE_ID_82350A, + a_priv->pci_device); + if (a_priv->pci_device) { + a_priv->model = MODEL_82350A; + dev_dbg(board->gpib_dev, "HP/Agilent 82350A board found\n"); + } else { + dev_err(board->gpib_dev, "no 82350/82351 board found\n"); + return -ENODEV; + } + } + } + if (pci_enable_device(a_priv->pci_device)) { + dev_err(board->gpib_dev, "error enabling pci device\n"); + return -EIO; + } + if (pci_request_regions(a_priv->pci_device, DRV_NAME)) + return -ENOMEM; + switch (a_priv->model) { + case MODEL_82350A: + a_priv->plx_base = ioremap(pci_resource_start(a_priv->pci_device, PLX_MEM_REGION), + pci_resource_len(a_priv->pci_device, PLX_MEM_REGION)); + dev_dbg(board->gpib_dev, "plx base address remapped to 0x%p\n", a_priv->plx_base); + a_priv->gpib_base = ioremap(pci_resource_start(a_priv->pci_device, + GPIB_82350A_REGION), + pci_resource_len(a_priv->pci_device, + GPIB_82350A_REGION)); + dev_dbg(board->gpib_dev, "chip base address remapped to 0x%p\n", a_priv->gpib_base); + tms_priv->mmiobase = a_priv->gpib_base + TMS9914_BASE_REG; + a_priv->sram_base = ioremap(pci_resource_start(a_priv->pci_device, + SRAM_82350A_REGION), + pci_resource_len(a_priv->pci_device, + SRAM_82350A_REGION)); + dev_dbg(board->gpib_dev, "sram base address remapped to 0x%p\n", a_priv->sram_base); + a_priv->borg_base = ioremap(pci_resource_start(a_priv->pci_device, + BORG_82350A_REGION), + pci_resource_len(a_priv->pci_device, + BORG_82350A_REGION)); + dev_dbg(board->gpib_dev, "borg base address remapped to 0x%p\n", a_priv->borg_base); + + retval = init_82350a_hardware(board, config); + if (retval < 0) + return retval; + break; + case MODEL_82350B: + case MODEL_82351A: + a_priv->gpib_base = ioremap(pci_resource_start(a_priv->pci_device, GPIB_REGION), + pci_resource_len(a_priv->pci_device, GPIB_REGION)); + dev_dbg(board->gpib_dev, "chip base address remapped to 0x%p\n", a_priv->gpib_base); + tms_priv->mmiobase = a_priv->gpib_base + TMS9914_BASE_REG; + a_priv->sram_base = ioremap(pci_resource_start(a_priv->pci_device, SRAM_REGION), + pci_resource_len(a_priv->pci_device, SRAM_REGION)); + dev_dbg(board->gpib_dev, "sram base address remapped to 0x%p\n", a_priv->sram_base); + a_priv->misc_base = ioremap(pci_resource_start(a_priv->pci_device, MISC_REGION), + pci_resource_len(a_priv->pci_device, MISC_REGION)); + dev_dbg(board->gpib_dev, "misc base address remapped to 0x%p\n", a_priv->misc_base); + break; + default: + dev_err(board->gpib_dev, "invalid board\n"); + return -ENODEV; + } + + retval = test_sram(board); + if (retval < 0) + return retval; + + if (request_irq(a_priv->pci_device->irq, agilent_82350b_interrupt, + IRQF_SHARED, DRV_NAME, board)) { + dev_err(board->gpib_dev, "failed to obtain irq %d\n", a_priv->pci_device->irq); + return -EIO; + } + a_priv->irq = a_priv->pci_device->irq; + dev_dbg(board->gpib_dev, " IRQ %d\n", a_priv->irq); + + writeb(0, a_priv->gpib_base + SRAM_ACCESS_CONTROL_REG); + a_priv->card_mode_bits = ENABLE_PCI_IRQ_BIT; + writeb(a_priv->card_mode_bits, a_priv->gpib_base + CARD_MODE_REG); + + if (a_priv->model == MODEL_82350A) { + /* enable PCI interrupts for 82350a */ + writel(PLX9050_LINTR1_EN_BIT | PLX9050_LINTR2_POLARITY_BIT | + PLX9050_PCI_INTR_EN_BIT, + a_priv->plx_base + PLX9050_INTCSR_REG); + } + + if (use_fifos) { + writeb(ENABLE_BUFFER_END_EVENTS_BIT | ENABLE_TERM_COUNT_EVENTS_BIT, + a_priv->gpib_base + EVENT_ENABLE_REG); + writeb(ENABLE_TERM_COUNT_INTERRUPT_BIT | ENABLE_BUFFER_END_INTERRUPT_BIT | + ENABLE_TMS9914_INTERRUPTS_BIT, a_priv->gpib_base + INTERRUPT_ENABLE_REG); + /* write-clear event status bits */ + writeb(BUFFER_END_STATUS_BIT | TERM_COUNT_STATUS_BIT, + a_priv->gpib_base + EVENT_STATUS_REG); + } else { + writeb(0, a_priv->gpib_base + EVENT_ENABLE_REG); + writeb(ENABLE_TMS9914_INTERRUPTS_BIT, + a_priv->gpib_base + INTERRUPT_ENABLE_REG); + } + board->t1_nano_sec = agilent_82350b_t1_delay(board, 2000); + tms9914_board_reset(tms_priv); + + tms9914_online(board, tms_priv); + + return 0; +} + +static int agilent_82350b_unaccel_attach(struct gpib_board *board, + const struct gpib_board_config *config) +{ + return agilent_82350b_generic_attach(board, config, 0); +} + +static int agilent_82350b_accel_attach(struct gpib_board *board, + const struct gpib_board_config *config) +{ + return agilent_82350b_generic_attach(board, config, 1); +} + +static void agilent_82350b_detach(struct gpib_board *board) +{ + struct agilent_82350b_priv *a_priv = board->private_data; + struct tms9914_priv *tms_priv; + + if (a_priv) { + if (a_priv->plx_base) /* disable interrupts */ + writel(0, a_priv->plx_base + PLX9050_INTCSR_REG); + + tms_priv = &a_priv->tms9914_priv; + if (a_priv->irq) + free_irq(a_priv->irq, board); + if (a_priv->gpib_base) { + tms9914_board_reset(tms_priv); + if (a_priv->misc_base) + iounmap(a_priv->misc_base); + if (a_priv->borg_base) + iounmap(a_priv->borg_base); + if (a_priv->sram_base) + iounmap(a_priv->sram_base); + if (a_priv->gpib_base) + iounmap(a_priv->gpib_base); + if (a_priv->plx_base) + iounmap(a_priv->plx_base); + pci_release_regions(a_priv->pci_device); + } + if (a_priv->pci_device) + pci_dev_put(a_priv->pci_device); + } + agilent_82350b_free_private(board); +} + +static struct gpib_interface agilent_82350b_unaccel_interface = { + .name = "agilent_82350b_unaccel", + .attach = agilent_82350b_unaccel_attach, + .detach = agilent_82350b_detach, + .read = agilent_82350b_read, + .write = agilent_82350b_write, + .command = agilent_82350b_command, + .request_system_control = agilent_82350b_request_system_control, + .take_control = agilent_82350b_take_control, + .go_to_standby = agilent_82350b_go_to_standby, + .interface_clear = agilent_82350b_interface_clear, + .remote_enable = agilent_82350b_remote_enable, + .enable_eos = agilent_82350b_enable_eos, + .disable_eos = agilent_82350b_disable_eos, + .parallel_poll = agilent_82350b_parallel_poll, + .parallel_poll_configure = agilent_82350b_parallel_poll_configure, + .parallel_poll_response = agilent_82350b_parallel_poll_response, + .local_parallel_poll_mode = NULL, /* XXX */ + .line_status = agilent_82350b_line_status, + .update_status = agilent_82350b_update_status, + .primary_address = agilent_82350b_primary_address, + .secondary_address = agilent_82350b_secondary_address, + .serial_poll_response = agilent_82350b_serial_poll_response, + .serial_poll_status = agilent_82350b_serial_poll_status, + .t1_delay = agilent_82350b_t1_delay, + .return_to_local = agilent_82350b_return_to_local, +}; + +static struct gpib_interface agilent_82350b_interface = { + .name = "agilent_82350b", + .attach = agilent_82350b_accel_attach, + .detach = agilent_82350b_detach, + .read = agilent_82350b_accel_read, + .write = agilent_82350b_accel_write, + .command = agilent_82350b_command, + .request_system_control = agilent_82350b_request_system_control, + .take_control = agilent_82350b_take_control, + .go_to_standby = agilent_82350b_go_to_standby, + .interface_clear = agilent_82350b_interface_clear, + .remote_enable = agilent_82350b_remote_enable, + .enable_eos = agilent_82350b_enable_eos, + .disable_eos = agilent_82350b_disable_eos, + .parallel_poll = agilent_82350b_parallel_poll, + .parallel_poll_configure = agilent_82350b_parallel_poll_configure, + .parallel_poll_response = agilent_82350b_parallel_poll_response, + .local_parallel_poll_mode = NULL, /* XXX */ + .line_status = agilent_82350b_line_status, + .update_status = agilent_82350b_update_status, + .primary_address = agilent_82350b_primary_address, + .secondary_address = agilent_82350b_secondary_address, + .serial_poll_response = agilent_82350b_serial_poll_response, + .serial_poll_status = agilent_82350b_serial_poll_status, + .t1_delay = agilent_82350b_t1_delay, + .return_to_local = agilent_82350b_return_to_local, +}; + +static int agilent_82350b_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) + +{ + return 0; +} + +static const struct pci_device_id agilent_82350b_pci_table[] = { + { PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_9050, PCI_VENDOR_ID_HP, + PCI_SUBDEVICE_ID_82350A, 0, 0, 0 }, + { PCI_VENDOR_ID_AGILENT, PCI_DEVICE_ID_82350B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_AGILENT, PCI_DEVICE_ID_82351A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, agilent_82350b_pci_table); + +static struct pci_driver agilent_82350b_pci_driver = { + .name = DRV_NAME, + .id_table = agilent_82350b_pci_table, + .probe = &agilent_82350b_pci_probe +}; + +static int __init agilent_82350b_init_module(void) +{ + int result; + + result = pci_register_driver(&agilent_82350b_pci_driver); + if (result) { + pr_err("pci_register_driver failed: error = %d\n", result); + return result; + } + + result = gpib_register_driver(&agilent_82350b_unaccel_interface, THIS_MODULE); + if (result) { + pr_err("gpib_register_driver failed: error = %d\n", result); + goto err_unaccel; + } + + result = gpib_register_driver(&agilent_82350b_interface, THIS_MODULE); + if (result) { + pr_err("gpib_register_driver failed: error = %d\n", result); + goto err_interface; + } + + return 0; + +err_interface: + gpib_unregister_driver(&agilent_82350b_unaccel_interface); +err_unaccel: + pci_unregister_driver(&agilent_82350b_pci_driver); + + return result; +} + +static void __exit agilent_82350b_exit_module(void) +{ + gpib_unregister_driver(&agilent_82350b_interface); + gpib_unregister_driver(&agilent_82350b_unaccel_interface); + + pci_unregister_driver(&agilent_82350b_pci_driver); +} + +module_init(agilent_82350b_init_module); +module_exit(agilent_82350b_exit_module); diff --git a/drivers/gpib/agilent_82350b/agilent_82350b.h b/drivers/gpib/agilent_82350b/agilent_82350b.h new file mode 100644 index 000000000000..ef841957297f --- /dev/null +++ b/drivers/gpib/agilent_82350b/agilent_82350b.h @@ -0,0 +1,157 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/*************************************************************************** + * copyright : (C) 2002, 2004 by Frank Mori Hess * + ***************************************************************************/ + +#include "gpibP.h" +#include "plx9050.h" +#include "tms9914.h" + +enum pci_vendor_ids { + PCI_VENDOR_ID_AGILENT = 0x15bc, +}; + +enum pci_device_ids { + PCI_DEVICE_ID_82350B = 0x0b01, + PCI_DEVICE_ID_82351A = 0x1218 +}; + +enum pci_subdevice_ids { + PCI_SUBDEVICE_ID_82350A = 0x10b0, +}; + +enum pci_regions_82350a { + PLX_MEM_REGION = 0, + PLX_IO_REGION = 1, + GPIB_82350A_REGION = 2, + SRAM_82350A_REGION = 3, + BORG_82350A_REGION = 4 +}; + +enum pci_regions_82350b { + GPIB_REGION = 0, + SRAM_REGION = 1, + MISC_REGION = 2, +}; + +enum board_model { + MODEL_82350A, + MODEL_82350B, + MODEL_82351A +}; + +/* struct which defines private_data for board */ +struct agilent_82350b_priv { + struct tms9914_priv tms9914_priv; + struct pci_dev *pci_device; + void __iomem *plx_base; /* 82350a only */ + void __iomem *gpib_base; + void __iomem *sram_base; + void __iomem *misc_base; + void __iomem *borg_base; + int irq; + unsigned short card_mode_bits; + unsigned short event_status_bits; + enum board_model model; + bool using_fifos; +}; + +/* registers */ +enum agilent_82350b_gpib_registers + +{ + CARD_MODE_REG = 0x1, + CONFIG_DATA_REG = 0x2, /* 82350A specific */ + INTERRUPT_ENABLE_REG = 0x3, + EVENT_STATUS_REG = 0x4, + EVENT_ENABLE_REG = 0x5, + STREAM_STATUS_REG = 0x7, + DEBUG_RAM0_REG = 0x8, + DEBUG_RAM1_REG = 0x9, + DEBUG_RAM2_REG = 0xa, + DEBUG_RAM3_REG = 0xb, + XFER_COUNT_LO_REG = 0xc, + XFER_COUNT_MID_REG = 0xd, + XFER_COUNT_HI_REG = 0xe, + TMS9914_BASE_REG = 0x10, + INTERNAL_CONFIG_REG = 0x18, + IMR0_READ_REG = 0x19, /* read */ + T1_DELAY_REG = 0x19, /* write */ + IMR1_READ_REG = 0x1a, + ADR_READ_REG = 0x1b, + SPMR_READ_REG = 0x1c, + PPR_READ_REG = 0x1d, + CDOR_READ_REG = 0x1e, + SRAM_ACCESS_CONTROL_REG = 0x1f, +}; + +enum card_mode_bits + +{ + ACTIVE_CONTROLLER_BIT = 0x2, /* read-only */ + CM_SYSTEM_CONTROLLER_BIT = 0x8, + ENABLE_BUS_MONITOR_BIT = 0x10, + ENABLE_PCI_IRQ_BIT = 0x20, +}; + +enum interrupt_enable_bits + +{ + ENABLE_TMS9914_INTERRUPTS_BIT = 0x1, + ENABLE_BUFFER_END_INTERRUPT_BIT = 0x10, + ENABLE_TERM_COUNT_INTERRUPT_BIT = 0x20, +}; + +enum event_enable_bits + +{ + ENABLE_BUFFER_END_EVENTS_BIT = 0x10, + ENABLE_TERM_COUNT_EVENTS_BIT = 0x20, +}; + +enum event_status_bits + +{ + TMS9914_IRQ_STATUS_BIT = 0x1, + IRQ_STATUS_BIT = 0x2, + BUFFER_END_STATUS_BIT = 0x10, /* write-clear */ + TERM_COUNT_STATUS_BIT = 0x20, /* write-clear */ +}; + +enum stream_status_bits + +{ + HALTED_STATUS_BIT = 0x1, /* read */ + RESTART_STREAM_BIT = 0x1, /* write */ +}; + +enum internal_config_bits + +{ + IC_SYSTEM_CONTROLLER_BIT = 0x80, +}; + +enum sram_access_control_bits + +{ + DIRECTION_GPIB_TO_HOST = 0x20, /* transfer direction */ + ENABLE_TI_TO_SRAM = 0x40, /* enable fifo */ + ENABLE_FAST_TALKER = 0x80 /* added for 82350A (not used) */ +}; + +enum borg_bits + +{ + BORG_READY_BIT = 0x40, + BORG_DONE_BIT = 0x80 +}; + +static const int agilent_82350b_fifo_size = 0x8000; + +static inline int agilent_82350b_fifo_is_halted(struct agilent_82350b_priv *a_priv) + +{ + return readb(a_priv->gpib_base + STREAM_STATUS_REG) & HALTED_STATUS_BIT; +} + diff --git a/drivers/gpib/agilent_82357a/Makefile b/drivers/gpib/agilent_82357a/Makefile new file mode 100644 index 000000000000..81a55c257a6e --- /dev/null +++ b/drivers/gpib/agilent_82357a/Makefile @@ -0,0 +1,4 @@ + +obj-$(CONFIG_GPIB_AGILENT_82357A) += agilent_82357a.o + + diff --git a/drivers/gpib/agilent_82357a/agilent_82357a.c b/drivers/gpib/agilent_82357a/agilent_82357a.c new file mode 100644 index 000000000000..77c8e549b208 --- /dev/null +++ b/drivers/gpib/agilent_82357a/agilent_82357a.c @@ -0,0 +1,1691 @@ +// SPDX-License-Identifier: GPL-2.0 + +/*************************************************************************** + * driver for Agilent 82357A/B usb to gpib adapters * + * copyright : (C) 2004 by Frank Mori Hess * + ***************************************************************************/ + +#define _GNU_SOURCE + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#define dev_fmt pr_fmt +#define DRV_NAME KBUILD_MODNAME + +#include +#include +#include +#include "agilent_82357a.h" +#include "gpibP.h" +#include "tms9914.h" + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("GPIB driver for Agilent 82357A/B usb adapters"); + +#define MAX_NUM_82357A_INTERFACES 128 +static struct usb_interface *agilent_82357a_driver_interfaces[MAX_NUM_82357A_INTERFACES]; +static DEFINE_MUTEX(agilent_82357a_hotplug_lock); // protect board insertion and removal + +static unsigned int agilent_82357a_update_status(struct gpib_board *board, + unsigned int clear_mask); + +static int agilent_82357a_take_control_internal(struct gpib_board *board, int synchronous); + +static void agilent_82357a_bulk_complete(struct urb *urb) +{ + struct agilent_82357a_urb_ctx *context = urb->context; + + complete(&context->complete); +} + +static void agilent_82357a_timeout_handler(struct timer_list *t) +{ + struct agilent_82357a_priv *a_priv = timer_container_of(a_priv, t, + bulk_timer); + struct agilent_82357a_urb_ctx *context = &a_priv->context; + + context->timed_out = 1; + complete(&context->complete); +} + +static int agilent_82357a_send_bulk_msg(struct agilent_82357a_priv *a_priv, void *data, + int data_length, int *actual_data_length, + int timeout_msecs) +{ + struct usb_device *usb_dev; + int retval; + unsigned int out_pipe; + struct agilent_82357a_urb_ctx *context = &a_priv->context; + + *actual_data_length = 0; + retval = mutex_lock_interruptible(&a_priv->bulk_alloc_lock); + if (retval) + return retval; + if (!a_priv->bus_interface) { + mutex_unlock(&a_priv->bulk_alloc_lock); + return -ENODEV; + } + if (a_priv->bulk_urb) { + mutex_unlock(&a_priv->bulk_alloc_lock); + return -EAGAIN; + } + a_priv->bulk_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!a_priv->bulk_urb) { + mutex_unlock(&a_priv->bulk_alloc_lock); + return -ENOMEM; + } + usb_dev = interface_to_usbdev(a_priv->bus_interface); + out_pipe = usb_sndbulkpipe(usb_dev, a_priv->bulk_out_endpoint); + init_completion(&context->complete); + context->timed_out = 0; + usb_fill_bulk_urb(a_priv->bulk_urb, usb_dev, out_pipe, data, data_length, + &agilent_82357a_bulk_complete, context); + + if (timeout_msecs) + mod_timer(&a_priv->bulk_timer, jiffies + msecs_to_jiffies(timeout_msecs)); + + retval = usb_submit_urb(a_priv->bulk_urb, GFP_KERNEL); + if (retval) { + dev_err(&usb_dev->dev, "failed to submit bulk out urb, retval=%i\n", retval); + mutex_unlock(&a_priv->bulk_alloc_lock); + goto cleanup; + } + mutex_unlock(&a_priv->bulk_alloc_lock); + if (wait_for_completion_interruptible(&context->complete)) { + retval = -ERESTARTSYS; + goto cleanup; + } + if (context->timed_out) { + retval = -ETIMEDOUT; + } else { + retval = a_priv->bulk_urb->status; + *actual_data_length = a_priv->bulk_urb->actual_length; + } +cleanup: + if (timeout_msecs) { + if (timer_pending(&a_priv->bulk_timer)) + timer_delete_sync(&a_priv->bulk_timer); + } + mutex_lock(&a_priv->bulk_alloc_lock); + if (a_priv->bulk_urb) { + usb_kill_urb(a_priv->bulk_urb); + usb_free_urb(a_priv->bulk_urb); + a_priv->bulk_urb = NULL; + } + mutex_unlock(&a_priv->bulk_alloc_lock); + return retval; +} + +static int agilent_82357a_receive_bulk_msg(struct agilent_82357a_priv *a_priv, void *data, + int data_length, int *actual_data_length, + int timeout_msecs) +{ + struct usb_device *usb_dev; + int retval; + unsigned int in_pipe; + struct agilent_82357a_urb_ctx *context = &a_priv->context; + + *actual_data_length = 0; + retval = mutex_lock_interruptible(&a_priv->bulk_alloc_lock); + if (retval) + return retval; + if (!a_priv->bus_interface) { + mutex_unlock(&a_priv->bulk_alloc_lock); + return -ENODEV; + } + if (a_priv->bulk_urb) { + mutex_unlock(&a_priv->bulk_alloc_lock); + return -EAGAIN; + } + a_priv->bulk_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!a_priv->bulk_urb) { + mutex_unlock(&a_priv->bulk_alloc_lock); + return -ENOMEM; + } + usb_dev = interface_to_usbdev(a_priv->bus_interface); + in_pipe = usb_rcvbulkpipe(usb_dev, AGILENT_82357_BULK_IN_ENDPOINT); + init_completion(&context->complete); + context->timed_out = 0; + usb_fill_bulk_urb(a_priv->bulk_urb, usb_dev, in_pipe, data, data_length, + &agilent_82357a_bulk_complete, context); + + if (timeout_msecs) + mod_timer(&a_priv->bulk_timer, jiffies + msecs_to_jiffies(timeout_msecs)); + + retval = usb_submit_urb(a_priv->bulk_urb, GFP_KERNEL); + if (retval) { + dev_err(&usb_dev->dev, "failed to submit bulk in urb, retval=%i\n", retval); + mutex_unlock(&a_priv->bulk_alloc_lock); + goto cleanup; + } + mutex_unlock(&a_priv->bulk_alloc_lock); + if (wait_for_completion_interruptible(&context->complete)) { + retval = -ERESTARTSYS; + goto cleanup; + } + if (context->timed_out) { + retval = -ETIMEDOUT; + goto cleanup; + } + retval = a_priv->bulk_urb->status; + *actual_data_length = a_priv->bulk_urb->actual_length; +cleanup: + if (timeout_msecs) + timer_delete_sync(&a_priv->bulk_timer); + + mutex_lock(&a_priv->bulk_alloc_lock); + if (a_priv->bulk_urb) { + usb_kill_urb(a_priv->bulk_urb); + usb_free_urb(a_priv->bulk_urb); + a_priv->bulk_urb = NULL; + } + mutex_unlock(&a_priv->bulk_alloc_lock); + return retval; +} + +static int agilent_82357a_receive_control_msg(struct agilent_82357a_priv *a_priv, __u8 request, + __u8 requesttype, __u16 value, __u16 index, + void *data, __u16 size, int timeout_msecs) +{ + struct usb_device *usb_dev; + int retval; + unsigned int in_pipe; + + retval = mutex_lock_interruptible(&a_priv->control_alloc_lock); + if (retval) + return retval; + if (!a_priv->bus_interface) { + mutex_unlock(&a_priv->control_alloc_lock); + return -ENODEV; + } + usb_dev = interface_to_usbdev(a_priv->bus_interface); + in_pipe = usb_rcvctrlpipe(usb_dev, AGILENT_82357_CONTROL_ENDPOINT); + retval = usb_control_msg(usb_dev, in_pipe, request, requesttype, value, index, data, + size, timeout_msecs); + mutex_unlock(&a_priv->control_alloc_lock); + return retval; +} + +static void agilent_82357a_dump_raw_block(const u8 *raw_data, int length) +{ + print_hex_dump(KERN_INFO, "", DUMP_PREFIX_NONE, 8, 1, raw_data, length, true); +} + +static int agilent_82357a_write_registers(struct agilent_82357a_priv *a_priv, + const struct agilent_82357a_register_pairlet *writes, + int num_writes) +{ + struct usb_device *usb_dev = interface_to_usbdev(a_priv->bus_interface); + int retval; + u8 *out_data, *in_data; + int out_data_length, in_data_length; + int bytes_written, bytes_read; + int i = 0; + int j; + static const int bytes_per_write = 2; + static const int header_length = 2; + static const int max_writes = 31; + + if (num_writes > max_writes) { + dev_err(&usb_dev->dev, "bug! num_writes=%i too large\n", num_writes); + return -EIO; + } + out_data_length = num_writes * bytes_per_write + header_length; + out_data = kmalloc(out_data_length, GFP_KERNEL); + if (!out_data) + return -ENOMEM; + + out_data[i++] = DATA_PIPE_CMD_WR_REGS; + out_data[i++] = num_writes; + for (j = 0; j < num_writes; j++) { + out_data[i++] = writes[j].address; + out_data[i++] = writes[j].value; + } + + retval = mutex_lock_interruptible(&a_priv->bulk_transfer_lock); + if (retval) { + kfree(out_data); + return retval; + } + retval = agilent_82357a_send_bulk_msg(a_priv, out_data, i, &bytes_written, 1000); + kfree(out_data); + if (retval) { + dev_err(&usb_dev->dev, "send_bulk_msg returned %i, bytes_written=%i, i=%i\n", + retval, bytes_written, i); + mutex_unlock(&a_priv->bulk_transfer_lock); + return retval; + } + in_data_length = 0x20; + in_data = kmalloc(in_data_length, GFP_KERNEL); + if (!in_data) { + mutex_unlock(&a_priv->bulk_transfer_lock); + return -ENOMEM; + } + retval = agilent_82357a_receive_bulk_msg(a_priv, in_data, in_data_length, + &bytes_read, 1000); + mutex_unlock(&a_priv->bulk_transfer_lock); + + if (retval) { + dev_err(&usb_dev->dev, "receive_bulk_msg returned %i, bytes_read=%i\n", + retval, bytes_read); + agilent_82357a_dump_raw_block(in_data, bytes_read); + kfree(in_data); + return -EIO; + } + if (in_data[0] != (0xff & ~DATA_PIPE_CMD_WR_REGS)) { + dev_err(&usb_dev->dev, "bulk command=0x%x != ~DATA_PIPE_CMD_WR_REGS\n", in_data[0]); + return -EIO; + } + if (in_data[1]) { + dev_err(&usb_dev->dev, "nonzero error code 0x%x in DATA_PIPE_CMD_WR_REGS response\n", + in_data[1]); + return -EIO; + } + kfree(in_data); + return 0; +} + +static int agilent_82357a_read_registers(struct agilent_82357a_priv *a_priv, + struct agilent_82357a_register_pairlet *reads, + int num_reads, int blocking) +{ + struct usb_device *usb_dev = interface_to_usbdev(a_priv->bus_interface); + int retval; + u8 *out_data, *in_data; + int out_data_length, in_data_length; + int bytes_written, bytes_read; + int i = 0; + int j; + static const int header_length = 2; + static const int max_reads = 62; + + if (num_reads > max_reads) { + dev_err(&usb_dev->dev, "bug! num_reads=%i too large\n", num_reads); + return -EIO; + } + out_data_length = num_reads + header_length; + out_data = kmalloc(out_data_length, GFP_KERNEL); + if (!out_data) + return -ENOMEM; + + out_data[i++] = DATA_PIPE_CMD_RD_REGS; + out_data[i++] = num_reads; + for (j = 0; j < num_reads; j++) + out_data[i++] = reads[j].address; + + if (blocking) { + retval = mutex_lock_interruptible(&a_priv->bulk_transfer_lock); + if (retval) { + kfree(out_data); + return retval; + } + } else { + retval = mutex_trylock(&a_priv->bulk_transfer_lock); + if (retval == 0) { + kfree(out_data); + return -EAGAIN; + } + } + retval = agilent_82357a_send_bulk_msg(a_priv, out_data, i, &bytes_written, 1000); + kfree(out_data); + if (retval) { + dev_err(&usb_dev->dev, "send_bulk_msg returned %i, bytes_written=%i, i=%i\n", + retval, bytes_written, i); + mutex_unlock(&a_priv->bulk_transfer_lock); + return retval; + } + in_data_length = 0x20; + in_data = kmalloc(in_data_length, GFP_KERNEL); + if (!in_data) { + mutex_unlock(&a_priv->bulk_transfer_lock); + return -ENOMEM; + } + retval = agilent_82357a_receive_bulk_msg(a_priv, in_data, in_data_length, + &bytes_read, 10000); + mutex_unlock(&a_priv->bulk_transfer_lock); + + if (retval) { + dev_err(&usb_dev->dev, "receive_bulk_msg returned %i, bytes_read=%i\n", + retval, bytes_read); + agilent_82357a_dump_raw_block(in_data, bytes_read); + kfree(in_data); + return -EIO; + } + i = 0; + if (in_data[i++] != (0xff & ~DATA_PIPE_CMD_RD_REGS)) { + dev_err(&usb_dev->dev, "bulk command=0x%x != ~DATA_PIPE_CMD_RD_REGS\n", in_data[0]); + return -EIO; + } + if (in_data[i++]) { + dev_err(&usb_dev->dev, "nonzero error code 0x%x in DATA_PIPE_CMD_RD_REGS response\n", + in_data[1]); + return -EIO; + } + for (j = 0; j < num_reads; j++) + reads[j].value = in_data[i++]; + kfree(in_data); + return 0; +} + +static int agilent_82357a_abort(struct agilent_82357a_priv *a_priv, int flush) +{ + struct usb_device *usb_dev = interface_to_usbdev(a_priv->bus_interface); + int retval = 0; + int receive_control_retval; + u16 wIndex = 0; + u8 *status_data; + static const unsigned int status_data_len = 2; + + status_data = kmalloc(status_data_len, GFP_KERNEL); + if (!status_data) + return -ENOMEM; + + if (flush) + wIndex |= XA_FLUSH; + receive_control_retval = agilent_82357a_receive_control_msg(a_priv, + agilent_82357a_control_request, + USB_DIR_IN | USB_TYPE_VENDOR | + USB_RECIP_DEVICE, XFER_ABORT, + wIndex, status_data, + status_data_len, 100); + if (receive_control_retval < 0) { + dev_err(&usb_dev->dev, "82357a_receive_control_msg() returned %i\n", + receive_control_retval); + retval = -EIO; + goto cleanup; + } + if (status_data[0] != (~XFER_ABORT & 0xff)) { + dev_err(&usb_dev->dev, "major code=0x%x != ~XFER_ABORT\n", status_data[0]); + retval = -EIO; + goto cleanup; + } + switch (status_data[1]) { + case UGP_SUCCESS: + retval = 0; + break; + case UGP_ERR_FLUSHING: + if (flush) { + retval = 0; + break; + } + fallthrough; + case UGP_ERR_FLUSHING_ALREADY: + default: + dev_err(&usb_dev->dev, "abort returned error code=0x%x\n", status_data[1]); + retval = -EIO; + break; + } + +cleanup: + kfree(status_data); + return retval; +} + +// interface functions +int agilent_82357a_command(struct gpib_board *board, u8 *buffer, size_t length, + size_t *bytes_written); + +static int agilent_82357a_read(struct gpib_board *board, u8 *buffer, size_t length, int *end, + size_t *nbytes) +{ + int retval; + struct agilent_82357a_priv *a_priv = board->private_data; + struct usb_device *usb_dev; + u8 *out_data, *in_data; + int out_data_length, in_data_length; + int bytes_written, bytes_read; + int i = 0; + u8 trailing_flags; + unsigned long start_jiffies = jiffies; + int msec_timeout; + + *nbytes = 0; + *end = 0; + + if (!a_priv->bus_interface) + return -ENODEV; + usb_dev = interface_to_usbdev(a_priv->bus_interface); + out_data_length = 0x9; + out_data = kmalloc(out_data_length, GFP_KERNEL); + if (!out_data) + return -ENOMEM; + out_data[i++] = DATA_PIPE_CMD_READ; + out_data[i++] = 0; // primary address when ARF_NO_ADDR is not set + out_data[i++] = 0; // secondary address when ARF_NO_ADDR is not set + out_data[i] = ARF_NO_ADDRESS | ARF_END_ON_EOI; + if (a_priv->eos_mode & REOS) + out_data[i] |= ARF_END_ON_EOS_CHAR; + ++i; + out_data[i++] = length & 0xff; + out_data[i++] = (length >> 8) & 0xff; + out_data[i++] = (length >> 16) & 0xff; + out_data[i++] = (length >> 24) & 0xff; + out_data[i++] = a_priv->eos_char; + msec_timeout = (board->usec_timeout + 999) / 1000; + retval = mutex_lock_interruptible(&a_priv->bulk_transfer_lock); + if (retval) { + kfree(out_data); + return retval; + } + retval = agilent_82357a_send_bulk_msg(a_priv, out_data, i, &bytes_written, msec_timeout); + kfree(out_data); + if (retval || bytes_written != i) { + dev_err(&usb_dev->dev, "send_bulk_msg returned %i, bytes_written=%i, i=%i\n", + retval, bytes_written, i); + mutex_unlock(&a_priv->bulk_transfer_lock); + if (retval < 0) + return retval; + return -EIO; + } + in_data_length = length + 1; + in_data = kmalloc(in_data_length, GFP_KERNEL); + if (!in_data) { + mutex_unlock(&a_priv->bulk_transfer_lock); + return -ENOMEM; + } + if (board->usec_timeout != 0) + msec_timeout -= jiffies_to_msecs(jiffies - start_jiffies) - 1; + if (msec_timeout >= 0) { + retval = agilent_82357a_receive_bulk_msg(a_priv, in_data, in_data_length, + &bytes_read, msec_timeout); + } else { + retval = -ETIMEDOUT; + bytes_read = 0; + } + if (retval == -ETIMEDOUT) { + int extra_bytes_read; + int extra_bytes_retval; + + agilent_82357a_abort(a_priv, 1); + extra_bytes_retval = agilent_82357a_receive_bulk_msg(a_priv, in_data + bytes_read, + in_data_length - bytes_read, + &extra_bytes_read, 100); + bytes_read += extra_bytes_read; + if (extra_bytes_retval) { + dev_err(&usb_dev->dev, "extra_bytes_retval=%i, bytes_read=%i\n", + extra_bytes_retval, bytes_read); + agilent_82357a_abort(a_priv, 0); + } + } else if (retval) { + dev_err(&usb_dev->dev, "receive_bulk_msg returned %i, bytes_read=%i\n", + retval, bytes_read); + agilent_82357a_abort(a_priv, 0); + } + mutex_unlock(&a_priv->bulk_transfer_lock); + if (bytes_read > length + 1) { + bytes_read = length + 1; + dev_warn(&usb_dev->dev, "bytes_read > length? truncating"); + } + + if (bytes_read >= 1) { + memcpy(buffer, in_data, bytes_read - 1); + trailing_flags = in_data[bytes_read - 1]; + *nbytes = bytes_read - 1; + if (trailing_flags & (ATRF_EOI | ATRF_EOS)) + *end = 1; + } + kfree(in_data); + + /* + * Fix for a bug in 9914A that does not return the contents of ADSR + * when the board is in listener active state and ATN is not asserted. + * Set ATN here to obtain a valid board level ibsta + */ + agilent_82357a_take_control_internal(board, 0); + + // FIXME check trailing flags for error + return retval; +} + +static ssize_t agilent_82357a_generic_write(struct gpib_board *board, + u8 *buffer, size_t length, + int send_commands, int send_eoi, + size_t *bytes_written) +{ + int retval; + struct agilent_82357a_priv *a_priv = board->private_data; + struct usb_device *usb_dev; + u8 *out_data = NULL; + u8 *status_data = NULL; + int out_data_length; + int raw_bytes_written; + int i = 0, j; + int msec_timeout; + unsigned short bsr, adsr; + struct agilent_82357a_register_pairlet read_reg; + + *bytes_written = 0; + if (!a_priv->bus_interface) + return -ENODEV; + + usb_dev = interface_to_usbdev(a_priv->bus_interface); + out_data_length = length + 0x8; + out_data = kmalloc(out_data_length, GFP_KERNEL); + if (!out_data) + return -ENOMEM; + out_data[i++] = DATA_PIPE_CMD_WRITE; + out_data[i++] = 0; // primary address when AWF_NO_ADDRESS is not set + out_data[i++] = 0; // secondary address when AWF_NO_ADDRESS is not set + out_data[i] = AWF_NO_ADDRESS | AWF_NO_FAST_TALKER_FIRST_BYTE; + if (send_commands) + out_data[i] |= AWF_ATN | AWF_NO_FAST_TALKER; + if (send_eoi) + out_data[i] |= AWF_SEND_EOI; + ++i; + out_data[i++] = length & 0xff; + out_data[i++] = (length >> 8) & 0xff; + out_data[i++] = (length >> 16) & 0xff; + out_data[i++] = (length >> 24) & 0xff; + for (j = 0; j < length; j++) + out_data[i++] = buffer[j]; + + clear_bit(AIF_WRITE_COMPLETE_BN, &a_priv->interrupt_flags); + + msec_timeout = (board->usec_timeout + 999) / 1000; + retval = mutex_lock_interruptible(&a_priv->bulk_transfer_lock); + if (retval) { + kfree(out_data); + return retval; + } + retval = agilent_82357a_send_bulk_msg(a_priv, out_data, i, &raw_bytes_written, + msec_timeout); + kfree(out_data); + if (retval || raw_bytes_written != i) { + agilent_82357a_abort(a_priv, 0); + dev_err(&usb_dev->dev, "send_bulk_msg returned %i, raw_bytes_written=%i, i=%i\n", + retval, raw_bytes_written, i); + mutex_unlock(&a_priv->bulk_transfer_lock); + if (retval < 0) + return retval; + return -EIO; + } + + retval = wait_event_interruptible(board->wait, + test_bit(AIF_WRITE_COMPLETE_BN, + &a_priv->interrupt_flags) || + test_bit(TIMO_NUM, &board->status)); + if (retval) { + dev_dbg(&usb_dev->dev, "wait write complete interrupted\n"); + agilent_82357a_abort(a_priv, 0); + mutex_unlock(&a_priv->bulk_transfer_lock); + return -ERESTARTSYS; + } + + if (test_bit(AIF_WRITE_COMPLETE_BN, &a_priv->interrupt_flags) == 0) { + dev_dbg(&usb_dev->dev, "write timed out ibs %i, tmo %i\n", + test_bit(TIMO_NUM, &board->status), msec_timeout); + + agilent_82357a_abort(a_priv, 0); + + mutex_unlock(&a_priv->bulk_transfer_lock); + + read_reg.address = BSR; + retval = agilent_82357a_read_registers(a_priv, &read_reg, 1, 1); + if (retval) { + dev_err(&usb_dev->dev, "read_registers() returned error\n"); + return -ETIMEDOUT; + } + + bsr = read_reg.value; + dev_dbg(&usb_dev->dev, "write aborted bsr 0x%x\n", bsr); + + if (send_commands) {/* check for no listeners */ + if ((bsr & BSR_ATN_BIT) && !(bsr & (BSR_NDAC_BIT | BSR_NRFD_BIT))) { + dev_dbg(&usb_dev->dev, "No listener on command\n"); + clear_bit(TIMO_NUM, &board->status); + return -ENOTCONN; // no listener on bus + } + } else { + read_reg.address = ADSR; + retval = agilent_82357a_read_registers(a_priv, &read_reg, 1, 1); + if (retval) { + dev_err(&usb_dev->dev, "read_registers() returned error\n"); + return -ETIMEDOUT; + } + adsr = read_reg.value; + if ((adsr & HR_TA) && !(bsr & (BSR_NDAC_BIT | BSR_NRFD_BIT))) { + dev_dbg(&usb_dev->dev, "No listener on write\n"); + clear_bit(TIMO_NUM, &board->status); + return -ECOMM; + } + } + + return -ETIMEDOUT; + } + + status_data = kmalloc(STATUS_DATA_LEN, GFP_KERNEL); + if (!status_data) { + mutex_unlock(&a_priv->bulk_transfer_lock); + return -ENOMEM; + } + + retval = agilent_82357a_receive_control_msg(a_priv, agilent_82357a_control_request, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + XFER_STATUS, 0, status_data, STATUS_DATA_LEN, + 100); + mutex_unlock(&a_priv->bulk_transfer_lock); + if (retval < 0) { + dev_err(&usb_dev->dev, "receive_control_msg() returned %i\n", retval); + kfree(status_data); + return -EIO; + } + *bytes_written = (u32)status_data[2]; + *bytes_written |= (u32)status_data[3] << 8; + *bytes_written |= (u32)status_data[4] << 16; + *bytes_written |= (u32)status_data[5] << 24; + + kfree(status_data); + return 0; +} + +static int agilent_82357a_write(struct gpib_board *board, u8 *buffer, + size_t length, int send_eoi, size_t *bytes_written) +{ + return agilent_82357a_generic_write(board, buffer, length, 0, send_eoi, bytes_written); +} + +int agilent_82357a_command(struct gpib_board *board, u8 *buffer, size_t length, + size_t *bytes_written) +{ + return agilent_82357a_generic_write(board, buffer, length, 1, 0, bytes_written); +} + +int agilent_82357a_take_control_internal(struct gpib_board *board, int synchronous) +{ + struct agilent_82357a_priv *a_priv = board->private_data; + struct usb_device *usb_dev = interface_to_usbdev(a_priv->bus_interface); + struct agilent_82357a_register_pairlet write; + int retval; + + write.address = AUXCR; + if (synchronous) + write.value = AUX_TCS; + else + write.value = AUX_TCA; + retval = agilent_82357a_write_registers(a_priv, &write, 1); + if (retval) + dev_err(&usb_dev->dev, "write_registers() returned error\n"); + + return retval; +} + +static int agilent_82357a_take_control(struct gpib_board *board, int synchronous) +{ + struct agilent_82357a_priv *a_priv = board->private_data; + const int timeout = 10; + int i; + + if (!a_priv->bus_interface) + return -ENODEV; + +/* + * It looks like the 9914 does not handle tcs properly. + * See comment above tms9914_take_control_workaround() in + * drivers/gpib/tms9914/tms9914_aux.c + */ + if (synchronous) + return -ETIMEDOUT; + + agilent_82357a_take_control_internal(board, synchronous); + // busy wait until ATN is asserted + for (i = 0; i < timeout; ++i) { + agilent_82357a_update_status(board, 0); + if (test_bit(ATN_NUM, &board->status)) + break; + udelay(1); + } + if (i == timeout) + return -ETIMEDOUT; + return 0; +} + +static int agilent_82357a_go_to_standby(struct gpib_board *board) +{ + struct agilent_82357a_priv *a_priv = board->private_data; + struct usb_device *usb_dev; + struct agilent_82357a_register_pairlet write; + int retval; + + if (!a_priv->bus_interface) + return -ENODEV; + + usb_dev = interface_to_usbdev(a_priv->bus_interface); + write.address = AUXCR; + write.value = AUX_GTS; + retval = agilent_82357a_write_registers(a_priv, &write, 1); + if (retval) + dev_err(&usb_dev->dev, "write_registers() returned error\n"); + return 0; +} + +static int agilent_82357a_request_system_control(struct gpib_board *board, int request_control) +{ + struct agilent_82357a_priv *a_priv = board->private_data; + struct usb_device *usb_dev; + struct agilent_82357a_register_pairlet writes[2]; + int retval; + int i = 0; + + if (!a_priv->bus_interface) + return -ENODEV; + + usb_dev = interface_to_usbdev(a_priv->bus_interface); + /* 82357B needs bit to be set in 9914 AUXCR register */ + writes[i].address = AUXCR; + if (request_control) { + writes[i].value = AUX_RQC; + a_priv->hw_control_bits |= SYSTEM_CONTROLLER; + } else { + return -EINVAL; + } + ++i; + writes[i].address = HW_CONTROL; + writes[i].value = a_priv->hw_control_bits; + ++i; + retval = agilent_82357a_write_registers(a_priv, writes, i); + if (retval) + dev_err(&usb_dev->dev, "write_registers() returned error\n"); + return retval; +} + +static void agilent_82357a_interface_clear(struct gpib_board *board, int assert) +{ + struct agilent_82357a_priv *a_priv = board->private_data; + struct usb_device *usb_dev; + struct agilent_82357a_register_pairlet write; + int retval; + + if (!a_priv->bus_interface) + return; // -ENODEV; + + usb_dev = interface_to_usbdev(a_priv->bus_interface); + write.address = AUXCR; + write.value = AUX_SIC; + if (assert) { + write.value |= AUX_CS; + a_priv->is_cic = 1; + } + retval = agilent_82357a_write_registers(a_priv, &write, 1); + if (retval) + dev_err(&usb_dev->dev, "write_registers() returned error\n"); +} + +static void agilent_82357a_remote_enable(struct gpib_board *board, int enable) +{ + struct agilent_82357a_priv *a_priv = board->private_data; + struct usb_device *usb_dev; + struct agilent_82357a_register_pairlet write; + int retval; + + if (!a_priv->bus_interface) + return; //-ENODEV; + + usb_dev = interface_to_usbdev(a_priv->bus_interface); + write.address = AUXCR; + write.value = AUX_SRE; + if (enable) + write.value |= AUX_CS; + retval = agilent_82357a_write_registers(a_priv, &write, 1); + if (retval) + dev_err(&usb_dev->dev, "write_registers() returned error\n"); + a_priv->ren_state = enable; + return;// 0; +} + +static int agilent_82357a_enable_eos(struct gpib_board *board, u8 eos_byte, + int compare_8_bits) +{ + struct agilent_82357a_priv *a_priv = board->private_data; + + if (!a_priv->bus_interface) + return -ENODEV; + if (compare_8_bits == 0) + return -EOPNOTSUPP; + + a_priv->eos_char = eos_byte; + a_priv->eos_mode = REOS | BIN; + return 0; +} + +static void agilent_82357a_disable_eos(struct gpib_board *board) +{ + struct agilent_82357a_priv *a_priv = board->private_data; + + a_priv->eos_mode &= ~REOS; +} + +static unsigned int agilent_82357a_update_status(struct gpib_board *board, + unsigned int clear_mask) +{ + struct agilent_82357a_priv *a_priv = board->private_data; + struct usb_device *usb_dev; + struct agilent_82357a_register_pairlet address_status, bus_status; + int retval; + + if (!a_priv->bus_interface) + return -ENODEV; + usb_dev = interface_to_usbdev(a_priv->bus_interface); + board->status &= ~clear_mask; + if (a_priv->is_cic) + set_bit(CIC_NUM, &board->status); + else + clear_bit(CIC_NUM, &board->status); + address_status.address = ADSR; + retval = agilent_82357a_read_registers(a_priv, &address_status, 1, 0); + if (retval) { + if (retval != -EAGAIN) + dev_err(&usb_dev->dev, "read_registers() returned error\n"); + return board->status; + } + // check for remote/local + if (address_status.value & HR_REM) + set_bit(REM_NUM, &board->status); + else + clear_bit(REM_NUM, &board->status); + // check for lockout + if (address_status.value & HR_LLO) + set_bit(LOK_NUM, &board->status); + else + clear_bit(LOK_NUM, &board->status); + // check for ATN + if (address_status.value & HR_ATN) + set_bit(ATN_NUM, &board->status); + else + clear_bit(ATN_NUM, &board->status); + // check for talker/listener addressed + if (address_status.value & HR_TA) + set_bit(TACS_NUM, &board->status); + else + clear_bit(TACS_NUM, &board->status); + if (address_status.value & HR_LA) + set_bit(LACS_NUM, &board->status); + else + clear_bit(LACS_NUM, &board->status); + + bus_status.address = BSR; + retval = agilent_82357a_read_registers(a_priv, &bus_status, 1, 0); + if (retval) { + if (retval != -EAGAIN) + dev_err(&usb_dev->dev, "read_registers() returned error\n"); + return board->status; + } + if (bus_status.value & BSR_SRQ_BIT) + set_bit(SRQI_NUM, &board->status); + else + clear_bit(SRQI_NUM, &board->status); + + return board->status; +} + +static int agilent_82357a_primary_address(struct gpib_board *board, unsigned int address) +{ + struct agilent_82357a_priv *a_priv = board->private_data; + struct usb_device *usb_dev = interface_to_usbdev(a_priv->bus_interface); + struct agilent_82357a_register_pairlet write; + int retval; + + if (!a_priv->bus_interface) + return -ENODEV; + usb_dev = interface_to_usbdev(a_priv->bus_interface); + // put primary address in address0 + write.address = ADR; + write.value = address & ADDRESS_MASK; + retval = agilent_82357a_write_registers(a_priv, &write, 1); + if (retval) { + dev_err(&usb_dev->dev, "write_registers() returned error\n"); + return retval; + } + return retval; +} + +static int agilent_82357a_secondary_address(struct gpib_board *board, + unsigned int address, int enable) +{ + if (enable) + return -EOPNOTSUPP; + return 0; +} + +static int agilent_82357a_parallel_poll(struct gpib_board *board, u8 *result) +{ + struct agilent_82357a_priv *a_priv = board->private_data; + struct usb_device *usb_dev; + struct agilent_82357a_register_pairlet writes[2]; + struct agilent_82357a_register_pairlet read; + int retval; + + if (!a_priv->bus_interface) + return -ENODEV; + usb_dev = interface_to_usbdev(a_priv->bus_interface); + // execute parallel poll + writes[0].address = AUXCR; + writes[0].value = AUX_CS | AUX_RPP; + writes[1].address = HW_CONTROL; + writes[1].value = a_priv->hw_control_bits & ~NOT_PARALLEL_POLL; + retval = agilent_82357a_write_registers(a_priv, writes, 2); + if (retval) { + dev_err(&usb_dev->dev, "write_registers() returned error\n"); + return retval; + } + udelay(2); // silly, since usb write will take way longer + read.address = CPTR; + retval = agilent_82357a_read_registers(a_priv, &read, 1, 1); + if (retval) { + dev_err(&usb_dev->dev, "read_registers() returned error\n"); + return retval; + } + *result = read.value; + // clear parallel poll state + writes[0].address = HW_CONTROL; + writes[0].value = a_priv->hw_control_bits | NOT_PARALLEL_POLL; + writes[1].address = AUXCR; + writes[1].value = AUX_RPP; + retval = agilent_82357a_write_registers(a_priv, writes, 2); + if (retval) { + dev_err(&usb_dev->dev, "write_registers() returned error\n"); + return retval; + } + return 0; +} + +static void agilent_82357a_parallel_poll_configure(struct gpib_board *board, u8 config) +{ + // board can only be system controller + return;// 0; +} + +static void agilent_82357a_parallel_poll_response(struct gpib_board *board, int ist) +{ + // board can only be system controller + return;// 0; +} + +static void agilent_82357a_serial_poll_response(struct gpib_board *board, u8 status) +{ + // board can only be system controller + return;// 0; +} + +static u8 agilent_82357a_serial_poll_status(struct gpib_board *board) +{ + // board can only be system controller + return 0; +} + +static void agilent_82357a_return_to_local(struct gpib_board *board) +{ + // board can only be system controller + return;// 0; +} + +static int agilent_82357a_line_status(const struct gpib_board *board) +{ + struct agilent_82357a_priv *a_priv = board->private_data; + struct usb_device *usb_dev; + struct agilent_82357a_register_pairlet bus_status; + int retval; + int status = VALID_ALL; + + if (!a_priv->bus_interface) + return -ENODEV; + usb_dev = interface_to_usbdev(a_priv->bus_interface); + bus_status.address = BSR; + retval = agilent_82357a_read_registers(a_priv, &bus_status, 1, 0); + if (retval) { + if (retval != -EAGAIN) + dev_err(&usb_dev->dev, "read_registers() returned error\n"); + return retval; + } + if (bus_status.value & BSR_REN_BIT) + status |= BUS_REN; + if (bus_status.value & BSR_IFC_BIT) + status |= BUS_IFC; + if (bus_status.value & BSR_SRQ_BIT) + status |= BUS_SRQ; + if (bus_status.value & BSR_EOI_BIT) + status |= BUS_EOI; + if (bus_status.value & BSR_NRFD_BIT) + status |= BUS_NRFD; + if (bus_status.value & BSR_NDAC_BIT) + status |= BUS_NDAC; + if (bus_status.value & BSR_DAV_BIT) + status |= BUS_DAV; + if (bus_status.value & BSR_ATN_BIT) + status |= BUS_ATN; + return status; +} + +static unsigned short nanosec_to_fast_talker_bits(unsigned int *nanosec) +{ + static const int nanosec_per_bit = 21; + static const int max_value = 0x72; + static const int min_value = 0x11; + unsigned short bits; + + bits = (*nanosec + nanosec_per_bit / 2) / nanosec_per_bit; + if (bits < min_value) + bits = min_value; + if (bits > max_value) + bits = max_value; + *nanosec = bits * nanosec_per_bit; + return bits; +} + +static int agilent_82357a_t1_delay(struct gpib_board *board, unsigned int nanosec) +{ + struct agilent_82357a_priv *a_priv = board->private_data; + struct usb_device *usb_dev; + struct agilent_82357a_register_pairlet write; + int retval; + + if (!a_priv->bus_interface) + return -ENODEV; + usb_dev = interface_to_usbdev(a_priv->bus_interface); + write.address = FAST_TALKER_T1; + write.value = nanosec_to_fast_talker_bits(&nanosec); + retval = agilent_82357a_write_registers(a_priv, &write, 1); + if (retval) + dev_err(&usb_dev->dev, "write_registers() returned error\n"); + return nanosec; +} + +static void agilent_82357a_interrupt_complete(struct urb *urb) +{ + struct gpib_board *board = urb->context; + struct agilent_82357a_priv *a_priv = board->private_data; + struct usb_device *usb_dev = interface_to_usbdev(a_priv->bus_interface); + int retval; + u8 *transfer_buffer = urb->transfer_buffer; + unsigned long interrupt_flags; + + switch (urb->status) { + /* success */ + case 0: + break; + /* unlinked, don't resubmit */ + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + return; + default: /* other error, resubmit */ + retval = usb_submit_urb(a_priv->interrupt_urb, GFP_ATOMIC); + if (retval) + dev_err(&usb_dev->dev, "failed to resubmit interrupt urb\n"); + return; + } + + interrupt_flags = transfer_buffer[0]; + if (test_bit(AIF_READ_COMPLETE_BN, &interrupt_flags)) + set_bit(AIF_READ_COMPLETE_BN, &a_priv->interrupt_flags); + if (test_bit(AIF_WRITE_COMPLETE_BN, &interrupt_flags)) + set_bit(AIF_WRITE_COMPLETE_BN, &a_priv->interrupt_flags); + if (test_bit(AIF_SRQ_BN, &interrupt_flags)) + set_bit(SRQI_NUM, &board->status); + + wake_up_interruptible(&board->wait); + + retval = usb_submit_urb(a_priv->interrupt_urb, GFP_ATOMIC); + if (retval) + dev_err(&usb_dev->dev, "failed to resubmit interrupt urb\n"); +} + +static int agilent_82357a_setup_urbs(struct gpib_board *board) +{ + struct agilent_82357a_priv *a_priv = board->private_data; + struct usb_device *usb_dev; + int int_pipe; + int retval; + + retval = mutex_lock_interruptible(&a_priv->interrupt_alloc_lock); + if (retval) + return retval; + if (!a_priv->bus_interface) { + retval = -ENODEV; + goto setup_exit; + } + + a_priv->interrupt_buffer = kmalloc(INTERRUPT_BUF_LEN, GFP_KERNEL); + if (!a_priv->interrupt_buffer) { + retval = -ENOMEM; + goto setup_exit; + } + a_priv->interrupt_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!a_priv->interrupt_urb) { + retval = -ENOMEM; + goto setup_exit; + } + usb_dev = interface_to_usbdev(a_priv->bus_interface); + int_pipe = usb_rcvintpipe(usb_dev, a_priv->interrupt_in_endpoint); + usb_fill_int_urb(a_priv->interrupt_urb, usb_dev, int_pipe, a_priv->interrupt_buffer, + INTERRUPT_BUF_LEN, &agilent_82357a_interrupt_complete, board, 1); + retval = usb_submit_urb(a_priv->interrupt_urb, GFP_KERNEL); + if (retval) { + usb_free_urb(a_priv->interrupt_urb); + a_priv->interrupt_urb = NULL; + dev_err(&usb_dev->dev, "failed to submit first interrupt urb, retval=%i\n", retval); + goto setup_exit; + } + mutex_unlock(&a_priv->interrupt_alloc_lock); + return 0; + +setup_exit: + kfree(a_priv->interrupt_buffer); + mutex_unlock(&a_priv->interrupt_alloc_lock); + return retval; +} + +static void agilent_82357a_cleanup_urbs(struct agilent_82357a_priv *a_priv) +{ + if (a_priv && a_priv->bus_interface) { + if (a_priv->interrupt_urb) + usb_kill_urb(a_priv->interrupt_urb); + if (a_priv->bulk_urb) + usb_kill_urb(a_priv->bulk_urb); + } +}; + +static void agilent_82357a_release_urbs(struct agilent_82357a_priv *a_priv) +{ + if (a_priv) { + usb_free_urb(a_priv->interrupt_urb); + a_priv->interrupt_urb = NULL; + kfree(a_priv->interrupt_buffer); + } +} + +static int agilent_82357a_allocate_private(struct gpib_board *board) +{ + struct agilent_82357a_priv *a_priv; + + board->private_data = kzalloc(sizeof(struct agilent_82357a_priv), GFP_KERNEL); + if (!board->private_data) + return -ENOMEM; + a_priv = board->private_data; + mutex_init(&a_priv->bulk_transfer_lock); + mutex_init(&a_priv->bulk_alloc_lock); + mutex_init(&a_priv->control_alloc_lock); + mutex_init(&a_priv->interrupt_alloc_lock); + return 0; +} + +static void agilent_82357a_free_private(struct gpib_board *board) +{ + kfree(board->private_data); + board->private_data = NULL; +} + +#define INIT_NUM_REG_WRITES 18 +static int agilent_82357a_init(struct gpib_board *board) +{ + struct agilent_82357a_priv *a_priv = board->private_data; + struct usb_device *usb_dev = interface_to_usbdev(a_priv->bus_interface); + struct agilent_82357a_register_pairlet hw_control; + struct agilent_82357a_register_pairlet writes[INIT_NUM_REG_WRITES]; + int retval; + unsigned int nanosec; + + writes[0].address = LED_CONTROL; + writes[0].value = FAIL_LED_ON; + writes[1].address = RESET_TO_POWERUP; + writes[1].value = RESET_SPACEBALL; + retval = agilent_82357a_write_registers(a_priv, writes, 2); + if (retval) { + dev_err(&usb_dev->dev, "write_registers() returned error\n"); + return -EIO; + } + set_current_state(TASK_INTERRUPTIBLE); + if (schedule_timeout(usec_to_jiffies(2000))) + return -ERESTARTSYS; + writes[0].address = AUXCR; + writes[0].value = AUX_NBAF; + writes[1].address = AUXCR; + writes[1].value = AUX_HLDE; + writes[2].address = AUXCR; + writes[2].value = AUX_TON; + writes[3].address = AUXCR; + writes[3].value = AUX_LON; + writes[4].address = AUXCR; + writes[4].value = AUX_RSV2; + writes[5].address = AUXCR; + writes[5].value = AUX_INVAL; + writes[6].address = AUXCR; + writes[6].value = AUX_RPP; + writes[7].address = AUXCR; + writes[7].value = AUX_STDL; + writes[8].address = AUXCR; + writes[8].value = AUX_VSTDL; + writes[9].address = FAST_TALKER_T1; + nanosec = board->t1_nano_sec; + writes[9].value = nanosec_to_fast_talker_bits(&nanosec); + board->t1_nano_sec = nanosec; + writes[10].address = ADR; + writes[10].value = board->pad & ADDRESS_MASK; + writes[11].address = PPR; + writes[11].value = 0; + writes[12].address = SPMR; + writes[12].value = 0; + writes[13].address = PROTOCOL_CONTROL; + writes[13].value = WRITE_COMPLETE_INTERRUPT_EN; + writes[14].address = IMR0; + writes[14].value = HR_BOIE | HR_BIIE; + writes[15].address = IMR1; + writes[15].value = HR_SRQIE; + // turn off reset state + writes[16].address = AUXCR; + writes[16].value = AUX_CHIP_RESET; + writes[17].address = LED_CONTROL; + writes[17].value = FIRMWARE_LED_CONTROL; + retval = agilent_82357a_write_registers(a_priv, writes, INIT_NUM_REG_WRITES); + if (retval) { + dev_err(&usb_dev->dev, "write_registers() returned error\n"); + return -EIO; + } + hw_control.address = HW_CONTROL; + retval = agilent_82357a_read_registers(a_priv, &hw_control, 1, 1); + if (retval) { + dev_err(&usb_dev->dev, "read_registers() returned error\n"); + return -EIO; + } + a_priv->hw_control_bits = (hw_control.value & ~0x7) | NOT_TI_RESET | NOT_PARALLEL_POLL; + + return 0; +} + +static inline int agilent_82357a_device_match(struct usb_interface *interface, + const struct gpib_board_config *config) +{ + struct usb_device * const usbdev = interface_to_usbdev(interface); + + if (gpib_match_device_path(&interface->dev, config->device_path) == 0) + return 0; + if (config->serial_number && + strcmp(usbdev->serial, config->serial_number) != 0) + return 0; + + return 1; +} + +static int agilent_82357a_attach(struct gpib_board *board, const struct gpib_board_config *config) +{ + int retval; + int i; + unsigned int product_id; + struct agilent_82357a_priv *a_priv; + struct usb_device *usb_dev; + + if (mutex_lock_interruptible(&agilent_82357a_hotplug_lock)) + return -ERESTARTSYS; + + retval = agilent_82357a_allocate_private(board); + if (retval < 0) { + mutex_unlock(&agilent_82357a_hotplug_lock); + return retval; + } + a_priv = board->private_data; + for (i = 0; i < MAX_NUM_82357A_INTERFACES; ++i) { + if (agilent_82357a_driver_interfaces[i] && + !usb_get_intfdata(agilent_82357a_driver_interfaces[i]) && + agilent_82357a_device_match(agilent_82357a_driver_interfaces[i], config)) { + a_priv->bus_interface = agilent_82357a_driver_interfaces[i]; + usb_set_intfdata(agilent_82357a_driver_interfaces[i], board); + usb_dev = interface_to_usbdev(a_priv->bus_interface); + break; + } + } + if (i == MAX_NUM_82357A_INTERFACES) { + dev_err(board->gpib_dev, + "No supported adapters found, have you loaded its firmware?\n"); + retval = -ENODEV; + goto attach_fail; + } + product_id = le16_to_cpu(interface_to_usbdev(a_priv->bus_interface)->descriptor.idProduct); + switch (product_id) { + case USB_DEVICE_ID_AGILENT_82357A: + a_priv->bulk_out_endpoint = AGILENT_82357A_BULK_OUT_ENDPOINT; + a_priv->interrupt_in_endpoint = AGILENT_82357A_INTERRUPT_IN_ENDPOINT; + break; + case USB_DEVICE_ID_AGILENT_82357B: + a_priv->bulk_out_endpoint = AGILENT_82357B_BULK_OUT_ENDPOINT; + a_priv->interrupt_in_endpoint = AGILENT_82357B_INTERRUPT_IN_ENDPOINT; + break; + default: + dev_err(&usb_dev->dev, "bug, unhandled product_id in switch?\n"); + retval = -EIO; + goto attach_fail; + } + + retval = agilent_82357a_setup_urbs(board); + if (retval < 0) + goto attach_fail; + + timer_setup(&a_priv->bulk_timer, agilent_82357a_timeout_handler, 0); + + board->t1_nano_sec = 800; + + retval = agilent_82357a_init(board); + + if (retval < 0) { + agilent_82357a_cleanup_urbs(a_priv); + agilent_82357a_release_urbs(a_priv); + goto attach_fail; + } + + dev_info(&usb_dev->dev, "bus %d dev num %d attached to gpib%d, interface %i\n", + usb_dev->bus->busnum, usb_dev->devnum, board->minor, i); + mutex_unlock(&agilent_82357a_hotplug_lock); + return retval; + +attach_fail: + agilent_82357a_free_private(board); + mutex_unlock(&agilent_82357a_hotplug_lock); + return retval; +} + +static int agilent_82357a_go_idle(struct gpib_board *board) +{ + struct agilent_82357a_priv *a_priv = board->private_data; + struct usb_device *usb_dev = interface_to_usbdev(a_priv->bus_interface); + struct agilent_82357a_register_pairlet writes[0x20]; + int retval; + + // turn on tms9914 reset state + writes[0].address = AUXCR; + writes[0].value = AUX_CS | AUX_CHIP_RESET; + a_priv->hw_control_bits &= ~NOT_TI_RESET; + writes[1].address = HW_CONTROL; + writes[1].value = a_priv->hw_control_bits; + writes[2].address = PROTOCOL_CONTROL; + writes[2].value = 0; + writes[3].address = IMR0; + writes[3].value = 0; + writes[4].address = IMR1; + writes[4].value = 0; + writes[5].address = LED_CONTROL; + writes[5].value = 0; + retval = agilent_82357a_write_registers(a_priv, writes, 6); + if (retval) { + dev_err(&usb_dev->dev, "write_registers() returned error\n"); + return -EIO; + } + return 0; +} + +static void agilent_82357a_detach(struct gpib_board *board) +{ + struct agilent_82357a_priv *a_priv; + + mutex_lock(&agilent_82357a_hotplug_lock); + + a_priv = board->private_data; + if (a_priv) { + if (a_priv->bus_interface) { + agilent_82357a_go_idle(board); + usb_set_intfdata(a_priv->bus_interface, NULL); + } + mutex_lock(&a_priv->control_alloc_lock); + mutex_lock(&a_priv->bulk_alloc_lock); + mutex_lock(&a_priv->interrupt_alloc_lock); + agilent_82357a_cleanup_urbs(a_priv); + agilent_82357a_release_urbs(a_priv); + agilent_82357a_free_private(board); + } + mutex_unlock(&agilent_82357a_hotplug_lock); +} + +static struct gpib_interface agilent_82357a_gpib_interface = { + .name = "agilent_82357a", + .attach = agilent_82357a_attach, + .detach = agilent_82357a_detach, + .read = agilent_82357a_read, + .write = agilent_82357a_write, + .command = agilent_82357a_command, + .take_control = agilent_82357a_take_control, + .go_to_standby = agilent_82357a_go_to_standby, + .request_system_control = agilent_82357a_request_system_control, + .interface_clear = agilent_82357a_interface_clear, + .remote_enable = agilent_82357a_remote_enable, + .enable_eos = agilent_82357a_enable_eos, + .disable_eos = agilent_82357a_disable_eos, + .parallel_poll = agilent_82357a_parallel_poll, + .parallel_poll_configure = agilent_82357a_parallel_poll_configure, + .parallel_poll_response = agilent_82357a_parallel_poll_response, + .local_parallel_poll_mode = NULL, // XXX + .line_status = agilent_82357a_line_status, + .update_status = agilent_82357a_update_status, + .primary_address = agilent_82357a_primary_address, + .secondary_address = agilent_82357a_secondary_address, + .serial_poll_response = agilent_82357a_serial_poll_response, + .serial_poll_status = agilent_82357a_serial_poll_status, + .t1_delay = agilent_82357a_t1_delay, + .return_to_local = agilent_82357a_return_to_local, + .no_7_bit_eos = 1, + .skip_check_for_command_acceptors = 1 +}; + +// Table with the USB-devices: just now only testing IDs +static struct usb_device_id agilent_82357a_driver_device_table[] = { + {USB_DEVICE(USB_VENDOR_ID_AGILENT, USB_DEVICE_ID_AGILENT_82357A)}, + {USB_DEVICE(USB_VENDOR_ID_AGILENT, USB_DEVICE_ID_AGILENT_82357B)}, + {} /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, agilent_82357a_driver_device_table); + +static int agilent_82357a_driver_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + int i; + char *path; + static const int path_length = 1024; + struct usb_device *usb_dev; + + if (mutex_lock_interruptible(&agilent_82357a_hotplug_lock)) + return -ERESTARTSYS; + usb_dev = usb_get_dev(interface_to_usbdev(interface)); + for (i = 0; i < MAX_NUM_82357A_INTERFACES; ++i) { + if (!agilent_82357a_driver_interfaces[i]) { + agilent_82357a_driver_interfaces[i] = interface; + usb_set_intfdata(interface, NULL); + dev_dbg(&usb_dev->dev, "set bus interface %i to address 0x%p\n", + i, interface); + break; + } + } + if (i == MAX_NUM_82357A_INTERFACES) { + usb_put_dev(usb_dev); + mutex_unlock(&agilent_82357a_hotplug_lock); + dev_err(&usb_dev->dev, "out of space in agilent_82357a_driver_interfaces[]\n"); + return -1; + } + path = kmalloc(path_length, GFP_KERNEL); + if (!path) { + usb_put_dev(usb_dev); + mutex_unlock(&agilent_82357a_hotplug_lock); + return -ENOMEM; + } + usb_make_path(usb_dev, path, path_length); + dev_info(&usb_dev->dev, "probe succeeded for path: %s\n", path); + kfree(path); + mutex_unlock(&agilent_82357a_hotplug_lock); + return 0; +} + +static void agilent_82357a_driver_disconnect(struct usb_interface *interface) +{ + int i; + struct usb_device *usb_dev = interface_to_usbdev(interface); + + mutex_lock(&agilent_82357a_hotplug_lock); + + for (i = 0; i < MAX_NUM_82357A_INTERFACES; ++i) { + if (agilent_82357a_driver_interfaces[i] == interface) { + struct gpib_board *board = usb_get_intfdata(interface); + + if (board) { + struct agilent_82357a_priv *a_priv = board->private_data; + + if (a_priv) { + mutex_lock(&a_priv->control_alloc_lock); + mutex_lock(&a_priv->bulk_alloc_lock); + mutex_lock(&a_priv->interrupt_alloc_lock); + agilent_82357a_cleanup_urbs(a_priv); + a_priv->bus_interface = NULL; + mutex_unlock(&a_priv->interrupt_alloc_lock); + mutex_unlock(&a_priv->bulk_alloc_lock); + mutex_unlock(&a_priv->control_alloc_lock); + } + } + agilent_82357a_driver_interfaces[i] = NULL; + break; + } + } + if (i == MAX_NUM_82357A_INTERFACES) + dev_err(&usb_dev->dev, "unable to find interface - bug?\n"); + usb_put_dev(usb_dev); + + mutex_unlock(&agilent_82357a_hotplug_lock); +} + +static int agilent_82357a_driver_suspend(struct usb_interface *interface, pm_message_t message) +{ + int i, retval; + struct usb_device *usb_dev = interface_to_usbdev(interface); + + mutex_lock(&agilent_82357a_hotplug_lock); + + for (i = 0; i < MAX_NUM_82357A_INTERFACES; ++i) { + if (agilent_82357a_driver_interfaces[i] == interface) { + struct gpib_board *board = usb_get_intfdata(interface); + + if (board) { + struct agilent_82357a_priv *a_priv = board->private_data; + + if (a_priv) { + agilent_82357a_abort(a_priv, 0); + agilent_82357a_abort(a_priv, 0); + retval = agilent_82357a_go_idle(board); + if (retval) { + dev_err(&usb_dev->dev, "failed to go idle, retval=%i\n", + retval); + mutex_unlock(&agilent_82357a_hotplug_lock); + return retval; + } + mutex_lock(&a_priv->interrupt_alloc_lock); + agilent_82357a_cleanup_urbs(a_priv); + mutex_unlock(&a_priv->interrupt_alloc_lock); + dev_dbg(&usb_dev->dev, + "bus %d dev num %d gpib %d, interface %i suspended\n", + usb_dev->bus->busnum, usb_dev->devnum, + board->minor, i); + } + } + break; + } + } + + mutex_unlock(&agilent_82357a_hotplug_lock); + + return 0; +} + +static int agilent_82357a_driver_resume(struct usb_interface *interface) +{ + struct usb_device *usb_dev = interface_to_usbdev(interface); + struct gpib_board *board; + int i, retval = 0; + + mutex_lock(&agilent_82357a_hotplug_lock); + + for (i = 0; i < MAX_NUM_82357A_INTERFACES; ++i) { + if (agilent_82357a_driver_interfaces[i] == interface) { + board = usb_get_intfdata(interface); + if (board) + break; + } + } + if (i == MAX_NUM_82357A_INTERFACES) { + retval = -ENOENT; + goto resume_exit; + } + + struct agilent_82357a_priv *a_priv = board->private_data; + + if (a_priv) { + if (a_priv->interrupt_urb) { + mutex_lock(&a_priv->interrupt_alloc_lock); + retval = usb_submit_urb(a_priv->interrupt_urb, GFP_KERNEL); + if (retval) { + dev_err(&usb_dev->dev, "failed to resubmit interrupt urb in resume, retval=%i\n", + retval); + mutex_unlock(&a_priv->interrupt_alloc_lock); + mutex_unlock(&agilent_82357a_hotplug_lock); + return retval; + } + mutex_unlock(&a_priv->interrupt_alloc_lock); + } + retval = agilent_82357a_init(board); + if (retval < 0) { + mutex_unlock(&agilent_82357a_hotplug_lock); + return retval; + } + // set/unset system controller + retval = agilent_82357a_request_system_control(board, board->master); + // toggle ifc if master + if (board->master) { + agilent_82357a_interface_clear(board, 1); + usleep_range(200, 250); + agilent_82357a_interface_clear(board, 0); + } + // assert/unassert REN + agilent_82357a_remote_enable(board, a_priv->ren_state); + + dev_dbg(&usb_dev->dev, + "bus %d dev num %d gpib%d, interface %i resumed\n", + usb_dev->bus->busnum, usb_dev->devnum, board->minor, i); + } + +resume_exit: + mutex_unlock(&agilent_82357a_hotplug_lock); + + return retval; +} + +static struct usb_driver agilent_82357a_bus_driver = { + .name = DRV_NAME, + .probe = agilent_82357a_driver_probe, + .disconnect = agilent_82357a_driver_disconnect, + .suspend = agilent_82357a_driver_suspend, + .resume = agilent_82357a_driver_resume, + .id_table = agilent_82357a_driver_device_table, +}; + +static int __init agilent_82357a_init_module(void) +{ + int i; + int ret; + + for (i = 0; i < MAX_NUM_82357A_INTERFACES; ++i) + agilent_82357a_driver_interfaces[i] = NULL; + + ret = usb_register(&agilent_82357a_bus_driver); + if (ret) { + pr_err("usb_register failed: error = %d\n", ret); + return ret; + } + + ret = gpib_register_driver(&agilent_82357a_gpib_interface, THIS_MODULE); + if (ret) { + pr_err("gpib_register_driver failed: error = %d\n", ret); + usb_deregister(&agilent_82357a_bus_driver); + return ret; + } + + return 0; +} + +static void __exit agilent_82357a_exit_module(void) +{ + gpib_unregister_driver(&agilent_82357a_gpib_interface); + usb_deregister(&agilent_82357a_bus_driver); +} + +module_init(agilent_82357a_init_module); +module_exit(agilent_82357a_exit_module); diff --git a/drivers/gpib/agilent_82357a/agilent_82357a.h b/drivers/gpib/agilent_82357a/agilent_82357a.h new file mode 100644 index 000000000000..33ac558e5552 --- /dev/null +++ b/drivers/gpib/agilent_82357a/agilent_82357a.h @@ -0,0 +1,182 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/*************************************************************************** + * copyright : (C) 2004 by Frank Mori Hess * + ***************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include "gpibP.h" +#include "tms9914.h" + +enum usb_vendor_ids { + USB_VENDOR_ID_AGILENT = 0x0957 +}; + +enum usb_device_ids { + USB_DEVICE_ID_AGILENT_82357A = 0x0107, + USB_DEVICE_ID_AGILENT_82357A_PREINIT = 0x0007, // device id before firmware is loaded + USB_DEVICE_ID_AGILENT_82357B = 0x0718, // device id before firmware is loaded + USB_DEVICE_ID_AGILENT_82357B_PREINIT = 0x0518, // device id before firmware is loaded +}; + +enum endpoint_addresses { + AGILENT_82357_CONTROL_ENDPOINT = 0x0, + AGILENT_82357_BULK_IN_ENDPOINT = 0x2, + AGILENT_82357A_BULK_OUT_ENDPOINT = 0x4, + AGILENT_82357A_INTERRUPT_IN_ENDPOINT = 0x6, + AGILENT_82357B_BULK_OUT_ENDPOINT = 0x6, + AGILENT_82357B_INTERRUPT_IN_ENDPOINT = 0x8, +}; + +enum bulk_commands { + DATA_PIPE_CMD_WRITE = 0x1, + DATA_PIPE_CMD_READ = 0x3, + DATA_PIPE_CMD_WR_REGS = 0x4, + DATA_PIPE_CMD_RD_REGS = 0x5 +}; + +enum agilent_82357a_read_flags { + ARF_END_ON_EOI = 0x1, + ARF_NO_ADDRESS = 0x2, + ARF_END_ON_EOS_CHAR = 0x4, + ARF_SPOLL = 0x8 +}; + +enum agilent_82357a_trailing_read_flags { + ATRF_EOI = 0x1, + ATRF_ATN = 0x2, + ATRF_IFC = 0x4, + ATRF_EOS = 0x8, + ATRF_ABORT = 0x10, + ATRF_COUNT = 0x20, + ATRF_DEAD_BUS = 0x40, + ATRF_UNADDRESSED = 0x80 +}; + +enum agilent_82357a_write_flags { + AWF_SEND_EOI = 0x1, + AWF_NO_FAST_TALKER_FIRST_BYTE = 0x2, + AWF_NO_FAST_TALKER = 0x4, + AWF_NO_ADDRESS = 0x8, + AWF_ATN = 0x10, + AWF_SEPARATE_HEADER = 0x80 +}; + +enum agilent_82357a_interrupt_flag_bit_numbers { + AIF_SRQ_BN = 0, + AIF_WRITE_COMPLETE_BN = 1, + AIF_READ_COMPLETE_BN = 2, +}; + +enum agilent_82357_error_codes { + UGP_SUCCESS = 0, + UGP_ERR_INVALID_CMD = 1, + UGP_ERR_INVALID_PARAM = 2, + UGP_ERR_INVALID_REG = 3, + UGP_ERR_GPIB_READ = 4, + UGP_ERR_GPIB_WRITE = 5, + UGP_ERR_FLUSHING = 6, + UGP_ERR_FLUSHING_ALREADY = 7, + UGP_ERR_UNSUPPORTED = 8, + UGP_ERR_OTHER = 9 +}; + +enum agilent_82357_control_values { + XFER_ABORT = 0xa0, + XFER_STATUS = 0xb0, +}; + +enum xfer_status_bits { + XS_COMPLETED = 0x1, + XS_READ = 0x2, +}; + +enum xfer_status_completion_bits { + XSC_EOI = 0x1, + XSC_ATN = 0x2, + XSC_IFC = 0x4, + XSC_EOS = 0x8, + XSC_ABORT = 0x10, + XSC_COUNT = 0x20, + XSC_DEAD_BUS = 0x40, + XSC_BUS_NOT_ADDRESSED = 0x80 +}; + +enum xfer_abort_type { + XA_FLUSH = 0x1 +}; + +#define STATUS_DATA_LEN 8 +#define INTERRUPT_BUF_LEN 8 + +struct agilent_82357a_urb_ctx { + struct completion complete; + unsigned timed_out : 1; +}; + +// struct which defines local data for each 82357 device +struct agilent_82357a_priv { + struct usb_interface *bus_interface; + unsigned short eos_char; + unsigned short eos_mode; + unsigned short hw_control_bits; + unsigned long interrupt_flags; + struct urb *bulk_urb; + struct urb *interrupt_urb; + u8 *interrupt_buffer; + struct mutex bulk_transfer_lock; // bulk transfer lock + struct mutex bulk_alloc_lock; // bulk transfer allocation lock + struct mutex interrupt_alloc_lock; // interrupt allocation lock + struct mutex control_alloc_lock; // control message allocation lock + struct timer_list bulk_timer; + struct agilent_82357a_urb_ctx context; + unsigned int bulk_out_endpoint; + unsigned int interrupt_in_endpoint; + unsigned is_cic : 1; + unsigned ren_state : 1; +}; + +struct agilent_82357a_register_pairlet { + short address; + unsigned short value; +}; + +enum firmware_registers { + HW_CONTROL = 0xa, + LED_CONTROL = 0xb, + RESET_TO_POWERUP = 0xc, + PROTOCOL_CONTROL = 0xd, + FAST_TALKER_T1 = 0xe +}; + +enum hardware_control_bits { + NOT_TI_RESET = 0x1, + SYSTEM_CONTROLLER = 0x2, + NOT_PARALLEL_POLL = 0x4, + OSCILLATOR_5V_ON = 0x8, + OUTPUT_5V_ON = 0x20, + CPLD_3V_ON = 0x80, +}; + +enum led_control_bits { + FIRMWARE_LED_CONTROL = 0x1, + FAIL_LED_ON = 0x20, + READY_LED_ON = 0x40, + ACCESS_LED_ON = 0x80 +}; + +enum reset_to_powerup_bits { + RESET_SPACEBALL = 0x1, // wait 2 millisec after sending +}; + +enum protocol_control_bits { + WRITE_COMPLETE_INTERRUPT_EN = 0x1, +}; + +static const int agilent_82357a_control_request = 0x4; + diff --git a/drivers/gpib/cb7210/Makefile b/drivers/gpib/cb7210/Makefile new file mode 100644 index 000000000000..d239ae80b415 --- /dev/null +++ b/drivers/gpib/cb7210/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_GPIB_CB7210) += cb7210.o + + diff --git a/drivers/gpib/cb7210/cb7210.c b/drivers/gpib/cb7210/cb7210.c new file mode 100644 index 000000000000..3e2397898a9b --- /dev/null +++ b/drivers/gpib/cb7210/cb7210.c @@ -0,0 +1,1598 @@ +// SPDX-License-Identifier: GPL-2.0 + +/*************************************************************************** + * Measurement Computing boards using cb7210.2 and cbi488.2 chips + * copyright : (C) 2001, 2002 by Frank Mori Hess + ***************************************************************************/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#define dev_fmt pr_fmt +#define DRV_NAME KBUILD_MODNAME + +#include "cb7210.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "gpib_pci_ids.h" +#include "quancom_pci.h" + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("GPIB driver Measurement Computing boards using cb7210.2 and cbi488.2"); + +static int cb7210_read(struct gpib_board *board, u8 *buffer, size_t length, + int *end, size_t *bytes_read); + + static inline int have_fifo_word(const struct cb7210_priv *cb_priv) +{ + if (((cb7210_read_byte(cb_priv, HS_STATUS)) & + (HS_RX_MSB_NOT_EMPTY | HS_RX_LSB_NOT_EMPTY)) == + (HS_RX_MSB_NOT_EMPTY | HS_RX_LSB_NOT_EMPTY)) + return 1; + else + return 0; +} + +static inline void input_fifo_enable(struct gpib_board *board, int enable) +{ + struct cb7210_priv *cb_priv = board->private_data; + struct nec7210_priv *nec_priv = &cb_priv->nec7210_priv; + unsigned long flags; + + spin_lock_irqsave(&board->spinlock, flags); + + if (enable) { + cb_priv->in_fifo_half_full = 0; + nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAI, 0); + + cb7210_write_byte(cb_priv, HS_RX_ENABLE | HS_TX_ENABLE | HS_CLR_SRQ_INT | + HS_CLR_EOI_EMPTY_INT | HS_CLR_HF_INT | cb_priv->hs_mode_bits, + HS_MODE); + + cb_priv->hs_mode_bits &= ~HS_ENABLE_MASK; + cb7210_write_byte(cb_priv, cb_priv->hs_mode_bits, HS_MODE); + + cb7210_write_byte(cb_priv, irq_bits(cb_priv->irq), HS_INT_LEVEL); + + cb_priv->hs_mode_bits |= HS_RX_ENABLE; + cb7210_write_byte(cb_priv, cb_priv->hs_mode_bits, HS_MODE); + } else { + nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAI, 0); + + cb_priv->hs_mode_bits &= ~HS_ENABLE_MASK; + cb7210_write_byte(cb_priv, cb_priv->hs_mode_bits, nec7210_iobase(cb_priv) + + HS_MODE); + + clear_bit(READ_READY_BN, &nec_priv->state); + } + + spin_unlock_irqrestore(&board->spinlock, flags); +} + +static int fifo_read(struct gpib_board *board, struct cb7210_priv *cb_priv, u8 *buffer, + size_t length, int *end, size_t *bytes_read) +{ + ssize_t retval = 0; + struct nec7210_priv *nec_priv = &cb_priv->nec7210_priv; + int hs_status; + u16 word; + unsigned long flags; + + *bytes_read = 0; + if (cb_priv->fifo_iobase == 0) { + dev_err(board->gpib_dev, "fifo iobase is zero!\n"); + return -EIO; + } + *end = 0; + if (length <= cb7210_fifo_size) { + dev_err(board->gpib_dev, " bug! fifo read length < fifo size\n"); + return -EINVAL; + } + + input_fifo_enable(board, 1); + + while (*bytes_read + cb7210_fifo_size < length) { + nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAI, HR_DMAI); + + if (wait_event_interruptible(board->wait, + (cb_priv->in_fifo_half_full && + have_fifo_word(cb_priv)) || + test_bit(RECEIVED_END_BN, &nec_priv->state) || + test_bit(DEV_CLEAR_BN, &nec_priv->state) || + test_bit(TIMO_NUM, &board->status))) { + retval = -ERESTARTSYS; + nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAI, 0); + break; + } + + spin_lock_irqsave(&board->spinlock, flags); + + nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAI, 0); + + while (have_fifo_word(cb_priv)) { + word = inw(cb_priv->fifo_iobase + DIR); + buffer[(*bytes_read)++] = word & 0xff; + buffer[(*bytes_read)++] = (word >> 8) & 0xff; + } + + cb_priv->in_fifo_half_full = 0; + + hs_status = cb7210_read_byte(cb_priv, HS_STATUS); + + spin_unlock_irqrestore(&board->spinlock, flags); + + if (test_and_clear_bit(RECEIVED_END_BN, &nec_priv->state)) { + *end = 1; + break; + } + if (hs_status & HS_FIFO_FULL) + break; + if (test_bit(TIMO_NUM, &board->status)) { + retval = -ETIMEDOUT; + break; + } + if (test_bit(DEV_CLEAR_BN, &nec_priv->state)) { + retval = -EINTR; + break; + } + } + hs_status = cb7210_read_byte(cb_priv, HS_STATUS); + if (hs_status & HS_RX_LSB_NOT_EMPTY) { + word = inw(cb_priv->fifo_iobase + DIR); + buffer[(*bytes_read)++] = word & 0xff; + } + + input_fifo_enable(board, 0); + + if (wait_event_interruptible(board->wait, + test_bit(READ_READY_BN, &nec_priv->state) || + test_bit(RECEIVED_END_BN, &nec_priv->state) || + test_bit(DEV_CLEAR_BN, &nec_priv->state) || + test_bit(TIMO_NUM, &board->status))) { + retval = -ERESTARTSYS; + } + if (test_bit(TIMO_NUM, &board->status)) + retval = -ETIMEDOUT; + if (test_bit(DEV_CLEAR_BN, &nec_priv->state)) + retval = -EINTR; + if (test_bit(READ_READY_BN, &nec_priv->state)) { + nec7210_set_handshake_mode(board, nec_priv, HR_HLDA); + buffer[(*bytes_read)++] = nec7210_read_data_in(board, nec_priv, end); + } + + return retval; +} + +static int cb7210_accel_read(struct gpib_board *board, u8 *buffer, + size_t length, int *end, size_t *bytes_read) +{ + ssize_t retval; + struct cb7210_priv *cb_priv = board->private_data; + struct nec7210_priv *nec_priv = &cb_priv->nec7210_priv; + size_t num_bytes; + + *bytes_read = 0; + // deal with limitations of fifo + if (length < cb7210_fifo_size + 3 || (nec_priv->auxa_bits & HR_REOS)) + return cb7210_read(board, buffer, length, end, bytes_read); + *end = 0; + + nec7210_release_rfd_holdoff(board, nec_priv); + + if (wait_event_interruptible(board->wait, + test_bit(READ_READY_BN, &nec_priv->state) || + test_bit(DEV_CLEAR_BN, &nec_priv->state) || + test_bit(TIMO_NUM, &board->status))) { + return -ERESTARTSYS; + } + if (test_bit(TIMO_NUM, &board->status)) + return -ETIMEDOUT; + if (test_bit(DEV_CLEAR_BN, &nec_priv->state)) + return -EINTR; + + nec7210_set_handshake_mode(board, nec_priv, HR_HLDE); + buffer[(*bytes_read)++] = nec7210_read_data_in(board, nec_priv, end); + if (*end) + return 0; + + nec7210_release_rfd_holdoff(board, nec_priv); + + retval = fifo_read(board, cb_priv, &buffer[*bytes_read], length - *bytes_read - 1, + end, &num_bytes); + *bytes_read += num_bytes; + if (retval < 0) + return retval; + if (*end) + return 0; + + retval = cb7210_read(board, &buffer[*bytes_read], 1, end, &num_bytes); + *bytes_read += num_bytes; + if (retval < 0) + return retval; + + return 0; +} + +static int output_fifo_empty(const struct cb7210_priv *cb_priv) +{ + if ((cb7210_read_byte(cb_priv, HS_STATUS) & (HS_TX_MSB_NOT_EMPTY | HS_TX_LSB_NOT_EMPTY)) + == 0) + return 1; + else + return 0; +} + +static inline void output_fifo_enable(struct gpib_board *board, int enable) +{ + struct cb7210_priv *cb_priv = board->private_data; + struct nec7210_priv *nec_priv = &cb_priv->nec7210_priv; + unsigned long flags; + + spin_lock_irqsave(&board->spinlock, flags); + + if (enable) { + nec7210_set_reg_bits(nec_priv, IMR1, HR_DOIE, 0); + nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAO, HR_DMAO); + + cb7210_write_byte(cb_priv, HS_RX_ENABLE | HS_TX_ENABLE | HS_CLR_SRQ_INT | + HS_CLR_EOI_EMPTY_INT | HS_CLR_HF_INT | cb_priv->hs_mode_bits, + HS_MODE); + + cb_priv->hs_mode_bits &= ~HS_ENABLE_MASK; + cb_priv->hs_mode_bits |= HS_TX_ENABLE; + cb7210_write_byte(cb_priv, cb_priv->hs_mode_bits, HS_MODE); + + cb7210_write_byte(cb_priv, irq_bits(cb_priv->irq), HS_INT_LEVEL); + + clear_bit(WRITE_READY_BN, &nec_priv->state); + + } else { + cb_priv->hs_mode_bits &= ~HS_ENABLE_MASK; + cb7210_write_byte(cb_priv, cb_priv->hs_mode_bits, HS_MODE); + + nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAO, 0); + nec7210_set_reg_bits(nec_priv, IMR1, HR_DOIE, HR_DOIE); + } + + spin_unlock_irqrestore(&board->spinlock, flags); +} + +static int fifo_write(struct gpib_board *board, u8 *buffer, size_t length, + size_t *bytes_written) +{ + size_t count = 0; + ssize_t retval = 0; + struct cb7210_priv *cb_priv = board->private_data; + struct nec7210_priv *nec_priv = &cb_priv->nec7210_priv; + unsigned int num_bytes, i; + unsigned long flags; + + *bytes_written = 0; + if (cb_priv->fifo_iobase == 0) { + dev_err(board->gpib_dev, "fifo iobase is zero!\n"); + return -EINVAL; + } + if (length == 0) + return 0; + + clear_bit(DEV_CLEAR_BN, &nec_priv->state); + clear_bit(BUS_ERROR_BN, &nec_priv->state); + + output_fifo_enable(board, 1); + + while (count < length) { + // wait until byte is ready to be sent + if (wait_event_interruptible(board->wait, + cb_priv->out_fifo_half_empty || + output_fifo_empty(cb_priv) || + test_bit(DEV_CLEAR_BN, &nec_priv->state) || + test_bit(BUS_ERROR_BN, &nec_priv->state) || + test_bit(TIMO_NUM, &board->status))) { + retval = -ERESTARTSYS; + break; + } + if (test_bit(TIMO_NUM, &board->status) || + test_bit(DEV_CLEAR_BN, &nec_priv->state) || + test_bit(BUS_ERROR_BN, &nec_priv->state)) + break; + + if (output_fifo_empty(cb_priv)) + num_bytes = cb7210_fifo_size - cb7210_fifo_width; + else + num_bytes = cb7210_fifo_size / 2; + if (num_bytes + count > length) + num_bytes = length - count; + if (num_bytes % cb7210_fifo_width) { + dev_err(board->gpib_dev, " bug! fifo write with odd number of bytes\n"); + retval = -EINVAL; + break; + } + + spin_lock_irqsave(&board->spinlock, flags); + for (i = 0; i < num_bytes / cb7210_fifo_width; i++) { + u16 word; + + word = buffer[count++] & 0xff; + word |= (buffer[count++] << 8) & 0xff00; + outw(word, cb_priv->fifo_iobase + CDOR); + } + cb_priv->out_fifo_half_empty = 0; + cb7210_write_byte(cb_priv, cb_priv->hs_mode_bits | + HS_CLR_EOI_EMPTY_INT | HS_CLR_HF_INT, HS_MODE); + cb7210_write_byte(cb_priv, cb_priv->hs_mode_bits, HS_MODE); + spin_unlock_irqrestore(&board->spinlock, flags); + } + // wait last byte has been sent + if (wait_event_interruptible(board->wait, + output_fifo_empty(cb_priv) || + test_bit(DEV_CLEAR_BN, &nec_priv->state) || + test_bit(BUS_ERROR_BN, &nec_priv->state) || + test_bit(TIMO_NUM, &board->status))) { + retval = -ERESTARTSYS; + } + if (test_bit(TIMO_NUM, &board->status)) + retval = -ETIMEDOUT; + if (test_bit(BUS_ERROR_BN, &nec_priv->state)) + retval = -EIO; + if (test_bit(DEV_CLEAR_BN, &nec_priv->state)) + retval = -EINTR; + + output_fifo_enable(board, 0); + + *bytes_written = count; + return retval; +} + +static int cb7210_accel_write(struct gpib_board *board, u8 *buffer, + size_t length, int send_eoi, size_t *bytes_written) +{ + struct cb7210_priv *cb_priv = board->private_data; + struct nec7210_priv *nec_priv = &cb_priv->nec7210_priv; + unsigned long fast_chunk_size, leftover; + int retval; + size_t num_bytes; + + *bytes_written = 0; + if (length > cb7210_fifo_width) + fast_chunk_size = length - 1; + else + fast_chunk_size = 0; + fast_chunk_size -= fast_chunk_size % cb7210_fifo_width; + leftover = length - fast_chunk_size; + + retval = fifo_write(board, buffer, fast_chunk_size, &num_bytes); + *bytes_written += num_bytes; + if (retval < 0) + return retval; + + retval = nec7210_write(board, nec_priv, buffer + fast_chunk_size, leftover, + send_eoi, &num_bytes); + *bytes_written += num_bytes; + return retval; +} + +static int cb7210_line_status(const struct gpib_board *board) +{ + int status = VALID_ALL; + int bsr_bits; + struct cb7210_priv *cb_priv; + + cb_priv = board->private_data; + + bsr_bits = cb7210_paged_read_byte(cb_priv, BUS_STATUS, BUS_STATUS_PAGE); + + if ((bsr_bits & BSR_REN_BIT) == 0) + status |= BUS_REN; + if ((bsr_bits & BSR_IFC_BIT) == 0) + status |= BUS_IFC; + if ((bsr_bits & BSR_SRQ_BIT) == 0) + status |= BUS_SRQ; + if ((bsr_bits & BSR_EOI_BIT) == 0) + status |= BUS_EOI; + if ((bsr_bits & BSR_NRFD_BIT) == 0) + status |= BUS_NRFD; + if ((bsr_bits & BSR_NDAC_BIT) == 0) + status |= BUS_NDAC; + if ((bsr_bits & BSR_DAV_BIT) == 0) + status |= BUS_DAV; + if ((bsr_bits & BSR_ATN_BIT) == 0) + status |= BUS_ATN; + + return status; +} + +static int cb7210_t1_delay(struct gpib_board *board, unsigned int nano_sec) +{ + struct cb7210_priv *cb_priv = board->private_data; + struct nec7210_priv *nec_priv = &cb_priv->nec7210_priv; + unsigned int retval; + + retval = nec7210_t1_delay(board, nec_priv, nano_sec); + + if (nano_sec <= 350) { + write_byte(nec_priv, AUX_HI_SPEED, AUXMR); + retval = 350; + } else { + write_byte(nec_priv, AUX_LO_SPEED, AUXMR); + } + return retval; +} + +static irqreturn_t cb7210_locked_internal_interrupt(struct gpib_board *board); + +/* + * GPIB interrupt service routines + */ + +static irqreturn_t cb_pci_interrupt(int irq, void *arg) +{ + int bits; + struct gpib_board *board = arg; + struct cb7210_priv *priv = board->private_data; + + // first task check if this is really our interrupt in a shared irq environment + switch (priv->pci_chip) { + case PCI_CHIP_AMCC_S5933: + if ((inl(priv->amcc_iobase + INTCSR_REG) & + (INBOX_INTR_CS_BIT | INTR_ASSERTED_BIT)) == 0) + return IRQ_NONE; + + // read incoming mailbox to clear mailbox full flag + inl(priv->amcc_iobase + INCOMING_MAILBOX_REG(3)); + // clear amccs5933 interrupt + bits = INBOX_FULL_INTR_BIT | INBOX_BYTE_BITS(3) | + INBOX_SELECT_BITS(3) | INBOX_INTR_CS_BIT; + outl(bits, priv->amcc_iobase + INTCSR_REG); + break; + case PCI_CHIP_QUANCOM: + if ((inb(nec7210_iobase(priv) + QUANCOM_IRQ_CONTROL_STATUS_REG) & + QUANCOM_IRQ_ASSERTED_BIT)) + outb(QUANCOM_IRQ_ENABLE_BIT, nec7210_iobase(priv) + + QUANCOM_IRQ_CONTROL_STATUS_REG); + break; + default: + break; + } + return cb7210_locked_internal_interrupt(arg); +} + +static irqreturn_t cb7210_internal_interrupt(struct gpib_board *board) +{ + int hs_status, status1, status2; + struct cb7210_priv *priv = board->private_data; + struct nec7210_priv *nec_priv = &priv->nec7210_priv; + int clear_bits; + + if ((priv->hs_mode_bits & HS_ENABLE_MASK)) { + status1 = 0; + hs_status = cb7210_read_byte(priv, HS_STATUS); + } else { + hs_status = 0; + status1 = read_byte(nec_priv, ISR1); + } + status2 = read_byte(nec_priv, ISR2); + nec7210_interrupt_have_status(board, nec_priv, status1, status2); + + dev_dbg(board->gpib_dev, "status 0x%x, mode 0x%x\n", hs_status, priv->hs_mode_bits); + + clear_bits = 0; + + if (hs_status & HS_HALF_FULL) { + if (priv->hs_mode_bits & HS_TX_ENABLE) + priv->out_fifo_half_empty = 1; + else if (priv->hs_mode_bits & HS_RX_ENABLE) + priv->in_fifo_half_full = 1; + clear_bits |= HS_CLR_HF_INT; + } + + if (hs_status & HS_SRQ_INT) { + set_bit(SRQI_NUM, &board->status); + clear_bits |= HS_CLR_SRQ_INT; + } + + if ((hs_status & HS_EOI_INT)) { + clear_bits |= HS_CLR_EOI_EMPTY_INT; + set_bit(RECEIVED_END_BN, &nec_priv->state); + if ((nec_priv->auxa_bits & HR_HANDSHAKE_MASK) == HR_HLDE) + set_bit(RFD_HOLDOFF_BN, &nec_priv->state); + } + + if ((priv->hs_mode_bits & HS_TX_ENABLE) && + (hs_status & (HS_TX_MSB_NOT_EMPTY | HS_TX_LSB_NOT_EMPTY)) == 0) + clear_bits |= HS_CLR_EOI_EMPTY_INT; + + if (clear_bits) { + cb7210_write_byte(priv, priv->hs_mode_bits | clear_bits, HS_MODE); + cb7210_write_byte(priv, priv->hs_mode_bits, HS_MODE); + wake_up_interruptible(&board->wait); + } + + return IRQ_HANDLED; +} + +static irqreturn_t cb7210_locked_internal_interrupt(struct gpib_board *board) +{ + unsigned long flags; + irqreturn_t retval; + + spin_lock_irqsave(&board->spinlock, flags); + retval = cb7210_internal_interrupt(board); + spin_unlock_irqrestore(&board->spinlock, flags); + return retval; +} + +static irqreturn_t cb7210_interrupt(int irq, void *arg) +{ + return cb7210_internal_interrupt(arg); +} + +static int cb_pci_attach(struct gpib_board *board, const struct gpib_board_config *config); +static int cb_isa_attach(struct gpib_board *board, const struct gpib_board_config *config); + +static void cb_pci_detach(struct gpib_board *board); +static void cb_isa_detach(struct gpib_board *board); + +// wrappers for interface functions +static int cb7210_read(struct gpib_board *board, u8 *buffer, size_t length, + int *end, size_t *bytes_read) +{ + struct cb7210_priv *priv = board->private_data; + + return nec7210_read(board, &priv->nec7210_priv, buffer, length, end, bytes_read); +} + +static int cb7210_write(struct gpib_board *board, u8 *buffer, size_t length, + int send_eoi, size_t *bytes_written) +{ + struct cb7210_priv *priv = board->private_data; + + return nec7210_write(board, &priv->nec7210_priv, buffer, length, send_eoi, bytes_written); +} + +static int cb7210_command(struct gpib_board *board, u8 *buffer, size_t length, + size_t *bytes_written) +{ + struct cb7210_priv *priv = board->private_data; + + return nec7210_command(board, &priv->nec7210_priv, buffer, length, bytes_written); +} + +static int cb7210_take_control(struct gpib_board *board, int synchronous) +{ + struct cb7210_priv *priv = board->private_data; + + return nec7210_take_control(board, &priv->nec7210_priv, synchronous); +} + +static int cb7210_go_to_standby(struct gpib_board *board) +{ + struct cb7210_priv *priv = board->private_data; + + return nec7210_go_to_standby(board, &priv->nec7210_priv); +} + +static int cb7210_request_system_control(struct gpib_board *board, int request_control) +{ + struct cb7210_priv *priv = board->private_data; + struct nec7210_priv *nec_priv = &priv->nec7210_priv; + + if (request_control) + priv->hs_mode_bits |= HS_SYS_CONTROL; + else + priv->hs_mode_bits &= ~HS_SYS_CONTROL; + + cb7210_write_byte(priv, priv->hs_mode_bits, HS_MODE); + return nec7210_request_system_control(board, nec_priv, request_control); +} + +static void cb7210_interface_clear(struct gpib_board *board, int assert) +{ + struct cb7210_priv *priv = board->private_data; + + nec7210_interface_clear(board, &priv->nec7210_priv, assert); +} + +static void cb7210_remote_enable(struct gpib_board *board, int enable) +{ + struct cb7210_priv *priv = board->private_data; + + nec7210_remote_enable(board, &priv->nec7210_priv, enable); +} + +static int cb7210_enable_eos(struct gpib_board *board, u8 eos_byte, int compare_8_bits) +{ + struct cb7210_priv *priv = board->private_data; + + return nec7210_enable_eos(board, &priv->nec7210_priv, eos_byte, compare_8_bits); +} + +static void cb7210_disable_eos(struct gpib_board *board) +{ + struct cb7210_priv *priv = board->private_data; + + nec7210_disable_eos(board, &priv->nec7210_priv); +} + +static unsigned int cb7210_update_status(struct gpib_board *board, unsigned int clear_mask) +{ + struct cb7210_priv *priv = board->private_data; + + return nec7210_update_status(board, &priv->nec7210_priv, clear_mask); +} + +static int cb7210_primary_address(struct gpib_board *board, unsigned int address) +{ + struct cb7210_priv *priv = board->private_data; + + return nec7210_primary_address(board, &priv->nec7210_priv, address); +} + +static int cb7210_secondary_address(struct gpib_board *board, unsigned int address, int enable) +{ + struct cb7210_priv *priv = board->private_data; + + return nec7210_secondary_address(board, &priv->nec7210_priv, address, enable); +} + +static int cb7210_parallel_poll(struct gpib_board *board, u8 *result) +{ + struct cb7210_priv *priv = board->private_data; + + return nec7210_parallel_poll(board, &priv->nec7210_priv, result); +} + +static void cb7210_parallel_poll_configure(struct gpib_board *board, u8 configuration) +{ + struct cb7210_priv *priv = board->private_data; + + nec7210_parallel_poll_configure(board, &priv->nec7210_priv, configuration); +} + +static void cb7210_parallel_poll_response(struct gpib_board *board, int ist) +{ + struct cb7210_priv *priv = board->private_data; + + nec7210_parallel_poll_response(board, &priv->nec7210_priv, ist); +} + +static void cb7210_serial_poll_response(struct gpib_board *board, u8 status) +{ + struct cb7210_priv *priv = board->private_data; + + nec7210_serial_poll_response(board, &priv->nec7210_priv, status); +} + +static u8 cb7210_serial_poll_status(struct gpib_board *board) +{ + struct cb7210_priv *priv = board->private_data; + + return nec7210_serial_poll_status(board, &priv->nec7210_priv); +} + +static void cb7210_return_to_local(struct gpib_board *board) +{ + struct cb7210_priv *priv = board->private_data; + struct nec7210_priv *nec_priv = &priv->nec7210_priv; + + write_byte(nec_priv, AUX_RTL2, AUXMR); + udelay(1); + write_byte(nec_priv, AUX_RTL, AUXMR); +} + +static struct gpib_interface cb_pci_unaccel_interface = { + .name = "cbi_pci_unaccel", + .attach = cb_pci_attach, + .detach = cb_pci_detach, + .read = cb7210_read, + .write = cb7210_write, + .command = cb7210_command, + .take_control = cb7210_take_control, + .go_to_standby = cb7210_go_to_standby, + .request_system_control = cb7210_request_system_control, + .interface_clear = cb7210_interface_clear, + .remote_enable = cb7210_remote_enable, + .enable_eos = cb7210_enable_eos, + .disable_eos = cb7210_disable_eos, + .parallel_poll = cb7210_parallel_poll, + .parallel_poll_configure = cb7210_parallel_poll_configure, + .parallel_poll_response = cb7210_parallel_poll_response, + .local_parallel_poll_mode = NULL, // XXX + .line_status = cb7210_line_status, + .update_status = cb7210_update_status, + .primary_address = cb7210_primary_address, + .secondary_address = cb7210_secondary_address, + .serial_poll_response = cb7210_serial_poll_response, + .serial_poll_status = cb7210_serial_poll_status, + .t1_delay = cb7210_t1_delay, + .return_to_local = cb7210_return_to_local, +}; + +static struct gpib_interface cb_pci_accel_interface = { + .name = "cbi_pci_accel", + .attach = cb_pci_attach, + .detach = cb_pci_detach, + .read = cb7210_accel_read, + .write = cb7210_accel_write, + .command = cb7210_command, + .take_control = cb7210_take_control, + .go_to_standby = cb7210_go_to_standby, + .request_system_control = cb7210_request_system_control, + .interface_clear = cb7210_interface_clear, + .remote_enable = cb7210_remote_enable, + .enable_eos = cb7210_enable_eos, + .disable_eos = cb7210_disable_eos, + .parallel_poll = cb7210_parallel_poll, + .parallel_poll_configure = cb7210_parallel_poll_configure, + .parallel_poll_response = cb7210_parallel_poll_response, + .local_parallel_poll_mode = NULL, // XXX + .line_status = cb7210_line_status, + .update_status = cb7210_update_status, + .primary_address = cb7210_primary_address, + .secondary_address = cb7210_secondary_address, + .serial_poll_response = cb7210_serial_poll_response, + .serial_poll_status = cb7210_serial_poll_status, + .t1_delay = cb7210_t1_delay, + .return_to_local = cb7210_return_to_local, +}; + +static struct gpib_interface cb_pci_interface = { + .name = "cbi_pci", + .attach = cb_pci_attach, + .detach = cb_pci_detach, + .read = cb7210_accel_read, + .write = cb7210_accel_write, + .command = cb7210_command, + .take_control = cb7210_take_control, + .go_to_standby = cb7210_go_to_standby, + .request_system_control = cb7210_request_system_control, + .interface_clear = cb7210_interface_clear, + .remote_enable = cb7210_remote_enable, + .enable_eos = cb7210_enable_eos, + .disable_eos = cb7210_disable_eos, + .parallel_poll = cb7210_parallel_poll, + .parallel_poll_configure = cb7210_parallel_poll_configure, + .parallel_poll_response = cb7210_parallel_poll_response, + .line_status = cb7210_line_status, + .update_status = cb7210_update_status, + .primary_address = cb7210_primary_address, + .secondary_address = cb7210_secondary_address, + .serial_poll_response = cb7210_serial_poll_response, + .serial_poll_status = cb7210_serial_poll_status, + .t1_delay = cb7210_t1_delay, + .return_to_local = cb7210_return_to_local, +}; + +static struct gpib_interface cb_isa_unaccel_interface = { + .name = "cbi_isa_unaccel", + .attach = cb_isa_attach, + .detach = cb_isa_detach, + .read = cb7210_read, + .write = cb7210_write, + .command = cb7210_command, + .take_control = cb7210_take_control, + .go_to_standby = cb7210_go_to_standby, + .request_system_control = cb7210_request_system_control, + .interface_clear = cb7210_interface_clear, + .remote_enable = cb7210_remote_enable, + .enable_eos = cb7210_enable_eos, + .disable_eos = cb7210_disable_eos, + .parallel_poll = cb7210_parallel_poll, + .parallel_poll_configure = cb7210_parallel_poll_configure, + .parallel_poll_response = cb7210_parallel_poll_response, + .local_parallel_poll_mode = NULL, // XXX + .line_status = cb7210_line_status, + .update_status = cb7210_update_status, + .primary_address = cb7210_primary_address, + .secondary_address = cb7210_secondary_address, + .serial_poll_response = cb7210_serial_poll_response, + .serial_poll_status = cb7210_serial_poll_status, + .t1_delay = cb7210_t1_delay, + .return_to_local = cb7210_return_to_local, +}; + +static struct gpib_interface cb_isa_interface = { + .name = "cbi_isa", + .attach = cb_isa_attach, + .detach = cb_isa_detach, + .read = cb7210_accel_read, + .write = cb7210_accel_write, + .command = cb7210_command, + .take_control = cb7210_take_control, + .go_to_standby = cb7210_go_to_standby, + .request_system_control = cb7210_request_system_control, + .interface_clear = cb7210_interface_clear, + .remote_enable = cb7210_remote_enable, + .enable_eos = cb7210_enable_eos, + .disable_eos = cb7210_disable_eos, + .parallel_poll = cb7210_parallel_poll, + .parallel_poll_configure = cb7210_parallel_poll_configure, + .parallel_poll_response = cb7210_parallel_poll_response, + .line_status = cb7210_line_status, + .update_status = cb7210_update_status, + .primary_address = cb7210_primary_address, + .secondary_address = cb7210_secondary_address, + .serial_poll_response = cb7210_serial_poll_response, + .serial_poll_status = cb7210_serial_poll_status, + .t1_delay = cb7210_t1_delay, + .return_to_local = cb7210_return_to_local, +}; + +static struct gpib_interface cb_isa_accel_interface = { + .name = "cbi_isa_accel", + .attach = cb_isa_attach, + .detach = cb_isa_detach, + .read = cb7210_accel_read, + .write = cb7210_accel_write, + .command = cb7210_command, + .take_control = cb7210_take_control, + .go_to_standby = cb7210_go_to_standby, + .request_system_control = cb7210_request_system_control, + .interface_clear = cb7210_interface_clear, + .remote_enable = cb7210_remote_enable, + .enable_eos = cb7210_enable_eos, + .disable_eos = cb7210_disable_eos, + .parallel_poll = cb7210_parallel_poll, + .parallel_poll_configure = cb7210_parallel_poll_configure, + .parallel_poll_response = cb7210_parallel_poll_response, + .local_parallel_poll_mode = NULL, // XXX + .line_status = cb7210_line_status, + .update_status = cb7210_update_status, + .primary_address = cb7210_primary_address, + .secondary_address = cb7210_secondary_address, + .serial_poll_response = cb7210_serial_poll_response, + .serial_poll_status = cb7210_serial_poll_status, + .t1_delay = cb7210_t1_delay, + .return_to_local = cb7210_return_to_local, +}; + +static int cb7210_allocate_private(struct gpib_board *board) +{ + struct cb7210_priv *priv; + + board->private_data = kmalloc(sizeof(struct cb7210_priv), GFP_KERNEL); + if (!board->private_data) + return -ENOMEM; + priv = board->private_data; + memset(priv, 0, sizeof(struct cb7210_priv)); + init_nec7210_private(&priv->nec7210_priv); + return 0; +} + +static void cb7210_generic_detach(struct gpib_board *board) +{ + kfree(board->private_data); + board->private_data = NULL; +} + +// generic part of attach functions shared by all cb7210 boards +static int cb7210_generic_attach(struct gpib_board *board) +{ + struct cb7210_priv *cb_priv; + struct nec7210_priv *nec_priv; + + board->status = 0; + + if (cb7210_allocate_private(board)) + return -ENOMEM; + cb_priv = board->private_data; + nec_priv = &cb_priv->nec7210_priv; + nec_priv->read_byte = nec7210_locking_ioport_read_byte; + nec_priv->write_byte = nec7210_locking_ioport_write_byte; + nec_priv->offset = cb7210_reg_offset; + nec_priv->type = CB7210; + return 0; +} + +static int cb7210_init(struct cb7210_priv *cb_priv, struct gpib_board *board) +{ + struct nec7210_priv *nec_priv = &cb_priv->nec7210_priv; + + cb7210_write_byte(cb_priv, HS_RESET7210, HS_INT_LEVEL); + cb7210_write_byte(cb_priv, irq_bits(cb_priv->irq), HS_INT_LEVEL); + + nec7210_board_reset(nec_priv, board); + cb7210_write_byte(cb_priv, HS_TX_ENABLE | HS_RX_ENABLE | HS_CLR_SRQ_INT | + HS_CLR_EOI_EMPTY_INT | HS_CLR_HF_INT, HS_MODE); + + cb_priv->hs_mode_bits = HS_HF_INT_EN; + cb7210_write_byte(cb_priv, cb_priv->hs_mode_bits, HS_MODE); + + write_byte(nec_priv, AUX_LO_SPEED, AUXMR); + /* + * set clock register for maximum (20 MHz) driving frequency + * ICR should be set to clock in megahertz (1-15) and to zero + * for clocks faster than 15 MHz (max 20MHz) + */ + write_byte(nec_priv, ICR | 0, AUXMR); + + if (cb_priv->pci_chip == PCI_CHIP_QUANCOM) { + /* change interrupt polarity */ + nec_priv->auxb_bits |= HR_INV; + write_byte(nec_priv, nec_priv->auxb_bits, AUXMR); + } + nec7210_board_online(nec_priv, board); + + /* poll so we can detect assertion of ATN */ + if (gpib_request_pseudo_irq(board, cb_pci_interrupt)) { + pr_err("failed to allocate pseudo_irq\n"); + return -1; + } + return 0; +} + +static int cb_pci_attach(struct gpib_board *board, const struct gpib_board_config *config) +{ + struct cb7210_priv *cb_priv; + struct nec7210_priv *nec_priv; + int isr_flags = 0; + int bits; + int retval; + + retval = cb7210_generic_attach(board); + if (retval) + return retval; + + cb_priv = board->private_data; + nec_priv = &cb_priv->nec7210_priv; + + cb_priv->pci_device = gpib_pci_get_device(config, PCI_VENDOR_ID_CBOARDS, + PCI_DEVICE_ID_CBOARDS_PCI_GPIB, NULL); + if (cb_priv->pci_device) + cb_priv->pci_chip = PCI_CHIP_AMCC_S5933; + if (!cb_priv->pci_device) { + cb_priv->pci_device = gpib_pci_get_device(config, PCI_VENDOR_ID_CBOARDS, + PCI_DEVICE_ID_CBOARDS_CPCI_GPIB, NULL); + if (cb_priv->pci_device) + cb_priv->pci_chip = PCI_CHIP_AMCC_S5933; + } + if (!cb_priv->pci_device) { + cb_priv->pci_device = gpib_pci_get_device(config, PCI_VENDOR_ID_QUANCOM, + PCI_DEVICE_ID_QUANCOM_GPIB, NULL); + if (cb_priv->pci_device) { + cb_priv->pci_chip = PCI_CHIP_QUANCOM; + nec_priv->offset = 4; + } + } + if (!cb_priv->pci_device) { + dev_err(board->gpib_dev, "no supported boards found.\n"); + return -ENODEV; + } + + if (pci_enable_device(cb_priv->pci_device)) { + dev_err(board->gpib_dev, "error enabling pci device\n"); + return -EIO; + } + + if (pci_request_regions(cb_priv->pci_device, DRV_NAME)) + return -EBUSY; + switch (cb_priv->pci_chip) { + case PCI_CHIP_AMCC_S5933: + cb_priv->amcc_iobase = pci_resource_start(cb_priv->pci_device, 0); + nec_priv->iobase = pci_resource_start(cb_priv->pci_device, 1); + cb_priv->fifo_iobase = pci_resource_start(cb_priv->pci_device, 2); + break; + case PCI_CHIP_QUANCOM: + nec_priv->iobase = pci_resource_start(cb_priv->pci_device, 0); + cb_priv->fifo_iobase = nec_priv->iobase; + break; + default: + dev_err(board->gpib_dev, "bug! unhandled pci_chip=%i\n", cb_priv->pci_chip); + return -EIO; + } + isr_flags |= IRQF_SHARED; + if (request_irq(cb_priv->pci_device->irq, cb_pci_interrupt, isr_flags, DRV_NAME, board)) { + dev_err(board->gpib_dev, "can't request IRQ %d\n", + cb_priv->pci_device->irq); + return -EBUSY; + } + cb_priv->irq = cb_priv->pci_device->irq; + + switch (cb_priv->pci_chip) { + case PCI_CHIP_AMCC_S5933: + // make sure mailbox flags are clear + inl(cb_priv->amcc_iobase + INCOMING_MAILBOX_REG(3)); + // enable interrupts on amccs5933 chip + bits = INBOX_FULL_INTR_BIT | INBOX_BYTE_BITS(3) | INBOX_SELECT_BITS(3) | + INBOX_INTR_CS_BIT; + outl(bits, cb_priv->amcc_iobase + INTCSR_REG); + break; + default: + break; + } + return cb7210_init(cb_priv, board); +} + +static void cb_pci_detach(struct gpib_board *board) +{ + struct cb7210_priv *cb_priv = board->private_data; + struct nec7210_priv *nec_priv; + + if (cb_priv) { + gpib_free_pseudo_irq(board); + nec_priv = &cb_priv->nec7210_priv; + if (cb_priv->irq) { + // disable amcc interrupts + outl(0, cb_priv->amcc_iobase + INTCSR_REG); + free_irq(cb_priv->irq, board); + } + if (nec_priv->iobase) { + nec7210_board_reset(nec_priv, board); + pci_release_regions(cb_priv->pci_device); + } + if (cb_priv->pci_device) + pci_dev_put(cb_priv->pci_device); + } + cb7210_generic_detach(board); +} + +static int cb_isa_attach(struct gpib_board *board, const struct gpib_board_config *config) +{ + int isr_flags = 0; + struct cb7210_priv *cb_priv; + struct nec7210_priv *nec_priv; + unsigned int bits; + int retval; + + retval = cb7210_generic_attach(board); + if (retval) + return retval; + cb_priv = board->private_data; + nec_priv = &cb_priv->nec7210_priv; + if (!request_region(config->ibbase, cb7210_iosize, DRV_NAME)) { + dev_err(board->gpib_dev, "ioports starting at 0x%x are already in use\n", + config->ibbase); + return -EBUSY; + } + nec_priv->iobase = config->ibbase; + cb_priv->fifo_iobase = nec7210_iobase(cb_priv); + + bits = irq_bits(config->ibirq); + if (bits == 0) + dev_err(board->gpib_dev, "board incapable of using irq %i, try 2-5, 7, 10, or 11\n", + config->ibirq); + + // install interrupt handler + if (request_irq(config->ibirq, cb7210_interrupt, isr_flags, DRV_NAME, board)) { + dev_err(board->gpib_dev, "failed to obtain IRQ %d\n", config->ibirq); + return -EBUSY; + } + cb_priv->irq = config->ibirq; + + return cb7210_init(cb_priv, board); +} + +static void cb_isa_detach(struct gpib_board *board) +{ + struct cb7210_priv *cb_priv = board->private_data; + struct nec7210_priv *nec_priv; + + if (cb_priv) { + gpib_free_pseudo_irq(board); + nec_priv = &cb_priv->nec7210_priv; + if (cb_priv->irq) + free_irq(cb_priv->irq, board); + if (nec_priv->iobase) { + nec7210_board_reset(nec_priv, board); + release_region(nec7210_iobase(cb_priv), cb7210_iosize); + } + } + cb7210_generic_detach(board); +} + +static int cb7210_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + return 0; +} + +static const struct pci_device_id cb7210_pci_table[] = { + {PCI_VENDOR_ID_CBOARDS, PCI_DEVICE_ID_CBOARDS_PCI_GPIB, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + {PCI_VENDOR_ID_CBOARDS, PCI_DEVICE_ID_CBOARDS_CPCI_GPIB, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + {PCI_VENDOR_ID_QUANCOM, PCI_DEVICE_ID_QUANCOM_GPIB, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, cb7210_pci_table); + +static struct pci_driver cb7210_pci_driver = { + .name = DRV_NAME, + .id_table = cb7210_pci_table, + .probe = &cb7210_pci_probe +}; + +/*************************************************************************** + * Support for computer boards pcmcia-gpib card + * + * Based on gpib PCMCIA client driver written by Claus Schroeter + * (clausi@chemie.fu-berlin.de), which was adapted from the + * pcmcia skeleton example (presumably David Hinds) + ***************************************************************************/ + +#ifdef CONFIG_GPIB_PCMCIA + +#include +#include +#include +#include + +#include +#include + +/* + * The event() function is this driver's Card Services event handler. + * It will be called by Card Services when an appropriate card status + * event is received. The config() and release() entry points are + * used to configure or release a socket, in response to card insertion + * and ejection events. They are invoked from the gpib event + * handler. + */ + +static int cb_gpib_config(struct pcmcia_device *link); +static void cb_gpib_release(struct pcmcia_device *link); +static int cb_pcmcia_attach(struct gpib_board *board, const struct gpib_board_config *config); +static void cb_pcmcia_detach(struct gpib_board *board); + +/* + * A linked list of "instances" of the gpib device. Each actual + * PCMCIA card corresponds to one device instance, and is described + * by one dev_link_t structure (defined in ds.h). + * + * You may not want to use a linked list for this -- for example, the + * memory card driver uses an array of dev_link_t pointers, where minor + * device numbers are used to derive the corresponding array index. + */ + +static struct pcmcia_device *curr_dev; + +/* + * A dev_link_t structure has fields for most things that are needed + * to keep track of a socket, but there will usually be some device + * specific information that also needs to be kept track of. The + * 'priv' pointer in a dev_link_t structure can be used to point to + * a device-specific private data structure, like this. + * + * A driver needs to provide a dev_node_t structure for each device + * on a card. In some cases, there is only one device per card (for + * example, ethernet cards, modems). In other cases, there may be + * many actual or logical devices (SCSI adapters, memory cards with + * multiple partitions). The dev_node_t structures need to be kept + * in a linked list starting at the 'dev' field of a dev_link_t + * structure. We allocate them in the card's private data structure, + * because they generally can't be allocated dynamically. + */ + +struct local_info { + struct pcmcia_device *p_dev; + struct gpib_board *dev; +}; + +/* + * gpib_attach() creates an "instance" of the driver, allocating + * local data structures for one device. The device is registered + * with Card Services. + * + * The dev_link structure is initialized, but we don't actually + * configure the card at this point -- we wait until we receive a + * card insertion event. + */ + +static int cb_gpib_probe(struct pcmcia_device *link) +{ + struct local_info *info; + int ret; + + /* Allocate space for private device-specific data */ + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->p_dev = link; + link->priv = info; + + /* The io structure describes IO port mapping */ + link->resource[0]->end = 16; + link->resource[0]->flags &= ~IO_DATA_PATH_WIDTH; + link->resource[0]->flags |= IO_DATA_PATH_WIDTH_AUTO; + link->resource[1]->end = 16; + link->resource[1]->flags &= ~IO_DATA_PATH_WIDTH; + link->resource[1]->flags |= IO_DATA_PATH_WIDTH_16; + link->io_lines = 10; + + /* General socket configuration */ + link->config_flags = CONF_ENABLE_IRQ | CONF_AUTO_SET_IO; + link->config_index = 1; + link->config_regs = PRESENT_OPTION; + + /* Register with Card Services */ + curr_dev = link; + ret = cb_gpib_config(link); + if (ret) + goto free_info; + + return 0; + +free_info: + kfree(info); + return ret; +} + +/* + * This deletes a driver "instance". The device is de-registered + * with Card Services. If it has been released, all local data + * structures are freed. Otherwise, the structures will be freed + * when the device is released. + */ + +static void cb_gpib_remove(struct pcmcia_device *link) +{ + struct local_info *info = link->priv; + //struct struct gpib_board *dev = info->dev; + + if (info->dev) + cb_pcmcia_detach(info->dev); + cb_gpib_release(link); + + //free_netdev(dev); + kfree(info); +} + +static int cb_gpib_config_iteration(struct pcmcia_device *link, void *priv_data) +{ + return pcmcia_request_io(link); +} + +/* + * gpib_config() is scheduled to run after a CARD_INSERTION event + * is received, to configure the PCMCIA socket, and to make the + * ethernet device available to the system. + */ + +static int cb_gpib_config(struct pcmcia_device *link) +{ + int retval; + + retval = pcmcia_loop_config(link, &cb_gpib_config_iteration, NULL); + if (retval) { + dev_warn(&link->dev, "no configuration found\n"); + cb_gpib_release(link); + return -ENODEV; + } + + /* + * This actually configures the PCMCIA socket -- setting up + * the I/O windows and the interrupt mapping. + */ + retval = pcmcia_enable_device(link); + if (retval) { + dev_warn(&link->dev, "pcmcia_enable_device failed\n"); + cb_gpib_release(link); + return -ENODEV; + } + + return 0; +} /* gpib_config */ + +/* + * After a card is removed, gpib_release() will unregister the net + * device, and release the PCMCIA configuration. If the device is + * still open, this will be postponed until it is closed. + */ + +static void cb_gpib_release(struct pcmcia_device *link) +{ + pcmcia_disable_device(link); +} + +static int cb_gpib_suspend(struct pcmcia_device *link) +{ + //struct local_info *info = link->priv; + //struct struct gpib_board *dev = info->dev; + + if (link->open) + dev_warn(&link->dev, "Device still open\n"); + //netif_device_detach(dev); + + return 0; +} + +static int cb_gpib_resume(struct pcmcia_device *link) +{ + //struct local_info *info = link->priv; + //struct struct gpib_board *dev = info->dev; + + /*if (link->open) { + * ni_gpib_probe(dev); / really? + * //netif_device_attach(dev); + * + */ + return cb_gpib_config(link); +} + +/*====================================================================*/ + +static struct pcmcia_device_id cb_pcmcia_ids[] = { + PCMCIA_DEVICE_MANF_CARD(0x01c5, 0x0005), + PCMCIA_DEVICE_NULL +}; +MODULE_DEVICE_TABLE(pcmcia, cb_pcmcia_ids); + +static struct pcmcia_driver cb_gpib_cs_driver = { + .name = "cb_gpib_cs", + .owner = THIS_MODULE, + .id_table = cb_pcmcia_ids, + .probe = cb_gpib_probe, + .remove = cb_gpib_remove, + .suspend = cb_gpib_suspend, + .resume = cb_gpib_resume, +}; + +static void cb_pcmcia_cleanup_module(void) +{ + pcmcia_unregister_driver(&cb_gpib_cs_driver); +} + +static struct gpib_interface cb_pcmcia_unaccel_interface = { + .name = "cbi_pcmcia_unaccel", + .attach = cb_pcmcia_attach, + .detach = cb_pcmcia_detach, + .read = cb7210_read, + .write = cb7210_write, + .command = cb7210_command, + .take_control = cb7210_take_control, + .go_to_standby = cb7210_go_to_standby, + .request_system_control = cb7210_request_system_control, + .interface_clear = cb7210_interface_clear, + .remote_enable = cb7210_remote_enable, + .enable_eos = cb7210_enable_eos, + .disable_eos = cb7210_disable_eos, + .parallel_poll = cb7210_parallel_poll, + .parallel_poll_configure = cb7210_parallel_poll_configure, + .parallel_poll_response = cb7210_parallel_poll_response, + .local_parallel_poll_mode = NULL, // XXX + .line_status = cb7210_line_status, + .update_status = cb7210_update_status, + .primary_address = cb7210_primary_address, + .secondary_address = cb7210_secondary_address, + .serial_poll_response = cb7210_serial_poll_response, + .serial_poll_status = cb7210_serial_poll_status, + .t1_delay = cb7210_t1_delay, + .return_to_local = cb7210_return_to_local, +}; + +static struct gpib_interface cb_pcmcia_interface = { + .name = "cbi_pcmcia", + .attach = cb_pcmcia_attach, + .detach = cb_pcmcia_detach, + .read = cb7210_accel_read, + .write = cb7210_accel_write, + .command = cb7210_command, + .take_control = cb7210_take_control, + .go_to_standby = cb7210_go_to_standby, + .request_system_control = cb7210_request_system_control, + .interface_clear = cb7210_interface_clear, + .remote_enable = cb7210_remote_enable, + .enable_eos = cb7210_enable_eos, + .disable_eos = cb7210_disable_eos, + .parallel_poll = cb7210_parallel_poll, + .parallel_poll_configure = cb7210_parallel_poll_configure, + .parallel_poll_response = cb7210_parallel_poll_response, + .local_parallel_poll_mode = NULL, // XXX + .line_status = cb7210_line_status, + .update_status = cb7210_update_status, + .primary_address = cb7210_primary_address, + .secondary_address = cb7210_secondary_address, + .serial_poll_response = cb7210_serial_poll_response, + .serial_poll_status = cb7210_serial_poll_status, + .t1_delay = cb7210_t1_delay, + .return_to_local = cb7210_return_to_local, +}; + +static struct gpib_interface cb_pcmcia_accel_interface = { + .name = "cbi_pcmcia_accel", + .attach = cb_pcmcia_attach, + .detach = cb_pcmcia_detach, + .read = cb7210_accel_read, + .write = cb7210_accel_write, + .command = cb7210_command, + .take_control = cb7210_take_control, + .go_to_standby = cb7210_go_to_standby, + .request_system_control = cb7210_request_system_control, + .interface_clear = cb7210_interface_clear, + .remote_enable = cb7210_remote_enable, + .enable_eos = cb7210_enable_eos, + .disable_eos = cb7210_disable_eos, + .parallel_poll = cb7210_parallel_poll, + .parallel_poll_configure = cb7210_parallel_poll_configure, + .parallel_poll_response = cb7210_parallel_poll_response, + .local_parallel_poll_mode = NULL, // XXX + .line_status = cb7210_line_status, + .update_status = cb7210_update_status, + .primary_address = cb7210_primary_address, + .secondary_address = cb7210_secondary_address, + .serial_poll_response = cb7210_serial_poll_response, + .serial_poll_status = cb7210_serial_poll_status, + .t1_delay = cb7210_t1_delay, + .return_to_local = cb7210_return_to_local, +}; + +static int cb_pcmcia_attach(struct gpib_board *board, const struct gpib_board_config *config) +{ + struct cb7210_priv *cb_priv; + struct nec7210_priv *nec_priv; + int retval; + + if (!curr_dev) { + dev_err(board->gpib_dev, "no cb pcmcia cards found\n"); + return -ENODEV; + } + + retval = cb7210_generic_attach(board); + if (retval) + return retval; + + cb_priv = board->private_data; + nec_priv = &cb_priv->nec7210_priv; + + if (!request_region(curr_dev->resource[0]->start, resource_size(curr_dev->resource[0]), + DRV_NAME)) { + dev_err(board->gpib_dev, "ioports starting at 0x%lx are already in use\n", + (unsigned long)curr_dev->resource[0]->start); + return -EBUSY; + } + nec_priv->iobase = curr_dev->resource[0]->start; + cb_priv->fifo_iobase = curr_dev->resource[0]->start; + + if (request_irq(curr_dev->irq, cb7210_interrupt, IRQF_SHARED, DRV_NAME, board)) { + dev_err(board->gpib_dev, "failed to request IRQ %d\n", curr_dev->irq); + return -EBUSY; + } + cb_priv->irq = curr_dev->irq; + + return cb7210_init(cb_priv, board); +} + +static void cb_pcmcia_detach(struct gpib_board *board) +{ + struct cb7210_priv *cb_priv = board->private_data; + struct nec7210_priv *nec_priv; + + if (cb_priv) { + nec_priv = &cb_priv->nec7210_priv; + gpib_free_pseudo_irq(board); + if (cb_priv->irq) + free_irq(cb_priv->irq, board); + if (nec_priv->iobase) { + nec7210_board_reset(nec_priv, board); + release_region(nec7210_iobase(cb_priv), cb7210_iosize); + } + } + cb7210_generic_detach(board); +} + +#endif /* CONFIG_GPIB_PCMCIA */ + +static int __init cb7210_init_module(void) +{ + int ret; + + ret = pci_register_driver(&cb7210_pci_driver); + if (ret) { + pr_err("pci_register_driver failed: error = %d\n", ret); + return ret; + } + + ret = gpib_register_driver(&cb_pci_interface, THIS_MODULE); + if (ret) { + pr_err("gpib_register_driver failed: error = %d\n", ret); + goto err_pci; + } + + ret = gpib_register_driver(&cb_isa_interface, THIS_MODULE); + if (ret) { + pr_err("gpib_register_driver failed: error = %d\n", ret); + goto err_isa; + } + + ret = gpib_register_driver(&cb_pci_accel_interface, THIS_MODULE); + if (ret) { + pr_err("gpib_register_driver failed: error = %d\n", ret); + goto err_pci_accel; + } + + ret = gpib_register_driver(&cb_pci_unaccel_interface, THIS_MODULE); + if (ret) { + pr_err("gpib_register_driver failed: error = %d\n", ret); + goto err_pci_unaccel; + } + + ret = gpib_register_driver(&cb_isa_accel_interface, THIS_MODULE); + if (ret) { + pr_err("gpib_register_driver failed: error = %d\n", ret); + goto err_isa_accel; + } + + ret = gpib_register_driver(&cb_isa_unaccel_interface, THIS_MODULE); + if (ret) { + pr_err("gpib_register_driver failed: error = %d\n", ret); + goto err_isa_unaccel; + } + +#ifdef CONFIG_GPIB_PCMCIA + ret = gpib_register_driver(&cb_pcmcia_interface, THIS_MODULE); + if (ret) { + pr_err("gpib_register_driver failed: error = %d\n", ret); + goto err_pcmcia; + } + + ret = gpib_register_driver(&cb_pcmcia_accel_interface, THIS_MODULE); + if (ret) { + pr_err("gpib_register_driver failed: error = %d\n", ret); + goto err_pcmcia_accel; + } + + ret = gpib_register_driver(&cb_pcmcia_unaccel_interface, THIS_MODULE); + if (ret) { + pr_err("gpib_register_driver failed: error = %d\n", ret); + goto err_pcmcia_unaccel; + } + + ret = pcmcia_register_driver(&cb_gpib_cs_driver); + if (ret) { + pr_err("pcmcia_register_driver failed: error = %d\n", ret); + goto err_pcmcia_driver; + } +#endif + + return 0; + +#ifdef CONFIG_GPIB_PCMCIA +err_pcmcia_driver: + gpib_unregister_driver(&cb_pcmcia_unaccel_interface); +err_pcmcia_unaccel: + gpib_unregister_driver(&cb_pcmcia_accel_interface); +err_pcmcia_accel: + gpib_unregister_driver(&cb_pcmcia_interface); +err_pcmcia: +#endif + gpib_unregister_driver(&cb_isa_unaccel_interface); +err_isa_unaccel: + gpib_unregister_driver(&cb_isa_accel_interface); +err_isa_accel: + gpib_unregister_driver(&cb_pci_unaccel_interface); +err_pci_unaccel: + gpib_unregister_driver(&cb_pci_accel_interface); +err_pci_accel: + gpib_unregister_driver(&cb_isa_interface); +err_isa: + gpib_unregister_driver(&cb_pci_interface); +err_pci: + pci_unregister_driver(&cb7210_pci_driver); + + return ret; +} + +static void __exit cb7210_exit_module(void) +{ + gpib_unregister_driver(&cb_pci_interface); + gpib_unregister_driver(&cb_isa_interface); + gpib_unregister_driver(&cb_pci_accel_interface); + gpib_unregister_driver(&cb_pci_unaccel_interface); + gpib_unregister_driver(&cb_isa_accel_interface); + gpib_unregister_driver(&cb_isa_unaccel_interface); +#ifdef CONFIG_GPIB_PCMCIA + gpib_unregister_driver(&cb_pcmcia_interface); + gpib_unregister_driver(&cb_pcmcia_accel_interface); + gpib_unregister_driver(&cb_pcmcia_unaccel_interface); + cb_pcmcia_cleanup_module(); +#endif + + pci_unregister_driver(&cb7210_pci_driver); +} + +module_init(cb7210_init_module); +module_exit(cb7210_exit_module); diff --git a/drivers/gpib/cb7210/cb7210.h b/drivers/gpib/cb7210/cb7210.h new file mode 100644 index 000000000000..ddc841ff87ae --- /dev/null +++ b/drivers/gpib/cb7210/cb7210.h @@ -0,0 +1,203 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/*************************************************************************** + * copyright : (C) 2002 by Frank Mori Hess + ***************************************************************************/ + +#include "nec7210.h" +#include "gpibP.h" +#include "amccs5933.h" + +#include +#include + +enum { + PCI_DEVICE_ID_CBOARDS_PCI_GPIB = 0x6, + PCI_DEVICE_ID_CBOARDS_CPCI_GPIB = 0xe, +}; + +enum pci_chip { + PCI_CHIP_NONE = 0, + PCI_CHIP_AMCC_S5933, + PCI_CHIP_QUANCOM +}; + +// struct which defines private_data for cb7210 boards +struct cb7210_priv { + struct nec7210_priv nec7210_priv; + struct pci_dev *pci_device; + // base address of amccs5933 pci chip + unsigned long amcc_iobase; + unsigned long fifo_iobase; + unsigned int irq; + enum pci_chip pci_chip; + u8 hs_mode_bits; + unsigned out_fifo_half_empty : 1; + unsigned in_fifo_half_full : 1; +}; + +// pci-gpib register offset +static const int cb7210_reg_offset = 1; + +// uses 10 ioports +static const int cb7210_iosize = 10; + +// fifo size in bytes +static const int cb7210_fifo_size = 2048; +static const int cb7210_fifo_width = 2; + +// cb7210 specific registers and bits +enum cb7210_regs { + BUS_STATUS = 0x7, +}; + +enum cb7210_page_in { + BUS_STATUS_PAGE = 1, +}; + +enum hs_regs { + // write registers + HS_MODE = 0x8, /* HS_MODE register */ + HS_INT_LEVEL = 0x9, /* HS_INT_LEVEL register */ + // read registers + HS_STATUS = 0x8, /* HS_STATUS register */ +}; + +static inline u32 nec7210_iobase(const struct cb7210_priv *cb_priv) +{ + return cb_priv->nec7210_priv.iobase; +} + +static inline int cb7210_page_in_bits(unsigned int page) +{ + return 0x50 | (page & 0xf); +} + +static inline u8 cb7210_paged_read_byte(struct cb7210_priv *cb_priv, + unsigned int register_num, unsigned int page) +{ + struct nec7210_priv *nec_priv = &cb_priv->nec7210_priv; + u8 retval; + unsigned long flags; + + spin_lock_irqsave(&nec_priv->register_page_lock, flags); + outb(cb7210_page_in_bits(page), nec7210_iobase(cb_priv) + AUXMR * nec_priv->offset); + udelay(1); + retval = inb(nec7210_iobase(cb_priv) + register_num * nec_priv->offset); + spin_unlock_irqrestore(&nec_priv->register_page_lock, flags); + return retval; +} + +// don't use for register_num < 8, since it doesn't lock +static inline u8 cb7210_read_byte(const struct cb7210_priv *cb_priv, + enum hs_regs register_num) +{ + const struct nec7210_priv *nec_priv = &cb_priv->nec7210_priv; + u8 retval; + + retval = inb(nec7210_iobase(cb_priv) + register_num * nec_priv->offset); + return retval; +} + +static inline void cb7210_paged_write_byte(struct cb7210_priv *cb_priv, u8 data, + unsigned int register_num, unsigned int page) +{ + struct nec7210_priv *nec_priv = &cb_priv->nec7210_priv; + unsigned long flags; + + spin_lock_irqsave(&nec_priv->register_page_lock, flags); + outb(cb7210_page_in_bits(page), nec7210_iobase(cb_priv) + AUXMR * nec_priv->offset); + udelay(1); + outb(data, nec7210_iobase(cb_priv) + register_num * nec_priv->offset); + spin_unlock_irqrestore(&nec_priv->register_page_lock, flags); +} + +// don't use for register_num < 8, since it doesn't lock +static inline void cb7210_write_byte(const struct cb7210_priv *cb_priv, u8 data, + enum hs_regs register_num) +{ + const struct nec7210_priv *nec_priv = &cb_priv->nec7210_priv; + + outb(data, nec7210_iobase(cb_priv) + register_num * nec_priv->offset); +} + +enum bus_status_bits { + BSR_ATN_BIT = 0x1, + BSR_EOI_BIT = 0x2, + BSR_SRQ_BIT = 0x4, + BSR_IFC_BIT = 0x8, + BSR_REN_BIT = 0x10, + BSR_DAV_BIT = 0x20, + BSR_NRFD_BIT = 0x40, + BSR_NDAC_BIT = 0x80, +}; + +/* CBI 488.2 HS control */ + +/* + * when both bit 0 and 1 are set, it + * 1 clears the transmit state machine to an initial condition + * 2 clears any residual interrupts left latched on cbi488.2 + * 3 resets all control bits in HS_MODE to zero + * 4 enables TX empty interrupts + * when both bit 0 and 1 are zero, then the high speed mode is disabled + */ +enum hs_mode_bits { + HS_ENABLE_MASK = 0x3, + HS_TX_ENABLE = (1 << 0), + HS_RX_ENABLE = (1 << 1), + HS_HF_INT_EN = (1 << 3), + HS_CLR_SRQ_INT = (1 << 4), + HS_CLR_EOI_EMPTY_INT = (1 << 5), + HS_CLR_HF_INT = (1 << 6), + HS_SYS_CONTROL = (1 << 7), +}; + +/* CBI 488.2 status */ +enum hs_status_bits { + HS_FIFO_FULL = (1 << 0), + HS_HALF_FULL = (1 << 1), + HS_SRQ_INT = (1 << 2), + HS_EOI_INT = (1 << 3), + HS_TX_MSB_NOT_EMPTY = (1 << 4), + HS_RX_MSB_NOT_EMPTY = (1 << 5), + HS_TX_LSB_NOT_EMPTY = (1 << 6), + HS_RX_LSB_NOT_EMPTY = (1 << 7), +}; + +/* CBI488.2 hs_int_level register */ +enum hs_int_level_bits { + HS_RESET7210 = (1 << 7), +}; + +static inline unsigned int irq_bits(unsigned int irq) +{ + switch (irq) { + case 2: + case 3: + case 4: + case 5: + return irq - 1; + case 7: + return 0x5; + case 10: + return 0x6; + case 11: + return 0x7; + default: + return 0; + } +} + +enum cb7210_aux_cmds { +/* + * AUX_RTL2 is an undocumented aux command which causes cb7210 to assert + * (and keep asserted) local rtl message. This is used in conjunction + * with the (stupid) cb7210 implementation + * of the normal nec7210 AUX_RTL aux command, which + * causes the rtl message to toggle between on and off. + */ + AUX_RTL2 = 0xd, + AUX_LO_SPEED = 0x40, + AUX_HI_SPEED = 0x41, +}; diff --git a/drivers/gpib/cec/Makefile b/drivers/gpib/cec/Makefile new file mode 100644 index 000000000000..b7141e23d4e0 --- /dev/null +++ b/drivers/gpib/cec/Makefile @@ -0,0 +1,3 @@ + +obj-$(CONFIG_GPIB_CEC_PCI) += cec_gpib.o + diff --git a/drivers/gpib/cec/cec.h b/drivers/gpib/cec/cec.h new file mode 100644 index 000000000000..3ce2869c7429 --- /dev/null +++ b/drivers/gpib/cec/cec.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/*************************************************************************** + * copyright : (C) 2002 by Frank Mori Hess + ***************************************************************************/ + +#include "nec7210.h" +#include "gpibP.h" +#include "plx9050.h" + +struct cec_priv { + struct nec7210_priv nec7210_priv; + struct pci_dev *pci_device; + // base address for plx9052 pci chip + unsigned long plx_iobase; + unsigned int irq; +}; + +// offset between consecutive nec7210 registers +static const int cec_reg_offset = 1; diff --git a/drivers/gpib/cec/cec_gpib.c b/drivers/gpib/cec/cec_gpib.c new file mode 100644 index 000000000000..dbf9b95baabc --- /dev/null +++ b/drivers/gpib/cec/cec_gpib.c @@ -0,0 +1,393 @@ +// SPDX-License-Identifier: GPL-2.0 + +/*************************************************************************** + * copyright : (C) 2002 by Frank Mori Hess + ***************************************************************************/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#define dev_fmt pr_fmt +#define DRV_NAME KBUILD_MODNAME + +#include "cec.h" +#include +#include +#include +#include +#include +#include + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("GPIB driver for CEC PCI and PCMCIA boards"); + +/* + * GPIB interrupt service routines + */ + +static irqreturn_t cec_interrupt(int irq, void *arg) +{ + struct gpib_board *board = arg; + struct cec_priv *priv = board->private_data; + unsigned long flags; + irqreturn_t retval; + + spin_lock_irqsave(&board->spinlock, flags); + retval = nec7210_interrupt(board, &priv->nec7210_priv); + spin_unlock_irqrestore(&board->spinlock, flags); + return retval; +} + +#define CEC_VENDOR_ID 0x12fc +#define CEC_DEV_ID 0x5cec +#define CEC_SUBID 0x9050 + +static int cec_pci_attach(struct gpib_board *board, const struct gpib_board_config *config); + +static void cec_pci_detach(struct gpib_board *board); + +// wrappers for interface functions +static int cec_read(struct gpib_board *board, u8 *buffer, size_t length, int *end, + size_t *bytes_read) +{ + struct cec_priv *priv = board->private_data; + + return nec7210_read(board, &priv->nec7210_priv, buffer, length, end, bytes_read); +} + +static int cec_write(struct gpib_board *board, u8 *buffer, size_t length, int send_eoi, + size_t *bytes_written) +{ + struct cec_priv *priv = board->private_data; + + return nec7210_write(board, &priv->nec7210_priv, buffer, length, send_eoi, bytes_written); +} + +static int cec_command(struct gpib_board *board, u8 *buffer, + size_t length, size_t *bytes_written) +{ + struct cec_priv *priv = board->private_data; + + return nec7210_command(board, &priv->nec7210_priv, buffer, length, bytes_written); +} + +static int cec_take_control(struct gpib_board *board, int synchronous) +{ + struct cec_priv *priv = board->private_data; + + return nec7210_take_control(board, &priv->nec7210_priv, synchronous); +} + +static int cec_go_to_standby(struct gpib_board *board) +{ + struct cec_priv *priv = board->private_data; + + return nec7210_go_to_standby(board, &priv->nec7210_priv); +} + +static int cec_request_system_control(struct gpib_board *board, int request_control) +{ + struct cec_priv *priv = board->private_data; + + return nec7210_request_system_control(board, &priv->nec7210_priv, request_control); +} + +static void cec_interface_clear(struct gpib_board *board, int assert) +{ + struct cec_priv *priv = board->private_data; + + nec7210_interface_clear(board, &priv->nec7210_priv, assert); +} + +static void cec_remote_enable(struct gpib_board *board, int enable) +{ + struct cec_priv *priv = board->private_data; + + nec7210_remote_enable(board, &priv->nec7210_priv, enable); +} + +static int cec_enable_eos(struct gpib_board *board, u8 eos_byte, int compare_8_bits) +{ + struct cec_priv *priv = board->private_data; + + return nec7210_enable_eos(board, &priv->nec7210_priv, eos_byte, compare_8_bits); +} + +static void cec_disable_eos(struct gpib_board *board) +{ + struct cec_priv *priv = board->private_data; + + nec7210_disable_eos(board, &priv->nec7210_priv); +} + +static unsigned int cec_update_status(struct gpib_board *board, unsigned int clear_mask) +{ + struct cec_priv *priv = board->private_data; + + return nec7210_update_status(board, &priv->nec7210_priv, clear_mask); +} + +static int cec_primary_address(struct gpib_board *board, unsigned int address) +{ + struct cec_priv *priv = board->private_data; + + return nec7210_primary_address(board, &priv->nec7210_priv, address); +} + +static int cec_secondary_address(struct gpib_board *board, unsigned int address, int enable) +{ + struct cec_priv *priv = board->private_data; + + return nec7210_secondary_address(board, &priv->nec7210_priv, address, enable); +} + +static int cec_parallel_poll(struct gpib_board *board, u8 *result) +{ + struct cec_priv *priv = board->private_data; + + return nec7210_parallel_poll(board, &priv->nec7210_priv, result); +} + +static void cec_parallel_poll_configure(struct gpib_board *board, u8 config) +{ + struct cec_priv *priv = board->private_data; + + nec7210_parallel_poll_configure(board, &priv->nec7210_priv, config); +} + +static void cec_parallel_poll_response(struct gpib_board *board, int ist) +{ + struct cec_priv *priv = board->private_data; + + nec7210_parallel_poll_response(board, &priv->nec7210_priv, ist); +} + +static void cec_serial_poll_response(struct gpib_board *board, u8 status) +{ + struct cec_priv *priv = board->private_data; + + nec7210_serial_poll_response(board, &priv->nec7210_priv, status); +} + +static u8 cec_serial_poll_status(struct gpib_board *board) +{ + struct cec_priv *priv = board->private_data; + + return nec7210_serial_poll_status(board, &priv->nec7210_priv); +} + +static int cec_t1_delay(struct gpib_board *board, unsigned int nano_sec) +{ + struct cec_priv *priv = board->private_data; + + return nec7210_t1_delay(board, &priv->nec7210_priv, nano_sec); +} + +static void cec_return_to_local(struct gpib_board *board) +{ + struct cec_priv *priv = board->private_data; + + nec7210_return_to_local(board, &priv->nec7210_priv); +} + +static struct gpib_interface cec_pci_interface = { + .name = "cec_pci", + .attach = cec_pci_attach, + .detach = cec_pci_detach, + .read = cec_read, + .write = cec_write, + .command = cec_command, + .take_control = cec_take_control, + .go_to_standby = cec_go_to_standby, + .request_system_control = cec_request_system_control, + .interface_clear = cec_interface_clear, + .remote_enable = cec_remote_enable, + .enable_eos = cec_enable_eos, + .disable_eos = cec_disable_eos, + .parallel_poll = cec_parallel_poll, + .parallel_poll_configure = cec_parallel_poll_configure, + .parallel_poll_response = cec_parallel_poll_response, + .local_parallel_poll_mode = NULL, // XXX + .line_status = NULL, // XXX + .update_status = cec_update_status, + .primary_address = cec_primary_address, + .secondary_address = cec_secondary_address, + .serial_poll_response = cec_serial_poll_response, + .serial_poll_status = cec_serial_poll_status, + .t1_delay = cec_t1_delay, + .return_to_local = cec_return_to_local, +}; + +static int cec_allocate_private(struct gpib_board *board) +{ + struct cec_priv *priv; + + board->private_data = kmalloc(sizeof(struct cec_priv), GFP_KERNEL); + if (!board->private_data) + return -1; + priv = board->private_data; + memset(priv, 0, sizeof(struct cec_priv)); + init_nec7210_private(&priv->nec7210_priv); + return 0; +} + +static void cec_free_private(struct gpib_board *board) +{ + kfree(board->private_data); + board->private_data = NULL; +} + +static int cec_generic_attach(struct gpib_board *board) +{ + struct cec_priv *cec_priv; + struct nec7210_priv *nec_priv; + + board->status = 0; + + if (cec_allocate_private(board)) + return -ENOMEM; + cec_priv = board->private_data; + nec_priv = &cec_priv->nec7210_priv; + nec_priv->read_byte = nec7210_ioport_read_byte; + nec_priv->write_byte = nec7210_ioport_write_byte; + nec_priv->offset = cec_reg_offset; + nec_priv->type = NEC7210; // guess + return 0; +} + +static void cec_init(struct cec_priv *cec_priv, const struct gpib_board *board) +{ + struct nec7210_priv *nec_priv = &cec_priv->nec7210_priv; + + nec7210_board_reset(nec_priv, board); + + /* set internal counter register for 8 MHz input clock */ + write_byte(nec_priv, ICR | 8, AUXMR); + + nec7210_board_online(nec_priv, board); +} + +static int cec_pci_attach(struct gpib_board *board, const struct gpib_board_config *config) +{ + struct cec_priv *cec_priv; + struct nec7210_priv *nec_priv; + int isr_flags = 0; + int retval; + + retval = cec_generic_attach(board); + if (retval) + return retval; + + cec_priv = board->private_data; + nec_priv = &cec_priv->nec7210_priv; + + // find board + cec_priv->pci_device = NULL; + while ((cec_priv->pci_device = + gpib_pci_get_device(config, CEC_VENDOR_ID, + CEC_DEV_ID, cec_priv->pci_device))) { + // check for board with plx9050 controller + if (cec_priv->pci_device->subsystem_device == CEC_SUBID) + break; + } + if (!cec_priv->pci_device) { + dev_err(board->gpib_dev, "no cec PCI board found\n"); + return -ENODEV; + } + + if (pci_enable_device(cec_priv->pci_device)) { + dev_err(board->gpib_dev, "error enabling pci device\n"); + return -EIO; + } + + if (pci_request_regions(cec_priv->pci_device, "cec-gpib")) + return -EBUSY; + + cec_priv->plx_iobase = pci_resource_start(cec_priv->pci_device, 1); + nec_priv->iobase = pci_resource_start(cec_priv->pci_device, 3); + + isr_flags |= IRQF_SHARED; + if (request_irq(cec_priv->pci_device->irq, cec_interrupt, isr_flags, DRV_NAME, board)) { + dev_err(board->gpib_dev, "failed to obtain IRQ %d\n", cec_priv->pci_device->irq); + return -EBUSY; + } + cec_priv->irq = cec_priv->pci_device->irq; + if (gpib_request_pseudo_irq(board, cec_interrupt)) { + dev_err(board->gpib_dev, "failed to allocate pseudo irq\n"); + return -1; + } + cec_init(cec_priv, board); + + // enable interrupts on plx chip + outl(PLX9050_LINTR1_EN_BIT | PLX9050_LINTR1_POLARITY_BIT | PLX9050_PCI_INTR_EN_BIT, + cec_priv->plx_iobase + PLX9050_INTCSR_REG); + + return 0; +} + +static void cec_pci_detach(struct gpib_board *board) +{ + struct cec_priv *cec_priv = board->private_data; + struct nec7210_priv *nec_priv; + + if (cec_priv) { + nec_priv = &cec_priv->nec7210_priv; + gpib_free_pseudo_irq(board); + if (cec_priv->irq) { + // disable plx9050 interrupts + outl(0, cec_priv->plx_iobase + PLX9050_INTCSR_REG); + free_irq(cec_priv->irq, board); + } + if (nec_priv->iobase) { + nec7210_board_reset(nec_priv, board); + pci_release_regions(cec_priv->pci_device); + } + if (cec_priv->pci_device) + pci_dev_put(cec_priv->pci_device); + } + cec_free_private(board); +} + +static int cec_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + return 0; +} + +static const struct pci_device_id cec_pci_table[] = { + {CEC_VENDOR_ID, CEC_DEV_ID, PCI_ANY_ID, CEC_SUBID, 0, 0, 0 }, + {0} +}; +MODULE_DEVICE_TABLE(pci, cec_pci_table); + +static struct pci_driver cec_pci_driver = { + .name = DRV_NAME, + .id_table = cec_pci_table, + .probe = &cec_pci_probe +}; + +static int __init cec_init_module(void) +{ + int result; + + result = pci_register_driver(&cec_pci_driver); + if (result) { + pr_err("pci_register_driver failed: error = %d\n", result); + return result; + } + + result = gpib_register_driver(&cec_pci_interface, THIS_MODULE); + if (result) { + pr_err("gpib_register_driver failed: error = %d\n", result); + return result; + } + + return 0; +} + +static void cec_exit_module(void) +{ + gpib_unregister_driver(&cec_pci_interface); + + pci_unregister_driver(&cec_pci_driver); +} + +module_init(cec_init_module); +module_exit(cec_exit_module); diff --git a/drivers/gpib/common/Makefile b/drivers/gpib/common/Makefile new file mode 100644 index 000000000000..460586edb574 --- /dev/null +++ b/drivers/gpib/common/Makefile @@ -0,0 +1,6 @@ + +obj-$(CONFIG_GPIB_COMMON) += gpib_common.o + +gpib_common-objs := gpib_os.o iblib.o + + diff --git a/drivers/gpib/common/gpib_os.c b/drivers/gpib/common/gpib_os.c new file mode 100644 index 000000000000..9dbbac8b8436 --- /dev/null +++ b/drivers/gpib/common/gpib_os.c @@ -0,0 +1,2271 @@ +// SPDX-License-Identifier: GPL-2.0 + +/*************************************************************************** + * copyright : (C) 2001, 2004 by Frank Mori Hess + *************************************************************************** + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#define dev_fmt pr_fmt + +#include "ibsys.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("GPIB base support"); +MODULE_ALIAS_CHARDEV_MAJOR(GPIB_CODE); + +static int board_type_ioctl(struct gpib_file_private *file_priv, + struct gpib_board *board, unsigned long arg); +static int read_ioctl(struct gpib_file_private *file_priv, struct gpib_board *board, + unsigned long arg); +static int write_ioctl(struct gpib_file_private *file_priv, struct gpib_board *board, + unsigned long arg); +static int command_ioctl(struct gpib_file_private *file_priv, struct gpib_board *board, + unsigned long arg); +static int open_dev_ioctl(struct file *filep, struct gpib_board *board, unsigned long arg); +static int close_dev_ioctl(struct file *filep, struct gpib_board *board, unsigned long arg); +static int serial_poll_ioctl(struct gpib_board *board, unsigned long arg); +static int wait_ioctl(struct gpib_file_private *file_priv, + struct gpib_board *board, unsigned long arg); +static int parallel_poll_ioctl(struct gpib_board *board, unsigned long arg); +static int online_ioctl(struct gpib_board *board, unsigned long arg); +static int remote_enable_ioctl(struct gpib_board *board, unsigned long arg); +static int take_control_ioctl(struct gpib_board *board, unsigned long arg); +static int line_status_ioctl(struct gpib_board *board, unsigned long arg); +static int pad_ioctl(struct gpib_board *board, struct gpib_file_private *file_priv, + unsigned long arg); +static int sad_ioctl(struct gpib_board *board, struct gpib_file_private *file_priv, + unsigned long arg); +static int eos_ioctl(struct gpib_board *board, unsigned long arg); +static int request_service_ioctl(struct gpib_board *board, unsigned long arg); +static int request_service2_ioctl(struct gpib_board *board, unsigned long arg); +static int iobase_ioctl(struct gpib_board_config *config, unsigned long arg); +static int irq_ioctl(struct gpib_board_config *config, unsigned long arg); +static int dma_ioctl(struct gpib_board_config *config, unsigned long arg); +static int autospoll_ioctl(struct gpib_board *board, struct gpib_file_private *file_priv, + unsigned long arg); +static int mutex_ioctl(struct gpib_board *board, struct gpib_file_private *file_priv, + unsigned long arg); +static int timeout_ioctl(struct gpib_board *board, unsigned long arg); +static int status_bytes_ioctl(struct gpib_board *board, unsigned long arg); +static int board_info_ioctl(const struct gpib_board *board, unsigned long arg); +static int ppc_ioctl(struct gpib_board *board, unsigned long arg); +static int set_local_ppoll_mode_ioctl(struct gpib_board *board, unsigned long arg); +static int get_local_ppoll_mode_ioctl(struct gpib_board *board, unsigned long arg); +static int query_board_rsv_ioctl(struct gpib_board *board, unsigned long arg); +static int interface_clear_ioctl(struct gpib_board *board, unsigned long arg); +static int select_pci_ioctl(struct gpib_board_config *config, unsigned long arg); +static int select_device_path_ioctl(struct gpib_board_config *config, unsigned long arg); +static int event_ioctl(struct gpib_board *board, unsigned long arg); +static int request_system_control_ioctl(struct gpib_board *board, unsigned long arg); +static int t1_delay_ioctl(struct gpib_board *board, unsigned long arg); + +static int cleanup_open_devices(struct gpib_file_private *file_priv, struct gpib_board *board); + +static int pop_gpib_event_nolock(struct gpib_board *board, + struct gpib_event_queue *queue, short *event_type); + +/* + * Timer functions + */ + +/* Watchdog timeout routine */ + +static void watchdog_timeout(struct timer_list *t) +{ + struct gpib_board *board = timer_container_of(board, t, timer); + + set_bit(TIMO_NUM, &board->status); + wake_up_interruptible(&board->wait); +} + +/* install timer interrupt handler */ +void os_start_timer(struct gpib_board *board, unsigned int usec_timeout) +/* Starts the timeout task */ +{ + if (timer_pending(&board->timer)) { + dev_err(board->gpib_dev, "bug! timer already running?\n"); + return; + } + clear_bit(TIMO_NUM, &board->status); + + if (usec_timeout > 0) { + board->timer.function = watchdog_timeout; + /* set number of ticks */ + mod_timer(&board->timer, jiffies + usec_to_jiffies(usec_timeout)); + } +} + +void os_remove_timer(struct gpib_board *board) +/* Removes the timeout task */ +{ + if (timer_pending(&board->timer)) + timer_delete_sync(&board->timer); +} + +int io_timed_out(struct gpib_board *board) +{ + if (test_bit(TIMO_NUM, &board->status)) + return 1; + return 0; +} + +/* + * this is a function instead of a constant because of Suse + * defining HZ to be a function call to get_hz() + */ +static inline int pseudo_irq_period(void) +{ + return (HZ + 99) / 100; +} + +static void pseudo_irq_handler(struct timer_list *t) +{ + struct gpib_pseudo_irq *pseudo_irq = timer_container_of(pseudo_irq, t, + timer); + + if (pseudo_irq->handler) + pseudo_irq->handler(0, pseudo_irq->board); + else + pr_err("gpib: bug! pseudo_irq.handler is NULL\n"); + + if (atomic_read(&pseudo_irq->active)) + mod_timer(&pseudo_irq->timer, jiffies + pseudo_irq_period()); +} + +int gpib_request_pseudo_irq(struct gpib_board *board, irqreturn_t (*handler)(int, void *)) +{ + if (timer_pending(&board->pseudo_irq.timer) || board->pseudo_irq.handler) { + dev_err(board->gpib_dev, "only one pseudo interrupt per board allowed\n"); + return -1; + } + + board->pseudo_irq.handler = handler; + board->pseudo_irq.timer.function = pseudo_irq_handler; + board->pseudo_irq.board = board; + + atomic_set(&board->pseudo_irq.active, 1); + + mod_timer(&board->pseudo_irq.timer, jiffies + pseudo_irq_period()); + + return 0; +} +EXPORT_SYMBOL(gpib_request_pseudo_irq); + +void gpib_free_pseudo_irq(struct gpib_board *board) +{ + atomic_set(&board->pseudo_irq.active, 0); + + timer_delete_sync(&board->pseudo_irq.timer); + board->pseudo_irq.handler = NULL; +} +EXPORT_SYMBOL(gpib_free_pseudo_irq); + +static const unsigned int serial_timeout = 1000000; + +unsigned int num_status_bytes(const struct gpib_status_queue *dev) +{ + if (!dev) + return 0; + return dev->num_status_bytes; +} + +// push status byte onto back of status byte fifo +int push_status_byte(struct gpib_board *board, struct gpib_status_queue *device, u8 poll_byte) +{ + struct list_head *head = &device->status_bytes; + struct gpib_status_byte *status; + static const unsigned int max_num_status_bytes = 1024; + int retval; + + if (num_status_bytes(device) >= max_num_status_bytes) { + u8 lost_byte; + + device->dropped_byte = 1; + retval = pop_status_byte(board, device, &lost_byte); + if (retval < 0) + return retval; + } + + status = kmalloc(sizeof(*status), GFP_KERNEL); + if (!status) + return -ENOMEM; + + INIT_LIST_HEAD(&status->list); + status->poll_byte = poll_byte; + + list_add_tail(&status->list, head); + + device->num_status_bytes++; + + dev_dbg(board->gpib_dev, "pushed status byte 0x%x, %i in queue\n", + (int)poll_byte, num_status_bytes(device)); + + return 0; +} + +// pop status byte from front of status byte fifo +int pop_status_byte(struct gpib_board *board, struct gpib_status_queue *device, u8 *poll_byte) +{ + struct list_head *head = &device->status_bytes; + struct list_head *front = head->next; + struct gpib_status_byte *status; + + if (num_status_bytes(device) == 0) + return -EIO; + + if (front == head) + return -EIO; + + if (device->dropped_byte) { + device->dropped_byte = 0; + return -EPIPE; + } + + status = list_entry(front, struct gpib_status_byte, list); + *poll_byte = status->poll_byte; + + list_del(front); + kfree(status); + + device->num_status_bytes--; + + dev_dbg(board->gpib_dev, "popped status byte 0x%x, %i in queue\n", + (int)*poll_byte, num_status_bytes(device)); + + return 0; +} + +struct gpib_status_queue *get_gpib_status_queue(struct gpib_board *board, unsigned int pad, int sad) +{ + struct gpib_status_queue *device; + struct list_head *list_ptr; + const struct list_head *head = &board->device_list; + + for (list_ptr = head->next; list_ptr != head; list_ptr = list_ptr->next) { + device = list_entry(list_ptr, struct gpib_status_queue, list); + if (gpib_address_equal(device->pad, device->sad, pad, sad)) + return device; + } + + return NULL; +} + +int get_serial_poll_byte(struct gpib_board *board, unsigned int pad, int sad, + unsigned int usec_timeout, u8 *poll_byte) +{ + struct gpib_status_queue *device; + + device = get_gpib_status_queue(board, pad, sad); + if (num_status_bytes(device)) + return pop_status_byte(board, device, poll_byte); + else + return dvrsp(board, pad, sad, usec_timeout, poll_byte); +} + +int autopoll_all_devices(struct gpib_board *board) +{ + int retval; + + if (mutex_lock_interruptible(&board->user_mutex)) + return -ERESTARTSYS; + if (mutex_lock_interruptible(&board->big_gpib_mutex)) { + mutex_unlock(&board->user_mutex); + return -ERESTARTSYS; + } + + dev_dbg(board->gpib_dev, "autopoll has board lock\n"); + + retval = serial_poll_all(board, serial_timeout); + if (retval < 0) { + mutex_unlock(&board->big_gpib_mutex); + mutex_unlock(&board->user_mutex); + return retval; + } + + dev_dbg(board->gpib_dev, "complete\n"); + /* + * need to wake wait queue in case someone is + * waiting on RQS + */ + wake_up_interruptible(&board->wait); + mutex_unlock(&board->big_gpib_mutex); + mutex_unlock(&board->user_mutex); + + return retval; +} + +static int setup_serial_poll(struct gpib_board *board, unsigned int usec_timeout) +{ + u8 cmd_string[8]; + int i; + size_t bytes_written; + int ret; + + os_start_timer(board, usec_timeout); + ret = ibcac(board, 1, 1); + if (ret < 0) { + os_remove_timer(board); + return ret; + } + + i = 0; + cmd_string[i++] = UNL; + cmd_string[i++] = MLA(board->pad); /* controller's listen address */ + if (board->sad >= 0) + cmd_string[i++] = MSA(board->sad); + cmd_string[i++] = SPE; // serial poll enable + + ret = board->interface->command(board, cmd_string, i, &bytes_written); + if (ret < 0 || bytes_written < i) { + dev_dbg(board->gpib_dev, "failed to setup serial poll\n"); + os_remove_timer(board); + return -EIO; + } + os_remove_timer(board); + + return 0; +} + +static int read_serial_poll_byte(struct gpib_board *board, unsigned int pad, + int sad, unsigned int usec_timeout, u8 *result) +{ + u8 cmd_string[8]; + int end_flag; + int ret; + int i; + size_t nbytes; + + dev_dbg(board->gpib_dev, "entering pad=%i sad=%i\n", pad, sad); + + os_start_timer(board, usec_timeout); + ret = ibcac(board, 1, 1); + if (ret < 0) { + os_remove_timer(board); + return ret; + } + + i = 0; + // send talk address + cmd_string[i++] = MTA(pad); + if (sad >= 0) + cmd_string[i++] = MSA(sad); + + ret = board->interface->command(board, cmd_string, i, &nbytes); + if (ret < 0 || nbytes < i) { + dev_err(board->gpib_dev, "failed to setup serial poll\n"); + os_remove_timer(board); + return -EIO; + } + + ibgts(board); + + // read poll result + ret = board->interface->read(board, result, 1, &end_flag, &nbytes); + if (ret < 0 || nbytes < 1) { + dev_err(board->gpib_dev, "serial poll failed\n"); + os_remove_timer(board); + return -EIO; + } + os_remove_timer(board); + + return 0; +} + +static int cleanup_serial_poll(struct gpib_board *board, unsigned int usec_timeout) +{ + u8 cmd_string[8]; + int ret; + size_t bytes_written; + + os_start_timer(board, usec_timeout); + ret = ibcac(board, 1, 1); + if (ret < 0) { + os_remove_timer(board); + return ret; + } + + cmd_string[0] = SPD; /* disable serial poll bytes */ + cmd_string[1] = UNT; + ret = board->interface->command(board, cmd_string, 2, &bytes_written); + if (ret < 0 || bytes_written < 2) { + dev_err(board->gpib_dev, "failed to disable serial poll\n"); + os_remove_timer(board); + return -EIO; + } + os_remove_timer(board); + + return 0; +} + +static int serial_poll_single(struct gpib_board *board, unsigned int pad, int sad, + unsigned int usec_timeout, u8 *result) +{ + int retval, cleanup_retval; + + retval = setup_serial_poll(board, usec_timeout); + if (retval < 0) + return retval; + retval = read_serial_poll_byte(board, pad, sad, usec_timeout, result); + cleanup_retval = cleanup_serial_poll(board, usec_timeout); + if (retval < 0) + return retval; + if (cleanup_retval < 0) + return retval; + + return 0; +} + +int serial_poll_all(struct gpib_board *board, unsigned int usec_timeout) +{ + int retval = 0; + struct list_head *cur; + const struct list_head *head = NULL; + struct gpib_status_queue *device; + u8 result; + unsigned int num_bytes = 0; + + head = &board->device_list; + if (head->next == head) + return 0; + + retval = setup_serial_poll(board, usec_timeout); + if (retval < 0) + return retval; + + for (cur = head->next; cur != head; cur = cur->next) { + device = list_entry(cur, struct gpib_status_queue, list); + retval = read_serial_poll_byte(board, + device->pad, device->sad, usec_timeout, &result); + if (retval < 0) + continue; + if (result & request_service_bit) { + retval = push_status_byte(board, device, result); + if (retval < 0) + continue; + num_bytes++; + } + } + + retval = cleanup_serial_poll(board, usec_timeout); + if (retval < 0) + return retval; + + return num_bytes; +} + +/* + * DVRSP + * This function performs a serial poll of the device with primary + * address pad and secondary address sad. If the device has no + * secondary address, pass a negative number in for this argument. At the + * end of a successful serial poll the response is returned in result. + * SPD and UNT are sent at the completion of the poll. + */ + +int dvrsp(struct gpib_board *board, unsigned int pad, int sad, + unsigned int usec_timeout, u8 *result) +{ + int status = ibstatus(board); + int retval; + + if ((status & CIC) == 0) { + dev_err(board->gpib_dev, "not CIC during serial poll\n"); + return -1; + } + + if (pad > MAX_GPIB_PRIMARY_ADDRESS || sad > MAX_GPIB_SECONDARY_ADDRESS || sad < -1) { + dev_err(board->gpib_dev, "bad address for serial poll"); + return -1; + } + + retval = serial_poll_single(board, pad, sad, usec_timeout, result); + if (io_timed_out(board)) + retval = -ETIMEDOUT; + + return retval; +} + +static struct gpib_descriptor *handle_to_descriptor(const struct gpib_file_private *file_priv, + int handle) +{ + if (handle < 0 || handle >= GPIB_MAX_NUM_DESCRIPTORS) { + pr_err("gpib: invalid handle %i\n", handle); + return NULL; + } + + return file_priv->descriptors[handle]; +} + +static int init_gpib_file_private(struct gpib_file_private *priv) +{ + memset(priv, 0, sizeof(*priv)); + atomic_set(&priv->holding_mutex, 0); + priv->descriptors[0] = kmalloc(sizeof(struct gpib_descriptor), GFP_KERNEL); + if (!priv->descriptors[0]) { + pr_err("gpib: failed to allocate default board descriptor\n"); + return -ENOMEM; + } + init_gpib_descriptor(priv->descriptors[0]); + priv->descriptors[0]->is_board = 1; + mutex_init(&priv->descriptors_mutex); + return 0; +} + +int ibopen(struct inode *inode, struct file *filep) +{ + unsigned int minor = iminor(inode); + struct gpib_board *board; + struct gpib_file_private *priv; + + if (minor >= GPIB_MAX_NUM_BOARDS) { + pr_err("gpib: invalid minor number of device file\n"); + return -ENXIO; + } + + board = &board_array[minor]; + + filep->private_data = kmalloc(sizeof(struct gpib_file_private), GFP_KERNEL); + if (!filep->private_data) + return -ENOMEM; + + priv = filep->private_data; + init_gpib_file_private((struct gpib_file_private *)filep->private_data); + + if (board->use_count == 0) { + int retval; + + retval = request_module("gpib%i", minor); + if (retval) + dev_dbg(board->gpib_dev, "request module returned %i\n", retval); + } + if (board->interface) { + if (!try_module_get(board->provider_module)) { + dev_err(board->gpib_dev, "try_module_get() failed\n"); + return -EIO; + } + board->use_count++; + priv->got_module = 1; + } + return 0; +} + +int ibclose(struct inode *inode, struct file *filep) +{ + unsigned int minor = iminor(inode); + struct gpib_board *board; + struct gpib_file_private *priv = filep->private_data; + struct gpib_descriptor *desc; + + if (minor >= GPIB_MAX_NUM_BOARDS) { + pr_err("gpib: invalid minor number of device file\n"); + return -ENODEV; + } + + board = &board_array[minor]; + + if (priv) { + desc = handle_to_descriptor(priv, 0); + if (desc) { + if (desc->autopoll_enabled) { + dev_dbg(board->gpib_dev, "decrementing autospollers\n"); + if (board->autospollers > 0) + board->autospollers--; + else + dev_err(board->gpib_dev, + "Attempt to decrement zero autospollers\n"); + } + } else { + dev_err(board->gpib_dev, "Unexpected null gpib_descriptor\n"); + } + + cleanup_open_devices(priv, board); + + if (atomic_read(&priv->holding_mutex)) + mutex_unlock(&board->user_mutex); + + if (priv->got_module && board->use_count) { + module_put(board->provider_module); + --board->use_count; + } + + kfree(filep->private_data); + filep->private_data = NULL; + } + + return 0; +} + +long ibioctl(struct file *filep, unsigned int cmd, unsigned long arg) +{ + unsigned int minor = iminor(file_inode(filep)); + struct gpib_board *board; + struct gpib_file_private *file_priv = filep->private_data; + long retval = -ENOTTY; + + if (minor >= GPIB_MAX_NUM_BOARDS) { + pr_err("gpib: invalid minor number of device file\n"); + return -ENODEV; + } + board = &board_array[minor]; + + if (mutex_lock_interruptible(&board->big_gpib_mutex)) + return -ERESTARTSYS; + + dev_dbg(board->gpib_dev, "ioctl %d, interface=%s, use=%d, onl=%d\n", + cmd & 0xff, + board->interface ? board->interface->name : "", + board->use_count, + board->online); + + switch (cmd) { + case CFCBOARDTYPE: + retval = board_type_ioctl(file_priv, board, arg); + goto done; + case IBONL: + retval = online_ioctl(board, arg); + goto done; + default: + break; + } + if (!board->interface) { + dev_err(board->gpib_dev, "no gpib board configured\n"); + retval = -ENODEV; + goto done; + } + if (file_priv->got_module == 0) { + if (!try_module_get(board->provider_module)) { + dev_err(board->gpib_dev, "try_module_get() failed\n"); + retval = -EIO; + goto done; + } + file_priv->got_module = 1; + board->use_count++; + } + switch (cmd) { + case CFCBASE: + retval = iobase_ioctl(&board->config, arg); + goto done; + case CFCIRQ: + retval = irq_ioctl(&board->config, arg); + goto done; + case CFCDMA: + retval = dma_ioctl(&board->config, arg); + goto done; + case IBAUTOSPOLL: + retval = autospoll_ioctl(board, file_priv, arg); + goto done; + case IBBOARD_INFO: + retval = board_info_ioctl(board, arg); + goto done; + case IBMUTEX: + /* + * Need to unlock board->big_gpib_mutex before potentially locking board->user_mutex + * to maintain consistent locking order + */ + mutex_unlock(&board->big_gpib_mutex); + return mutex_ioctl(board, file_priv, arg); + case IBPAD: + retval = pad_ioctl(board, file_priv, arg); + goto done; + case IBSAD: + retval = sad_ioctl(board, file_priv, arg); + goto done; + case IBSELECT_PCI: + retval = select_pci_ioctl(&board->config, arg); + goto done; + case IBSELECT_DEVICE_PATH: + retval = select_device_path_ioctl(&board->config, arg); + goto done; + default: + break; + } + + if (!board->online) { + retval = -EINVAL; + goto done; + } + + switch (cmd) { + case IBEVENT: + retval = event_ioctl(board, arg); + goto done; + case IBCLOSEDEV: + retval = close_dev_ioctl(filep, board, arg); + goto done; + case IBOPENDEV: + retval = open_dev_ioctl(filep, board, arg); + goto done; + case IBSPOLL_BYTES: + retval = status_bytes_ioctl(board, arg); + goto done; + case IBWAIT: + retval = wait_ioctl(file_priv, board, arg); + if (retval == -ERESTARTSYS) + return retval; + goto done; + case IBLINES: + retval = line_status_ioctl(board, arg); + goto done; + case IBLOC: + board->interface->return_to_local(board); + retval = 0; + goto done; + default: + break; + } + + spin_lock(&board->locking_pid_spinlock); + if (current->pid != board->locking_pid) { + spin_unlock(&board->locking_pid_spinlock); + retval = -EPERM; + goto done; + } + spin_unlock(&board->locking_pid_spinlock); + + switch (cmd) { + case IB_T1_DELAY: + retval = t1_delay_ioctl(board, arg); + goto done; + case IBCAC: + retval = take_control_ioctl(board, arg); + goto done; + case IBCMD: + /* + * IO ioctls can take a long time, we need to unlock board->big_gpib_mutex + * before we call them. + */ + mutex_unlock(&board->big_gpib_mutex); + return command_ioctl(file_priv, board, arg); + case IBEOS: + retval = eos_ioctl(board, arg); + goto done; + case IBGTS: + retval = ibgts(board); + goto done; + case IBPPC: + retval = ppc_ioctl(board, arg); + goto done; + case IBPP2_SET: + retval = set_local_ppoll_mode_ioctl(board, arg); + goto done; + case IBPP2_GET: + retval = get_local_ppoll_mode_ioctl(board, arg); + goto done; + case IBQUERY_BOARD_RSV: + retval = query_board_rsv_ioctl(board, arg); + goto done; + case IBRD: + /* + * IO ioctls can take a long time, we need to unlock board->big_gpib_mutex + * before we call them. + */ + mutex_unlock(&board->big_gpib_mutex); + return read_ioctl(file_priv, board, arg); + case IBRPP: + retval = parallel_poll_ioctl(board, arg); + goto done; + case IBRSC: + retval = request_system_control_ioctl(board, arg); + goto done; + case IBRSP: + retval = serial_poll_ioctl(board, arg); + goto done; + case IBRSV: + retval = request_service_ioctl(board, arg); + goto done; + case IBRSV2: + retval = request_service2_ioctl(board, arg); + goto done; + case IBSIC: + retval = interface_clear_ioctl(board, arg); + goto done; + case IBSRE: + retval = remote_enable_ioctl(board, arg); + goto done; + case IBTMO: + retval = timeout_ioctl(board, arg); + goto done; + case IBWRT: + /* + * IO ioctls can take a long time, we need to unlock board->big_gpib_mutex + * before we call them. + */ + mutex_unlock(&board->big_gpib_mutex); + return write_ioctl(file_priv, board, arg); + default: + retval = -ENOTTY; + goto done; + } + +done: + mutex_unlock(&board->big_gpib_mutex); + dev_dbg(board->gpib_dev, "ioctl done status = 0x%lx\n", board->status); + return retval; +} + +static int board_type_ioctl(struct gpib_file_private *file_priv, + struct gpib_board *board, unsigned long arg) +{ + struct list_head *list_ptr; + struct gpib_board_type_ioctl cmd; + int retval; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + if (board->online) + return -EBUSY; + + retval = copy_from_user(&cmd, (void __user *)arg, + sizeof(struct gpib_board_type_ioctl)); + if (retval) + return -EFAULT; + + for (list_ptr = registered_drivers.next; list_ptr != ®istered_drivers; + list_ptr = list_ptr->next) { + struct gpib_interface_list *entry; + + entry = list_entry(list_ptr, struct gpib_interface_list, list); + if (strcmp(entry->interface->name, cmd.name) == 0) { + int i; + int had_module = file_priv->got_module; + + if (board->use_count) { + for (i = 0; i < board->use_count; ++i) + module_put(board->provider_module); + board->interface = NULL; + file_priv->got_module = 0; + } + board->interface = entry->interface; + board->provider_module = entry->module; + for (i = 0; i < board->use_count; ++i) { + if (!try_module_get(entry->module)) { + board->use_count = i; + return -EIO; + } + } + if (had_module == 0) { + if (!try_module_get(entry->module)) + return -EIO; + ++board->use_count; + } + file_priv->got_module = 1; + return 0; + } + } + + return -EINVAL; +} + +static int read_ioctl(struct gpib_file_private *file_priv, struct gpib_board *board, + unsigned long arg) +{ + struct gpib_read_write_ioctl read_cmd; + u8 __user *userbuf; + unsigned long remain; + int end_flag = 0; + int retval; + ssize_t read_ret = 0; + struct gpib_descriptor *desc; + size_t nbytes; + + retval = copy_from_user(&read_cmd, (void __user *)arg, sizeof(read_cmd)); + if (retval) + return -EFAULT; + + if (read_cmd.completed_transfer_count > read_cmd.requested_transfer_count) + return -EINVAL; + + desc = handle_to_descriptor(file_priv, read_cmd.handle); + if (!desc) + return -EINVAL; + + if (WARN_ON_ONCE(sizeof(userbuf) > sizeof(read_cmd.buffer_ptr))) + return -EFAULT; + + userbuf = (u8 __user *)(unsigned long)read_cmd.buffer_ptr; + userbuf += read_cmd.completed_transfer_count; + + remain = read_cmd.requested_transfer_count - read_cmd.completed_transfer_count; + + /* Check write access to buffer */ + if (!access_ok(userbuf, remain)) + return -EFAULT; + + atomic_set(&desc->io_in_progress, 1); + + /* Read buffer loads till we fill the user supplied buffer */ + while (remain > 0 && end_flag == 0) { + nbytes = 0; + read_ret = ibrd(board, board->buffer, (board->buffer_length < remain) ? + board->buffer_length : remain, &end_flag, &nbytes); + if (nbytes == 0) + break; + retval = copy_to_user(userbuf, board->buffer, nbytes); + if (retval) { + retval = -EFAULT; + break; + } + remain -= nbytes; + userbuf += nbytes; + if (read_ret < 0) + break; + } + read_cmd.completed_transfer_count = read_cmd.requested_transfer_count - remain; + read_cmd.end = end_flag; + /* + * suppress errors (for example due to timeout or interruption by device clear) + * if all bytes got sent. This prevents races that can occur in the various drivers + * if a device receives a device clear immediately after a transfer completes and + * the driver code wasn't careful enough to handle that case. + */ + if (remain == 0 || end_flag) + read_ret = 0; + if (retval == 0) + retval = copy_to_user((void __user *)arg, &read_cmd, sizeof(read_cmd)); + + atomic_set(&desc->io_in_progress, 0); + + wake_up_interruptible(&board->wait); + if (retval) + return -EFAULT; + + return read_ret; +} + +static int command_ioctl(struct gpib_file_private *file_priv, + struct gpib_board *board, unsigned long arg) +{ + struct gpib_read_write_ioctl cmd; + u8 __user *userbuf; + unsigned long remain; + int retval; + int fault = 0; + struct gpib_descriptor *desc; + size_t bytes_written; + int no_clear_io_in_prog; + + retval = copy_from_user(&cmd, (void __user *)arg, sizeof(cmd)); + if (retval) + return -EFAULT; + + if (cmd.completed_transfer_count > cmd.requested_transfer_count) + return -EINVAL; + + desc = handle_to_descriptor(file_priv, cmd.handle); + if (!desc) + return -EINVAL; + + userbuf = (u8 __user *)(unsigned long)cmd.buffer_ptr; + userbuf += cmd.completed_transfer_count; + + no_clear_io_in_prog = cmd.end; + cmd.end = 0; + + remain = cmd.requested_transfer_count - cmd.completed_transfer_count; + + /* Check read access to buffer */ + if (!access_ok(userbuf, remain)) + return -EFAULT; + + /* + * Write buffer loads till we empty the user supplied buffer. + * Call drivers at least once, even if remain is zero, in + * order to allow them to insure previous commands were + * completely finished, in the case of a restarted ioctl. + */ + + atomic_set(&desc->io_in_progress, 1); + + do { + fault = copy_from_user(board->buffer, userbuf, (board->buffer_length < remain) ? + board->buffer_length : remain); + if (fault) { + retval = -EFAULT; + bytes_written = 0; + } else { + retval = ibcmd(board, board->buffer, (board->buffer_length < remain) ? + board->buffer_length : remain, &bytes_written); + } + remain -= bytes_written; + userbuf += bytes_written; + if (retval < 0) { + atomic_set(&desc->io_in_progress, 0); + + wake_up_interruptible(&board->wait); + break; + } + } while (remain > 0); + + cmd.completed_transfer_count = cmd.requested_transfer_count - remain; + + if (fault == 0) + fault = copy_to_user((void __user *)arg, &cmd, sizeof(cmd)); + + /* + * no_clear_io_in_prog (cmd.end) is true when io_in_progress should + * not be set to zero because the cmd in progress is the address setup + * operation for an async read or write. This causes CMPL not to be set + * in general_ibstatus until the async read or write completes. + */ + if (!no_clear_io_in_prog || fault) + atomic_set(&desc->io_in_progress, 0); + + wake_up_interruptible(&board->wait); + if (fault) + return -EFAULT; + + return retval; +} + +static int write_ioctl(struct gpib_file_private *file_priv, struct gpib_board *board, + unsigned long arg) +{ + struct gpib_read_write_ioctl write_cmd; + u8 __user *userbuf; + unsigned long remain; + int retval = 0; + int fault; + struct gpib_descriptor *desc; + + fault = copy_from_user(&write_cmd, (void __user *)arg, sizeof(write_cmd)); + if (fault) + return -EFAULT; + + if (write_cmd.completed_transfer_count > write_cmd.requested_transfer_count) + return -EINVAL; + + desc = handle_to_descriptor(file_priv, write_cmd.handle); + if (!desc) + return -EINVAL; + + userbuf = (u8 __user *)(unsigned long)write_cmd.buffer_ptr; + userbuf += write_cmd.completed_transfer_count; + + remain = write_cmd.requested_transfer_count - write_cmd.completed_transfer_count; + + /* Check read access to buffer */ + if (!access_ok(userbuf, remain)) + return -EFAULT; + + atomic_set(&desc->io_in_progress, 1); + + /* Write buffer loads till we empty the user supplied buffer */ + while (remain > 0) { + int send_eoi; + size_t bytes_written = 0; + + send_eoi = remain <= board->buffer_length && write_cmd.end; + fault = copy_from_user(board->buffer, userbuf, (board->buffer_length < remain) ? + board->buffer_length : remain); + if (fault) { + retval = -EFAULT; + break; + } + retval = ibwrt(board, board->buffer, (board->buffer_length < remain) ? + board->buffer_length : remain, send_eoi, &bytes_written); + remain -= bytes_written; + userbuf += bytes_written; + if (retval < 0) + break; + } + write_cmd.completed_transfer_count = write_cmd.requested_transfer_count - remain; + /* + * suppress errors (for example due to timeout or interruption by device clear) + * if all bytes got sent. This prevents races that can occur in the various drivers + * if a device receives a device clear immediately after a transfer completes and + * the driver code wasn't careful enough to handle that case. + */ + if (remain == 0) + retval = 0; + if (fault == 0) + fault = copy_to_user((void __user *)arg, &write_cmd, sizeof(write_cmd)); + + atomic_set(&desc->io_in_progress, 0); + + wake_up_interruptible(&board->wait); + if (fault) + return -EFAULT; + + return retval; +} + +static int status_bytes_ioctl(struct gpib_board *board, unsigned long arg) +{ + struct gpib_status_queue *device; + struct gpib_spoll_bytes_ioctl cmd; + int retval; + + retval = copy_from_user(&cmd, (void __user *)arg, sizeof(cmd)); + if (retval) + return -EFAULT; + + device = get_gpib_status_queue(board, cmd.pad, cmd.sad); + if (!device) + cmd.num_bytes = 0; + else + cmd.num_bytes = num_status_bytes(device); + + retval = copy_to_user((void __user *)arg, &cmd, sizeof(cmd)); + if (retval) + return -EFAULT; + + return 0; +} + +static int increment_open_device_count(struct gpib_board *board, struct list_head *head, + unsigned int pad, int sad) +{ + struct list_head *list_ptr; + struct gpib_status_queue *device; + + /* + * first see if address has already been opened, then increment + * open count + */ + for (list_ptr = head->next; list_ptr != head; list_ptr = list_ptr->next) { + device = list_entry(list_ptr, struct gpib_status_queue, list); + if (gpib_address_equal(device->pad, device->sad, pad, sad)) { + dev_dbg(board->gpib_dev, "incrementing open count for pad %i, sad %i\n", + device->pad, device->sad); + device->reference_count++; + return 0; + } + } + + /* otherwise we need to allocate a new struct gpib_status_queue */ + device = kmalloc(sizeof(struct gpib_status_queue), GFP_ATOMIC); + if (!device) + return -ENOMEM; + init_gpib_status_queue(device); + device->pad = pad; + device->sad = sad; + device->reference_count = 1; + + list_add(&device->list, head); + + dev_dbg(board->gpib_dev, "opened pad %i, sad %i\n", device->pad, device->sad); + + return 0; +} + +static int subtract_open_device_count(struct gpib_board *board, struct list_head *head, + unsigned int pad, int sad, unsigned int count) +{ + struct gpib_status_queue *device; + struct list_head *list_ptr; + + for (list_ptr = head->next; list_ptr != head; list_ptr = list_ptr->next) { + device = list_entry(list_ptr, struct gpib_status_queue, list); + if (gpib_address_equal(device->pad, device->sad, pad, sad)) { + dev_dbg(board->gpib_dev, "decrementing open count for pad %i, sad %i\n", + device->pad, device->sad); + if (count > device->reference_count) { + dev_err(board->gpib_dev, "bug! in %s()\n", __func__); + return -EINVAL; + } + device->reference_count -= count; + if (device->reference_count == 0) { + dev_dbg(board->gpib_dev, "closing pad %i, sad %i\n", + device->pad, device->sad); + list_del(list_ptr); + kfree(device); + } + return 0; + } + } + dev_err(board->gpib_dev, "bug! tried to close address that was never opened!\n"); + return -EINVAL; +} + +static inline int decrement_open_device_count(struct gpib_board *board, struct list_head *head, + unsigned int pad, int sad) +{ + return subtract_open_device_count(board, head, pad, sad, 1); +} + +static int cleanup_open_devices(struct gpib_file_private *file_priv, struct gpib_board *board) +{ + int retval = 0; + int i; + + for (i = 0; i < GPIB_MAX_NUM_DESCRIPTORS; i++) { + struct gpib_descriptor *desc; + + desc = file_priv->descriptors[i]; + if (!desc) + continue; + + if (desc->is_board == 0) { + retval = decrement_open_device_count(board, &board->device_list, desc->pad, + desc->sad); + if (retval < 0) + return retval; + } + kfree(desc); + file_priv->descriptors[i] = NULL; + } + + return 0; +} + +static int open_dev_ioctl(struct file *filep, struct gpib_board *board, unsigned long arg) +{ + struct gpib_open_dev_ioctl open_dev_cmd; + int retval; + struct gpib_file_private *file_priv = filep->private_data; + int i; + + retval = copy_from_user(&open_dev_cmd, (void __user *)arg, sizeof(open_dev_cmd)); + if (retval) + return -EFAULT; + + if (mutex_lock_interruptible(&file_priv->descriptors_mutex)) + return -ERESTARTSYS; + for (i = 0; i < GPIB_MAX_NUM_DESCRIPTORS; i++) + if (!file_priv->descriptors[i]) + break; + if (i == GPIB_MAX_NUM_DESCRIPTORS) { + mutex_unlock(&file_priv->descriptors_mutex); + return -ERANGE; + } + file_priv->descriptors[i] = kmalloc(sizeof(struct gpib_descriptor), GFP_KERNEL); + if (!file_priv->descriptors[i]) { + mutex_unlock(&file_priv->descriptors_mutex); + return -ENOMEM; + } + init_gpib_descriptor(file_priv->descriptors[i]); + + file_priv->descriptors[i]->pad = open_dev_cmd.pad; + file_priv->descriptors[i]->sad = open_dev_cmd.sad; + file_priv->descriptors[i]->is_board = open_dev_cmd.is_board; + mutex_unlock(&file_priv->descriptors_mutex); + + retval = increment_open_device_count(board, &board->device_list, open_dev_cmd.pad, + open_dev_cmd.sad); + if (retval < 0) + return retval; + + /* + * clear stuck srq state, since we may be able to find service request on + * the new device + */ + atomic_set(&board->stuck_srq, 0); + + open_dev_cmd.handle = i; + retval = copy_to_user((void __user *)arg, &open_dev_cmd, sizeof(open_dev_cmd)); + if (retval) + return -EFAULT; + + return 0; +} + +static int close_dev_ioctl(struct file *filep, struct gpib_board *board, unsigned long arg) +{ + struct gpib_close_dev_ioctl cmd; + struct gpib_file_private *file_priv = filep->private_data; + int retval; + + retval = copy_from_user(&cmd, (void __user *)arg, sizeof(cmd)); + if (retval) + return -EFAULT; + + if (cmd.handle >= GPIB_MAX_NUM_DESCRIPTORS) + return -EINVAL; + if (!file_priv->descriptors[cmd.handle]) + return -EINVAL; + + retval = decrement_open_device_count(board, &board->device_list, + file_priv->descriptors[cmd.handle]->pad, + file_priv->descriptors[cmd.handle]->sad); + if (retval < 0) + return retval; + + kfree(file_priv->descriptors[cmd.handle]); + file_priv->descriptors[cmd.handle] = NULL; + + return 0; +} + +static int serial_poll_ioctl(struct gpib_board *board, unsigned long arg) +{ + struct gpib_serial_poll_ioctl serial_cmd; + int retval; + + retval = copy_from_user(&serial_cmd, (void __user *)arg, sizeof(serial_cmd)); + if (retval) + return -EFAULT; + + retval = get_serial_poll_byte(board, serial_cmd.pad, serial_cmd.sad, board->usec_timeout, + &serial_cmd.status_byte); + if (retval < 0) + return retval; + + retval = copy_to_user((void __user *)arg, &serial_cmd, sizeof(serial_cmd)); + if (retval) + return -EFAULT; + + return 0; +} + +static int wait_ioctl(struct gpib_file_private *file_priv, struct gpib_board *board, + unsigned long arg) +{ + struct gpib_wait_ioctl wait_cmd; + int retval; + struct gpib_descriptor *desc; + + retval = copy_from_user(&wait_cmd, (void __user *)arg, sizeof(wait_cmd)); + if (retval) + return -EFAULT; + + desc = handle_to_descriptor(file_priv, wait_cmd.handle); + if (!desc) + return -EINVAL; + + retval = ibwait(board, wait_cmd.wait_mask, wait_cmd.clear_mask, + wait_cmd.set_mask, &wait_cmd.ibsta, wait_cmd.usec_timeout, desc); + if (retval < 0) + return retval; + + retval = copy_to_user((void __user *)arg, &wait_cmd, sizeof(wait_cmd)); + if (retval) + return -EFAULT; + + return 0; +} + +static int parallel_poll_ioctl(struct gpib_board *board, unsigned long arg) +{ + u8 poll_byte; + int retval; + + retval = ibrpp(board, &poll_byte); + if (retval < 0) + return retval; + + retval = copy_to_user((void __user *)arg, &poll_byte, sizeof(poll_byte)); + if (retval) + return -EFAULT; + + return 0; +} + +static int online_ioctl(struct gpib_board *board, unsigned long arg) +{ + struct gpib_online_ioctl online_cmd; + int retval; + void __user *init_data = NULL; + + board->config.init_data = NULL; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + retval = copy_from_user(&online_cmd, (void __user *)arg, sizeof(online_cmd)); + if (retval) + return -EFAULT; + if (online_cmd.init_data_length > 0) { + board->config.init_data = vmalloc(online_cmd.init_data_length); + if (!board->config.init_data) + return -ENOMEM; + if (WARN_ON_ONCE(sizeof(init_data) > sizeof(online_cmd.init_data_ptr))) + return -EFAULT; + init_data = (void __user *)(unsigned long)(online_cmd.init_data_ptr); + retval = copy_from_user(board->config.init_data, init_data, + online_cmd.init_data_length); + if (retval) { + vfree(board->config.init_data); + return -EFAULT; + } + board->config.init_data_length = online_cmd.init_data_length; + } else { + board->config.init_data = NULL; + board->config.init_data_length = 0; + } + if (online_cmd.online) + retval = ibonline(board); + else + retval = iboffline(board); + if (board->config.init_data) { + vfree(board->config.init_data); + board->config.init_data = NULL; + board->config.init_data_length = 0; + } + return retval; +} + +static int remote_enable_ioctl(struct gpib_board *board, unsigned long arg) +{ + int enable; + int retval; + + retval = copy_from_user(&enable, (void __user *)arg, sizeof(enable)); + if (retval) + return -EFAULT; + + return ibsre(board, enable); +} + +static int take_control_ioctl(struct gpib_board *board, unsigned long arg) +{ + int synchronous; + int retval; + + retval = copy_from_user(&synchronous, (void __user *)arg, sizeof(synchronous)); + if (retval) + return -EFAULT; + + return ibcac(board, synchronous, 1); +} + +static int line_status_ioctl(struct gpib_board *board, unsigned long arg) +{ + short lines; + int retval; + + retval = iblines(board, &lines); + if (retval < 0) + return retval; + + retval = copy_to_user((void __user *)arg, &lines, sizeof(lines)); + if (retval) + return -EFAULT; + + return 0; +} + +static int pad_ioctl(struct gpib_board *board, struct gpib_file_private *file_priv, + unsigned long arg) +{ + struct gpib_pad_ioctl cmd; + int retval; + struct gpib_descriptor *desc; + + retval = copy_from_user(&cmd, (void __user *)arg, sizeof(cmd)); + if (retval) + return -EFAULT; + + desc = handle_to_descriptor(file_priv, cmd.handle); + if (!desc) + return -EINVAL; + + if (desc->is_board) { + retval = ibpad(board, cmd.pad); + if (retval < 0) + return retval; + } else { + retval = decrement_open_device_count(board, &board->device_list, desc->pad, + desc->sad); + if (retval < 0) + return retval; + + desc->pad = cmd.pad; + + retval = increment_open_device_count(board, &board->device_list, desc->pad, + desc->sad); + if (retval < 0) + return retval; + } + + return 0; +} + +static int sad_ioctl(struct gpib_board *board, struct gpib_file_private *file_priv, + unsigned long arg) +{ + struct gpib_sad_ioctl cmd; + int retval; + struct gpib_descriptor *desc; + + retval = copy_from_user(&cmd, (void __user *)arg, sizeof(cmd)); + if (retval) + return -EFAULT; + + desc = handle_to_descriptor(file_priv, cmd.handle); + if (!desc) + return -EINVAL; + + if (desc->is_board) { + retval = ibsad(board, cmd.sad); + if (retval < 0) + return retval; + } else { + retval = decrement_open_device_count(board, &board->device_list, desc->pad, + desc->sad); + if (retval < 0) + return retval; + + desc->sad = cmd.sad; + + retval = increment_open_device_count(board, &board->device_list, desc->pad, + desc->sad); + if (retval < 0) + return retval; + } + return 0; +} + +static int eos_ioctl(struct gpib_board *board, unsigned long arg) +{ + struct gpib_eos_ioctl eos_cmd; + int retval; + + retval = copy_from_user(&eos_cmd, (void __user *)arg, sizeof(eos_cmd)); + if (retval) + return -EFAULT; + + return ibeos(board, eos_cmd.eos, eos_cmd.eos_flags); +} + +static int request_service_ioctl(struct gpib_board *board, unsigned long arg) +{ + u8 status_byte; + int retval; + + retval = copy_from_user(&status_byte, (void __user *)arg, sizeof(status_byte)); + if (retval) + return -EFAULT; + + return ibrsv2(board, status_byte, status_byte & request_service_bit); +} + +static int request_service2_ioctl(struct gpib_board *board, unsigned long arg) +{ + struct gpib_request_service2 request_service2_cmd; + int retval; + + retval = copy_from_user(&request_service2_cmd, (void __user *)arg, + sizeof(struct gpib_request_service2)); + if (retval) + return -EFAULT; + + return ibrsv2(board, request_service2_cmd.status_byte, + request_service2_cmd.new_reason_for_service); +} + +static int iobase_ioctl(struct gpib_board_config *config, unsigned long arg) +{ + u64 base_addr; + int retval; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + retval = copy_from_user(&base_addr, (void __user *)arg, sizeof(base_addr)); + if (retval) + return -EFAULT; + + if (WARN_ON_ONCE(sizeof(void *) > sizeof(base_addr))) + return -EFAULT; + config->ibbase = base_addr; + + return 0; +} + +static int irq_ioctl(struct gpib_board_config *config, unsigned long arg) +{ + unsigned int irq; + int retval; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + retval = copy_from_user(&irq, (void __user *)arg, sizeof(irq)); + if (retval) + return -EFAULT; + + config->ibirq = irq; + + return 0; +} + +static int dma_ioctl(struct gpib_board_config *config, unsigned long arg) +{ + unsigned int dma_channel; + int retval; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + retval = copy_from_user(&dma_channel, (void __user *)arg, sizeof(dma_channel)); + if (retval) + return -EFAULT; + + config->ibdma = dma_channel; + + return 0; +} + +static int autospoll_ioctl(struct gpib_board *board, struct gpib_file_private *file_priv, + unsigned long arg) +{ + short enable; + int retval; + struct gpib_descriptor *desc; + + retval = copy_from_user(&enable, (void __user *)arg, sizeof(enable)); + if (retval) + return -EFAULT; + + desc = handle_to_descriptor(file_priv, 0); /* board handle is 0 */ + + if (enable) { + if (!desc->autopoll_enabled) { + board->autospollers++; + desc->autopoll_enabled = 1; + } + retval = 0; + } else { + if (desc->autopoll_enabled) { + desc->autopoll_enabled = 0; + if (board->autospollers > 0) { + board->autospollers--; + retval = 0; + } else { + dev_err(board->gpib_dev, + "tried to set number of autospollers negative\n"); + retval = -EINVAL; + } + } else { + dev_err(board->gpib_dev, "autopoll disable requested before enable\n"); + retval = -EINVAL; + } + } + return retval; +} + +static int mutex_ioctl(struct gpib_board *board, struct gpib_file_private *file_priv, + unsigned long arg) +{ + int retval, lock_mutex; + + retval = copy_from_user(&lock_mutex, (void __user *)arg, sizeof(lock_mutex)); + if (retval) + return -EFAULT; + + if (lock_mutex) { + retval = mutex_lock_interruptible(&board->user_mutex); + if (retval) + return -ERESTARTSYS; + + spin_lock(&board->locking_pid_spinlock); + board->locking_pid = current->pid; + spin_unlock(&board->locking_pid_spinlock); + + atomic_set(&file_priv->holding_mutex, 1); + + dev_dbg(board->gpib_dev, "locked board mutex\n"); + } else { + spin_lock(&board->locking_pid_spinlock); + if (current->pid != board->locking_pid) { + dev_err(board->gpib_dev, "bug! pid %i tried to release mutex held by pid %i\n", + current->pid, board->locking_pid); + spin_unlock(&board->locking_pid_spinlock); + return -EPERM; + } + board->locking_pid = 0; + spin_unlock(&board->locking_pid_spinlock); + + atomic_set(&file_priv->holding_mutex, 0); + + mutex_unlock(&board->user_mutex); + dev_dbg(board->gpib_dev, "unlocked board mutex\n"); + } + return 0; +} + +static int timeout_ioctl(struct gpib_board *board, unsigned long arg) +{ + unsigned int timeout; + int retval; + + retval = copy_from_user(&timeout, (void __user *)arg, sizeof(timeout)); + if (retval) + return -EFAULT; + + board->usec_timeout = timeout; + dev_dbg(board->gpib_dev, "timeout set to %i usec\n", timeout); + + return 0; +} + +static int ppc_ioctl(struct gpib_board *board, unsigned long arg) +{ + struct gpib_ppoll_config_ioctl cmd; + int retval; + + retval = copy_from_user(&cmd, (void __user *)arg, sizeof(cmd)); + if (retval) + return -EFAULT; + + if (cmd.set_ist) { + board->ist = 1; + board->interface->parallel_poll_response(board, board->ist); + } else if (cmd.clear_ist) { + board->ist = 0; + board->interface->parallel_poll_response(board, board->ist); + } + + if (cmd.config) { + retval = ibppc(board, cmd.config); + if (retval < 0) + return retval; + } + + return 0; +} + +static int set_local_ppoll_mode_ioctl(struct gpib_board *board, unsigned long arg) +{ + short cmd; + int retval; + + retval = copy_from_user(&cmd, (void __user *)arg, sizeof(cmd)); + if (retval) + return -EFAULT; + + if (!board->interface->local_parallel_poll_mode) + return -ENOENT; + board->local_ppoll_mode = cmd != 0; + board->interface->local_parallel_poll_mode(board, board->local_ppoll_mode); + + return 0; +} + +static int get_local_ppoll_mode_ioctl(struct gpib_board *board, unsigned long arg) +{ + short cmd; + int retval; + + cmd = board->local_ppoll_mode; + retval = copy_to_user((void __user *)arg, &cmd, sizeof(cmd)); + if (retval) + return -EFAULT; + + return 0; +} + +static int query_board_rsv_ioctl(struct gpib_board *board, unsigned long arg) +{ + int status; + int retval; + + status = board->interface->serial_poll_status(board); + + retval = copy_to_user((void __user *)arg, &status, sizeof(status)); + if (retval) + return -EFAULT; + + return 0; +} + +static int board_info_ioctl(const struct gpib_board *board, unsigned long arg) +{ + struct gpib_board_info_ioctl info = { }; + int retval; + + info.pad = board->pad; + info.sad = board->sad; + info.parallel_poll_configuration = board->parallel_poll_configuration; + info.is_system_controller = board->master; + if (board->autospollers) + info.autopolling = 1; + else + info.autopolling = 0; + info.t1_delay = board->t1_nano_sec; + info.ist = board->ist; + info.no_7_bit_eos = board->interface->no_7_bit_eos; + retval = copy_to_user((void __user *)arg, &info, sizeof(info)); + if (retval) + return -EFAULT; + + return 0; +} + +static int interface_clear_ioctl(struct gpib_board *board, unsigned long arg) +{ + unsigned int usec_duration; + int retval; + + retval = copy_from_user(&usec_duration, (void __user *)arg, sizeof(usec_duration)); + if (retval) + return -EFAULT; + + return ibsic(board, usec_duration); +} + +static int select_pci_ioctl(struct gpib_board_config *config, unsigned long arg) +{ + struct gpib_select_pci_ioctl selection; + int retval; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + retval = copy_from_user(&selection, (void __user *)arg, sizeof(selection)); + if (retval) + return -EFAULT; + + config->pci_bus = selection.pci_bus; + config->pci_slot = selection.pci_slot; + + return 0; +} + +static int select_device_path_ioctl(struct gpib_board_config *config, unsigned long arg) +{ + struct gpib_select_device_path_ioctl *selection; + int retval; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + selection = vmalloc(sizeof(struct gpib_select_device_path_ioctl)); + if (!selection) + return -ENOMEM; + + retval = copy_from_user(selection, (void __user *)arg, + sizeof(struct gpib_select_device_path_ioctl)); + if (retval) { + vfree(selection); + return -EFAULT; + } + + selection->device_path[sizeof(selection->device_path) - 1] = '\0'; + kfree(config->device_path); + config->device_path = NULL; + if (strlen(selection->device_path) > 0) + config->device_path = kstrdup(selection->device_path, GFP_KERNEL); + + vfree(selection); + return 0; +} + +unsigned int num_gpib_events(const struct gpib_event_queue *queue) +{ + return queue->num_events; +} + +static int push_gpib_event_nolock(struct gpib_board *board, short event_type) +{ + struct gpib_event_queue *queue = &board->event_queue; + struct list_head *head = &queue->event_head; + struct gpib_event *event; + static const unsigned int max_num_events = 1024; + int retval; + + if (num_gpib_events(queue) >= max_num_events) { + short lost_event; + + queue->dropped_event = 1; + retval = pop_gpib_event_nolock(board, queue, &lost_event); + if (retval < 0) + return retval; + } + + event = kmalloc(sizeof(struct gpib_event), GFP_ATOMIC); + if (!event) { + queue->dropped_event = 1; + dev_err(board->gpib_dev, "failed to allocate memory for event\n"); + return -ENOMEM; + } + + INIT_LIST_HEAD(&event->list); + event->event_type = event_type; + + list_add_tail(&event->list, head); + + queue->num_events++; + + dev_dbg(board->gpib_dev, "pushed event %i, %i in queue\n", + (int)event_type, num_gpib_events(queue)); + + return 0; +} + +// push event onto back of event queue +int push_gpib_event(struct gpib_board *board, short event_type) +{ + unsigned long flags; + int retval; + + spin_lock_irqsave(&board->event_queue.lock, flags); + retval = push_gpib_event_nolock(board, event_type); + spin_unlock_irqrestore(&board->event_queue.lock, flags); + + if (event_type == EVENT_DEV_TRG) + board->status |= DTAS; + if (event_type == EVENT_DEV_CLR) + board->status |= DCAS; + + return retval; +} +EXPORT_SYMBOL(push_gpib_event); + +static int pop_gpib_event_nolock(struct gpib_board *board, + struct gpib_event_queue *queue, short *event_type) +{ + struct list_head *head = &queue->event_head; + struct list_head *front = head->next; + struct gpib_event *event; + + if (num_gpib_events(queue) == 0) { + *event_type = EVENT_NONE; + return 0; + } + + if (front == head) + return -EIO; + + if (queue->dropped_event) { + queue->dropped_event = 0; + return -EPIPE; + } + + event = list_entry(front, struct gpib_event, list); + *event_type = event->event_type; + + list_del(front); + kfree(event); + + queue->num_events--; + + dev_dbg(board->gpib_dev, "popped event %i, %i in queue\n", + (int)*event_type, num_gpib_events(queue)); + + return 0; +} + +// pop event from front of event queue +int pop_gpib_event(struct gpib_board *board, struct gpib_event_queue *queue, short *event_type) +{ + unsigned long flags; + int retval; + + spin_lock_irqsave(&queue->lock, flags); + retval = pop_gpib_event_nolock(board, queue, event_type); + spin_unlock_irqrestore(&queue->lock, flags); + return retval; +} + +static int event_ioctl(struct gpib_board *board, unsigned long arg) +{ + short user_event; + int retval; + short event; + + retval = pop_gpib_event(board, &board->event_queue, &event); + if (retval < 0) + return retval; + + user_event = event; + + retval = copy_to_user((void __user *)arg, &user_event, sizeof(user_event)); + if (retval) + return -EFAULT; + + return 0; +} + +static int request_system_control_ioctl(struct gpib_board *board, unsigned long arg) +{ + int request_control; + int retval; + + retval = copy_from_user(&request_control, (void __user *)arg, sizeof(request_control)); + if (retval) + return -EFAULT; + + return ibrsc(board, request_control); +} + +static int t1_delay_ioctl(struct gpib_board *board, unsigned long arg) +{ + unsigned int cmd; + unsigned int delay; + int retval; + + if (!board->interface->t1_delay) + return -ENOENT; + + retval = copy_from_user(&cmd, (void __user *)arg, sizeof(cmd)); + if (retval) + return -EFAULT; + + delay = cmd; + + retval = board->interface->t1_delay(board, delay); + if (retval < 0) + return retval; + + board->t1_nano_sec = retval; + return 0; +} + +static const struct file_operations ib_fops = { + .owner = THIS_MODULE, + .llseek = NULL, + .unlocked_ioctl = &ibioctl, + .compat_ioctl = &ibioctl, + .open = &ibopen, + .release = &ibclose, +}; + +struct gpib_board board_array[GPIB_MAX_NUM_BOARDS]; + +LIST_HEAD(registered_drivers); + +void init_gpib_descriptor(struct gpib_descriptor *desc) +{ + desc->pad = 0; + desc->sad = -1; + desc->is_board = 0; + desc->autopoll_enabled = 0; + atomic_set(&desc->io_in_progress, 0); +} + +int gpib_register_driver(struct gpib_interface *interface, struct module *provider_module) +{ + struct gpib_interface_list *entry; + + entry = kmalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) + return -ENOMEM; + + entry->interface = interface; + entry->module = provider_module; + list_add(&entry->list, ®istered_drivers); + + return 0; +} +EXPORT_SYMBOL(gpib_register_driver); + +void gpib_unregister_driver(struct gpib_interface *interface) +{ + int i; + struct list_head *list_ptr; + + for (i = 0; i < GPIB_MAX_NUM_BOARDS; i++) { + struct gpib_board *board = &board_array[i]; + + if (board->interface == interface) { + if (board->use_count > 0) + pr_warn("gpib: Warning: deregistered interface %s in use\n", + interface->name); + iboffline(board); + board->interface = NULL; + } + } + for (list_ptr = registered_drivers.next; list_ptr != ®istered_drivers;) { + struct gpib_interface_list *entry; + + entry = list_entry(list_ptr, struct gpib_interface_list, list); + list_ptr = list_ptr->next; + if (entry->interface == interface) { + list_del(&entry->list); + kfree(entry); + } + } +} +EXPORT_SYMBOL(gpib_unregister_driver); + +static void init_gpib_board_config(struct gpib_board_config *config) +{ + memset(config, 0, sizeof(struct gpib_board_config)); + config->pci_bus = -1; + config->pci_slot = -1; +} + +void init_gpib_board(struct gpib_board *board) +{ + board->interface = NULL; + board->provider_module = NULL; + board->buffer = NULL; + board->buffer_length = 0; + board->status = 0; + init_waitqueue_head(&board->wait); + mutex_init(&board->user_mutex); + mutex_init(&board->big_gpib_mutex); + board->locking_pid = 0; + spin_lock_init(&board->locking_pid_spinlock); + spin_lock_init(&board->spinlock); + timer_setup(&board->timer, NULL, 0); + board->dev = NULL; + board->gpib_dev = NULL; + init_gpib_board_config(&board->config); + board->private_data = NULL; + board->use_count = 0; + INIT_LIST_HEAD(&board->device_list); + board->pad = 0; + board->sad = -1; + board->usec_timeout = 3000000; + board->parallel_poll_configuration = 0; + board->online = 0; + board->autospollers = 0; + board->autospoll_task = NULL; + init_event_queue(&board->event_queue); + board->minor = -1; + init_gpib_pseudo_irq(&board->pseudo_irq); + board->master = 1; + atomic_set(&board->stuck_srq, 0); + board->local_ppoll_mode = 0; +} + +int gpib_allocate_board(struct gpib_board *board) +{ + if (!board->buffer) { + board->buffer_length = 0x4000; + board->buffer = vmalloc(board->buffer_length); + if (!board->buffer) { + board->buffer_length = 0; + return -ENOMEM; + } + } + return 0; +} + +void gpib_deallocate_board(struct gpib_board *board) +{ + short dummy; + + if (board->buffer) { + vfree(board->buffer); + board->buffer = NULL; + board->buffer_length = 0; + } + while (num_gpib_events(&board->event_queue)) + pop_gpib_event(board, &board->event_queue, &dummy); +} + +static void init_board_array(struct gpib_board *board_array, unsigned int length) +{ + int i; + + for (i = 0; i < length; i++) { + init_gpib_board(&board_array[i]); + board_array[i].minor = i; + } +} + +void init_gpib_status_queue(struct gpib_status_queue *device) +{ + INIT_LIST_HEAD(&device->list); + INIT_LIST_HEAD(&device->status_bytes); + device->num_status_bytes = 0; + device->reference_count = 0; + device->dropped_byte = 0; +} + +static struct class *gpib_class; + +static int __init gpib_common_init_module(void) +{ + int i; + + pr_info("GPIB core driver\n"); + init_board_array(board_array, GPIB_MAX_NUM_BOARDS); + if (register_chrdev(GPIB_CODE, "gpib", &ib_fops)) { + pr_err("gpib: can't get major %d\n", GPIB_CODE); + return -EIO; + } + gpib_class = class_create("gpib_common"); + if (IS_ERR(gpib_class)) { + pr_err("gpib: failed to create gpib class\n"); + unregister_chrdev(GPIB_CODE, "gpib"); + return PTR_ERR(gpib_class); + } + for (i = 0; i < GPIB_MAX_NUM_BOARDS; ++i) + board_array[i].gpib_dev = device_create(gpib_class, NULL, + MKDEV(GPIB_CODE, i), NULL, "gpib%i", i); + + return 0; +} + +static void __exit gpib_common_exit_module(void) +{ + int i; + + for (i = 0; i < GPIB_MAX_NUM_BOARDS; ++i) + device_destroy(gpib_class, MKDEV(GPIB_CODE, i)); + + class_destroy(gpib_class); + unregister_chrdev(GPIB_CODE, "gpib"); +} + +int gpib_match_device_path(struct device *dev, const char *device_path_in) +{ + if (device_path_in) { + char *device_path; + + device_path = kobject_get_path(&dev->kobj, GFP_KERNEL); + if (!device_path) { + dev_err(dev, "kobject_get_path returned NULL."); + return 0; + } + if (strcmp(device_path_in, device_path) != 0) { + kfree(device_path); + return 0; + } + kfree(device_path); + } + return 1; +} +EXPORT_SYMBOL(gpib_match_device_path); + +struct pci_dev *gpib_pci_get_device(const struct gpib_board_config *config, unsigned int vendor_id, + unsigned int device_id, struct pci_dev *from) +{ + struct pci_dev *pci_device = from; + + while ((pci_device = pci_get_device(vendor_id, device_id, pci_device))) { + if (config->pci_bus >= 0 && config->pci_bus != pci_device->bus->number) + continue; + if (config->pci_slot >= 0 && config->pci_slot != + PCI_SLOT(pci_device->devfn)) + continue; + if (gpib_match_device_path(&pci_device->dev, config->device_path) == 0) + continue; + return pci_device; + } + return NULL; +} +EXPORT_SYMBOL(gpib_pci_get_device); + +struct pci_dev *gpib_pci_get_subsys(const struct gpib_board_config *config, unsigned int vendor_id, + unsigned int device_id, unsigned int ss_vendor, + unsigned int ss_device, + struct pci_dev *from) +{ + struct pci_dev *pci_device = from; + + while ((pci_device = pci_get_subsys(vendor_id, device_id, + ss_vendor, ss_device, pci_device))) { + if (config->pci_bus >= 0 && config->pci_bus != pci_device->bus->number) + continue; + if (config->pci_slot >= 0 && config->pci_slot != + PCI_SLOT(pci_device->devfn)) + continue; + if (gpib_match_device_path(&pci_device->dev, config->device_path) == 0) + continue; + return pci_device; + } + return NULL; +} +EXPORT_SYMBOL(gpib_pci_get_subsys); + +module_init(gpib_common_init_module); +module_exit(gpib_common_exit_module); + diff --git a/drivers/gpib/common/iblib.c b/drivers/gpib/common/iblib.c new file mode 100644 index 000000000000..7cbb6a467177 --- /dev/null +++ b/drivers/gpib/common/iblib.c @@ -0,0 +1,717 @@ +// SPDX-License-Identifier: GPL-2.0 + +/*************************************************************************** + * copyright : (C) 2001, 2002 by Frank Mori Hess + ***************************************************************************/ + +#define dev_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include "ibsys.h" +#include +#include +#include + +/* + * IBCAC + * Return to the controller active state from the + * controller standby state, i.e., turn ATN on. Note + * that in order to enter the controller active state + * from the controller idle state, ibsic must be called. + * If sync is non-zero, attempt to take control synchronously. + * If fallback_to_async is non-zero, try to take control asynchronously + * if synchronous attempt fails. + */ +int ibcac(struct gpib_board *board, int sync, int fallback_to_async) +{ + int status = ibstatus(board); + int retval; + + if ((status & CIC) == 0) + return -EINVAL; + + if (status & ATN) + return 0; + + if (sync && (status & LACS) == 0) + /* + * tcs (take control synchronously) can only possibly work when + * controller is listener. Error code also needs to be -ETIMEDOUT + * or it will giveout without doing fallback. + */ + retval = -ETIMEDOUT; + else + retval = board->interface->take_control(board, sync); + + if (retval < 0 && fallback_to_async) { + if (sync && retval == -ETIMEDOUT) + retval = board->interface->take_control(board, 0); + } + board->interface->update_status(board, 0); + + return retval; +} + +/* + * After ATN is asserted, it should cause any connected devices + * to start listening for command bytes and leave acceptor idle state. + * So if ATN is asserted and neither NDAC or NRFD are asserted, + * then there are no devices and ibcmd should error out immediately. + * Some gpib hardware sees itself asserting NDAC/NRFD when it + * is controller in charge, in which case this check will + * do nothing useful (but shouldn't cause any harm either). + * Drivers that don't need this check (ni_usb for example) may + * set the skip_check_for_command_acceptors flag in their + * gpib_interface_struct to avoid useless overhead. + */ +static int check_for_command_acceptors(struct gpib_board *board) +{ + int lines; + + if (board->interface->skip_check_for_command_acceptors) + return 0; + if (!board->interface->line_status) + return 0; + + udelay(2); // allow time for devices to respond to ATN if it was just asserted + + lines = board->interface->line_status(board); + if (lines < 0) + return lines; + + if ((lines & VALID_NRFD) && (lines & VALID_NDAC)) { + if ((lines & BUS_NRFD) == 0 && (lines & BUS_NDAC) == 0) + return -ENOTCONN; + } + + return 0; +} + +/* + * IBCMD + * Write cnt command bytes from buf to the GPIB. The + * command operation terminates only on I/O complete. + * + * NOTE: + * 1. Prior to beginning the command, the interface is + * placed in the controller active state. + * 2. Before calling ibcmd for the first time, ibsic + * must be called to initialize the GPIB and enable + * the interface to leave the controller idle state. + */ +int ibcmd(struct gpib_board *board, u8 *buf, size_t length, size_t *bytes_written) +{ + ssize_t ret = 0; + int status; + + *bytes_written = 0; + + status = ibstatus(board); + + if ((status & CIC) == 0) + return -EINVAL; + + os_start_timer(board, board->usec_timeout); + + ret = ibcac(board, 1, 1); + if (ret == 0) { + ret = check_for_command_acceptors(board); + if (ret == 0) + ret = board->interface->command(board, buf, length, bytes_written); + } + + os_remove_timer(board); + + if (io_timed_out(board)) + ret = -ETIMEDOUT; + + return ret; +} + +/* + * IBGTS + * Go to the controller standby state from the controller + * active state, i.e., turn ATN off. + */ + +int ibgts(struct gpib_board *board) +{ + int status = ibstatus(board); + int retval; + + if ((status & CIC) == 0) + return -EINVAL; + + retval = board->interface->go_to_standby(board); /* go to standby */ + + board->interface->update_status(board, 0); + + return retval; +} + +static int autospoll_wait_should_wake_up(struct gpib_board *board) +{ + int retval; + + mutex_lock(&board->big_gpib_mutex); + + retval = board->master && board->autospollers > 0 && + !atomic_read(&board->stuck_srq) && + test_and_clear_bit(SRQI_NUM, &board->status); + + mutex_unlock(&board->big_gpib_mutex); + return retval; +} + +static int autospoll_thread(void *board_void) +{ + struct gpib_board *board = board_void; + int retval = 0; + + dev_dbg(board->gpib_dev, "entering autospoll thread\n"); + + while (1) { + wait_event_interruptible(board->wait, + kthread_should_stop() || + autospoll_wait_should_wake_up(board)); + dev_dbg(board->gpib_dev, "autospoll wait satisfied\n"); + if (kthread_should_stop()) + break; + + mutex_lock(&board->big_gpib_mutex); + /* make sure we are still good after we have lock */ + if (board->autospollers <= 0 || board->master == 0) { + mutex_unlock(&board->big_gpib_mutex); + continue; + } + mutex_unlock(&board->big_gpib_mutex); + + if (try_module_get(board->provider_module)) { + retval = autopoll_all_devices(board); + module_put(board->provider_module); + } else { + dev_err(board->gpib_dev, "try_module_get() failed!\n"); + } + if (retval <= 0) { + dev_err(board->gpib_dev, "stuck SRQ\n"); + + atomic_set(&board->stuck_srq, 1); // XXX could be better + set_bit(SRQI_NUM, &board->status); + } + } + return retval; +} + +int ibonline(struct gpib_board *board) +{ + int retval; + + if (board->online) + return -EBUSY; + if (!board->interface) + return -ENODEV; + retval = gpib_allocate_board(board); + if (retval < 0) + return retval; + + board->dev = NULL; + board->local_ppoll_mode = 0; + retval = board->interface->attach(board, &board->config); + if (retval < 0) { + board->interface->detach(board); + return retval; + } + /* + * nios2nommu on 2.6.11 uclinux kernel has weird problems + * with autospoll thread causing huge slowdowns + */ +#ifndef CONFIG_NIOS2 + board->autospoll_task = kthread_run(&autospoll_thread, board, + "gpib%d_autospoll_kthread", board->minor); + retval = IS_ERR(board->autospoll_task); + if (retval) { + dev_err(board->gpib_dev, "failed to create autospoll thread\n"); + board->interface->detach(board); + return retval; + } +#endif + board->online = 1; + dev_dbg(board->gpib_dev, "board online\n"); + + return 0; +} + +/* XXX need to make sure board is generally not in use (grab board lock?) */ +int iboffline(struct gpib_board *board) +{ + int retval; + + if (board->online == 0) + return 0; + if (!board->interface) + return -ENODEV; + + if (board->autospoll_task && !IS_ERR(board->autospoll_task)) { + retval = kthread_stop(board->autospoll_task); + if (retval) + dev_err(board->gpib_dev, "kthread_stop returned %i\n", retval); + board->autospoll_task = NULL; + } + + board->interface->detach(board); + gpib_deallocate_board(board); + board->online = 0; + dev_dbg(board->gpib_dev, "board offline\n"); + + return 0; +} + +/* + * IBLINES + * Poll the GPIB control lines and return their status in buf. + * + * LSB (bits 0-7) - VALID lines mask (lines that can be monitored). + * Next LSB (bits 8-15) - STATUS lines mask (lines that are currently set). + * + */ +int iblines(const struct gpib_board *board, short *lines) +{ + int retval; + + *lines = 0; + if (!board->interface->line_status) + return 0; + retval = board->interface->line_status(board); + if (retval < 0) + return retval; + *lines = retval; + return 0; +} + +/* + * IBRD + * Read up to 'length' bytes of data from the GPIB into buf. End + * on detection of END (EOI and or EOS) and set 'end_flag'. + * + * NOTE: + * 1. The interface is placed in the controller standby + * state prior to beginning the read. + * 2. Prior to calling ibrd, the intended devices as well + * as the interface board itself must be addressed by + * calling ibcmd. + */ + +int ibrd(struct gpib_board *board, u8 *buf, size_t length, int *end_flag, size_t *nbytes) +{ + ssize_t ret = 0; + int retval; + size_t bytes_read; + + *nbytes = 0; + *end_flag = 0; + if (length == 0) + return 0; + + if (board->master) { + retval = ibgts(board); + if (retval < 0) + return retval; + } + /* + * XXX resetting timer here could cause timeouts take longer than they should, + * since read_ioctl calls this + * function in a loop, there is probably a similar problem with writes/commands + */ + os_start_timer(board, board->usec_timeout); + + do { + ret = board->interface->read(board, buf, length - *nbytes, end_flag, &bytes_read); + if (ret < 0) + goto ibrd_out; + + buf += bytes_read; + *nbytes += bytes_read; + if (need_resched()) + schedule(); + } while (ret == 0 && *nbytes > 0 && *nbytes < length && *end_flag == 0); +ibrd_out: + os_remove_timer(board); + + return ret; +} + +/* + * IBRPP + * Conduct a parallel poll and return the byte in buf. + * + * NOTE: + * 1. Prior to conducting the poll the interface is placed + * in the controller active state. + */ +int ibrpp(struct gpib_board *board, u8 *result) +{ + int retval = 0; + + os_start_timer(board, board->usec_timeout); + retval = ibcac(board, 1, 1); + if (retval) + return -1; + + retval = board->interface->parallel_poll(board, result); + + os_remove_timer(board); + return retval; +} + +int ibppc(struct gpib_board *board, u8 configuration) +{ + configuration &= 0x1f; + board->interface->parallel_poll_configure(board, configuration); + board->parallel_poll_configuration = configuration; + + return 0; +} + +int ibrsv2(struct gpib_board *board, u8 status_byte, int new_reason_for_service) +{ + int board_status = ibstatus(board); + const unsigned int MSS = status_byte & request_service_bit; + + if ((board_status & CIC)) + return -EINVAL; + + if (MSS == 0 && new_reason_for_service) + return -EINVAL; + + if (board->interface->serial_poll_response2) { + board->interface->serial_poll_response2(board, status_byte, new_reason_for_service); + // fall back on simpler serial_poll_response if the behavior would be the same + } else if (board->interface->serial_poll_response && + (MSS == 0 || (MSS && new_reason_for_service))) { + board->interface->serial_poll_response(board, status_byte); + } else { + return -EOPNOTSUPP; + } + + return 0; +} + +/* + * IBSIC + * Send IFC for at least 100 microseconds. + * + * NOTE: + * 1. Ibsic must be called prior to the first call to + * ibcmd in order to initialize the bus and enable the + * interface to leave the controller idle state. + */ +int ibsic(struct gpib_board *board, unsigned int usec_duration) +{ + if (board->master == 0) + return -EINVAL; + + if (usec_duration < 100) + usec_duration = 100; + if (usec_duration > 1000) + usec_duration = 1000; + + dev_dbg(board->gpib_dev, "sending interface clear, delay = %ius\n", usec_duration); + board->interface->interface_clear(board, 1); + udelay(usec_duration); + board->interface->interface_clear(board, 0); + + return 0; +} + +int ibrsc(struct gpib_board *board, int request_control) +{ + int retval; + + if (!board->interface->request_system_control) + return -EPERM; + + retval = board->interface->request_system_control(board, request_control); + + if (retval) + return retval; + + board->master = request_control != 0; + + return 0; +} + +/* + * IBSRE + * Send REN true if v is non-zero or false if v is zero. + */ +int ibsre(struct gpib_board *board, int enable) +{ + if (board->master == 0) + return -EINVAL; + + board->interface->remote_enable(board, enable); /* set or clear REN */ + if (!enable) + usleep_range(100, 150); + + return 0; +} + +/* + * IBPAD + * change the GPIB address of the interface board. The address + * must be 0 through 30. ibonl resets the address to PAD. + */ +int ibpad(struct gpib_board *board, unsigned int addr) +{ + if (addr > MAX_GPIB_PRIMARY_ADDRESS) + return -EINVAL; + + board->pad = addr; + if (board->online) + board->interface->primary_address(board, board->pad); + dev_dbg(board->gpib_dev, "set primary addr to %i\n", board->pad); + return 0; +} + +/* + * IBSAD + * change the secondary GPIB address of the interface board. + * The address must be 0 through 30, or negative disables. ibonl resets the + * address to SAD. + */ +int ibsad(struct gpib_board *board, int addr) +{ + if (addr > MAX_GPIB_SECONDARY_ADDRESS) + return -EINVAL; + board->sad = addr; + if (board->online) { + if (board->sad >= 0) + board->interface->secondary_address(board, board->sad, 1); + else + board->interface->secondary_address(board, 0, 0); + } + dev_dbg(board->gpib_dev, "set secondary addr to %i\n", board->sad); + + return 0; +} + +/* + * IBEOS + * Set the end-of-string modes for I/O operations to v. + * + */ +int ibeos(struct gpib_board *board, int eos, int eosflags) +{ + int retval; + + if (eosflags & ~EOS_MASK) + return -EINVAL; + if (eosflags & REOS) { + retval = board->interface->enable_eos(board, eos, eosflags & BIN); + } else { + board->interface->disable_eos(board); + retval = 0; + } + return retval; +} + +int ibstatus(struct gpib_board *board) +{ + return general_ibstatus(board, NULL, 0, 0, NULL); +} + +int general_ibstatus(struct gpib_board *board, const struct gpib_status_queue *device, + int clear_mask, int set_mask, struct gpib_descriptor *desc) +{ + int status = 0; + short line_status; + + if (board->private_data) { + status = board->interface->update_status(board, clear_mask); + /* + * XXX should probably stop having drivers use TIMO bit in + * board->status to avoid confusion + */ + status &= ~TIMO; + /* get real SRQI status if we can */ + if (iblines(board, &line_status) == 0) { + if ((line_status & VALID_SRQ)) { + if ((line_status & BUS_SRQ)) + status |= SRQI; + else + status &= ~SRQI; + } + } + } + if (device) + if (num_status_bytes(device)) + status |= RQS; + + if (desc) { + if (set_mask & CMPL) + atomic_set(&desc->io_in_progress, 0); + else if (clear_mask & CMPL) + atomic_set(&desc->io_in_progress, 1); + + if (atomic_read(&desc->io_in_progress)) + status &= ~CMPL; + else + status |= CMPL; + } + if (num_gpib_events(&board->event_queue)) + status |= EVENT; + else + status &= ~EVENT; + + return status; +} + +struct wait_info { + struct gpib_board *board; + struct timer_list timer; + int timed_out; + unsigned long usec_timeout; +}; + +static void wait_timeout(struct timer_list *t) +{ + struct wait_info *winfo = timer_container_of(winfo, t, timer); + + winfo->timed_out = 1; + wake_up_interruptible(&winfo->board->wait); +} + +static void init_wait_info(struct wait_info *winfo) +{ + winfo->board = NULL; + winfo->timed_out = 0; + timer_setup_on_stack(&winfo->timer, wait_timeout, 0); +} + +static int wait_satisfied(struct wait_info *winfo, struct gpib_status_queue *status_queue, + int wait_mask, int *status, struct gpib_descriptor *desc) +{ + struct gpib_board *board = winfo->board; + int temp_status; + + if (mutex_lock_interruptible(&board->big_gpib_mutex)) + return -ERESTARTSYS; + + temp_status = general_ibstatus(board, status_queue, 0, 0, desc); + + mutex_unlock(&board->big_gpib_mutex); + + if (winfo->timed_out) + temp_status |= TIMO; + else + temp_status &= ~TIMO; + if (wait_mask & temp_status) { + *status = temp_status; + return 1; + } +// XXX does wait for END work? + return 0; +} + +/* install timer interrupt handler */ +static void start_wait_timer(struct wait_info *winfo) +/* Starts the timeout task */ +{ + winfo->timed_out = 0; + + if (winfo->usec_timeout > 0) + mod_timer(&winfo->timer, jiffies + usec_to_jiffies(winfo->usec_timeout)); +} + +static void remove_wait_timer(struct wait_info *winfo) +{ + timer_delete_sync(&winfo->timer); + timer_destroy_on_stack(&winfo->timer); +} + +/* + * IBWAIT + * Check or wait for a GPIB event to occur. The mask argument + * is a bit vector corresponding to the status bit vector. It + * has a bit set for each condition which can terminate the wait + * If the mask is 0 then + * no condition is waited for. + */ +int ibwait(struct gpib_board *board, int wait_mask, int clear_mask, int set_mask, + int *status, unsigned long usec_timeout, struct gpib_descriptor *desc) +{ + int retval = 0; + struct gpib_status_queue *status_queue; + struct wait_info winfo; + + if (desc->is_board) + status_queue = NULL; + else + status_queue = get_gpib_status_queue(board, desc->pad, desc->sad); + + if (wait_mask == 0) { + *status = general_ibstatus(board, status_queue, clear_mask, set_mask, desc); + return 0; + } + + mutex_unlock(&board->big_gpib_mutex); + + init_wait_info(&winfo); + winfo.board = board; + winfo.usec_timeout = usec_timeout; + start_wait_timer(&winfo); + + if (wait_event_interruptible(board->wait, wait_satisfied(&winfo, status_queue, + wait_mask, status, desc))) { + dev_dbg(board->gpib_dev, "wait interrupted\n"); + retval = -ERESTARTSYS; + } + remove_wait_timer(&winfo); + + if (retval) + return retval; + if (mutex_lock_interruptible(&board->big_gpib_mutex)) + return -ERESTARTSYS; + + /* make sure we only clear status bits that we are reporting */ + if (*status & clear_mask || set_mask) + general_ibstatus(board, status_queue, *status & clear_mask, set_mask, NULL); + + return 0; +} + +/* + * IBWRT + * Write cnt bytes of data from buf to the GPIB. The write + * operation terminates only on I/O complete. + * + * NOTE: + * 1. Prior to beginning the write, the interface is + * placed in the controller standby state. + * 2. Prior to calling ibwrt, the intended devices as + * well as the interface board itself must be + * addressed by calling ibcmd. + */ +int ibwrt(struct gpib_board *board, u8 *buf, size_t cnt, int send_eoi, size_t *bytes_written) +{ + int ret = 0; + int retval; + + if (cnt == 0) + return 0; + + if (board->master) { + retval = ibgts(board); + if (retval < 0) + return retval; + } + os_start_timer(board, board->usec_timeout); + ret = board->interface->write(board, buf, cnt, send_eoi, bytes_written); + + if (io_timed_out(board)) + ret = -ETIMEDOUT; + + os_remove_timer(board); + + return ret; +} + diff --git a/drivers/gpib/common/ibsys.h b/drivers/gpib/common/ibsys.h new file mode 100644 index 000000000000..e5a148f513a8 --- /dev/null +++ b/drivers/gpib/common/ibsys.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#include "gpibP.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define MAX_GPIB_PRIMARY_ADDRESS 30 +#define MAX_GPIB_SECONDARY_ADDRESS 31 + +int gpib_allocate_board(struct gpib_board *board); +void gpib_deallocate_board(struct gpib_board *board); + +unsigned int num_status_bytes(const struct gpib_status_queue *dev); +int push_status_byte(struct gpib_board *board, struct gpib_status_queue *device, + u8 poll_byte); +int pop_status_byte(struct gpib_board *board, struct gpib_status_queue *device, + u8 *poll_byte); +struct gpib_status_queue *get_gpib_status_queue(struct gpib_board *board, + unsigned int pad, int sad); +int get_serial_poll_byte(struct gpib_board *board, unsigned int pad, int sad, + unsigned int usec_timeout, u8 *poll_byte); +int autopoll_all_devices(struct gpib_board *board); diff --git a/drivers/gpib/eastwood/Makefile b/drivers/gpib/eastwood/Makefile new file mode 100644 index 000000000000..384825195f77 --- /dev/null +++ b/drivers/gpib/eastwood/Makefile @@ -0,0 +1,3 @@ + +obj-$(CONFIG_GPIB_FLUKE) += fluke_gpib.o + diff --git a/drivers/gpib/eastwood/fluke_gpib.c b/drivers/gpib/eastwood/fluke_gpib.c new file mode 100644 index 000000000000..3ae848e3f738 --- /dev/null +++ b/drivers/gpib/eastwood/fluke_gpib.c @@ -0,0 +1,1180 @@ +// SPDX-License-Identifier: GPL-2.0 + +/*************************************************************************** + * GPIB Driver for Fluke cda devices. Basically, its a driver for a (bugfixed) + * cb7210 connected to channel 0 of a pl330 dma controller. + * Author: Frank Mori Hess + * copyright: (C) 2006, 2010, 2015 Fluke Corporation + ***************************************************************************/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#define dev_fmt pr_fmt +#define DRV_NAME KBUILD_MODNAME + +#include "fluke_gpib.h" + +#include "gpibP.h" +#include +#include +#include +#include +#include +#include + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("GPIB Driver for Fluke cda devices"); + +static int fluke_attach_holdoff_all(struct gpib_board *board, + const struct gpib_board_config *config); +static int fluke_attach_holdoff_end(struct gpib_board *board, + const struct gpib_board_config *config); +static void fluke_detach(struct gpib_board *board); +static int fluke_config_dma(struct gpib_board *board, int output); +static irqreturn_t fluke_gpib_internal_interrupt(struct gpib_board *board); + +static struct platform_device *fluke_gpib_pdev; + +static u8 fluke_locking_read_byte(struct nec7210_priv *nec_priv, unsigned int register_number) +{ + u8 retval; + unsigned long flags; + + spin_lock_irqsave(&nec_priv->register_page_lock, flags); + retval = fluke_read_byte_nolock(nec_priv, register_number); + spin_unlock_irqrestore(&nec_priv->register_page_lock, flags); + return retval; +} + +static void fluke_locking_write_byte(struct nec7210_priv *nec_priv, u8 byte, + unsigned int register_number) +{ + unsigned long flags; + + spin_lock_irqsave(&nec_priv->register_page_lock, flags); + fluke_write_byte_nolock(nec_priv, byte, register_number); + spin_unlock_irqrestore(&nec_priv->register_page_lock, flags); +} + +// wrappers for interface functions +static int fluke_read(struct gpib_board *board, u8 *buffer, size_t length, int *end, + size_t *bytes_read) +{ + struct fluke_priv *priv = board->private_data; + + return nec7210_read(board, &priv->nec7210_priv, buffer, length, end, bytes_read); +} + +static int fluke_write(struct gpib_board *board, u8 *buffer, size_t length, + int send_eoi, size_t *bytes_written) +{ + struct fluke_priv *priv = board->private_data; + + return nec7210_write(board, &priv->nec7210_priv, buffer, length, send_eoi, bytes_written); +} + +static int fluke_command(struct gpib_board *board, u8 *buffer, + size_t length, size_t *bytes_written) +{ + struct fluke_priv *priv = board->private_data; + + return nec7210_command(board, &priv->nec7210_priv, buffer, length, bytes_written); +} + +static int fluke_take_control(struct gpib_board *board, int synchronous) +{ + struct fluke_priv *priv = board->private_data; + + return nec7210_take_control(board, &priv->nec7210_priv, synchronous); +} + +static int fluke_go_to_standby(struct gpib_board *board) +{ + struct fluke_priv *priv = board->private_data; + + return nec7210_go_to_standby(board, &priv->nec7210_priv); +} + +static int fluke_request_system_control(struct gpib_board *board, int request_control) +{ + struct fluke_priv *priv = board->private_data; + struct nec7210_priv *nec_priv = &priv->nec7210_priv; + + return nec7210_request_system_control(board, nec_priv, request_control); +} + +static void fluke_interface_clear(struct gpib_board *board, int assert) +{ + struct fluke_priv *priv = board->private_data; + + nec7210_interface_clear(board, &priv->nec7210_priv, assert); +} + +static void fluke_remote_enable(struct gpib_board *board, int enable) +{ + struct fluke_priv *priv = board->private_data; + + nec7210_remote_enable(board, &priv->nec7210_priv, enable); +} + +static int fluke_enable_eos(struct gpib_board *board, u8 eos_byte, int compare_8_bits) +{ + struct fluke_priv *priv = board->private_data; + + return nec7210_enable_eos(board, &priv->nec7210_priv, eos_byte, compare_8_bits); +} + +static void fluke_disable_eos(struct gpib_board *board) +{ + struct fluke_priv *priv = board->private_data; + + nec7210_disable_eos(board, &priv->nec7210_priv); +} + +static unsigned int fluke_update_status(struct gpib_board *board, unsigned int clear_mask) +{ + struct fluke_priv *priv = board->private_data; + + return nec7210_update_status(board, &priv->nec7210_priv, clear_mask); +} + +static int fluke_primary_address(struct gpib_board *board, unsigned int address) +{ + struct fluke_priv *priv = board->private_data; + + return nec7210_primary_address(board, &priv->nec7210_priv, address); +} + +static int fluke_secondary_address(struct gpib_board *board, unsigned int address, int enable) +{ + struct fluke_priv *priv = board->private_data; + + return nec7210_secondary_address(board, &priv->nec7210_priv, address, enable); +} + +static int fluke_parallel_poll(struct gpib_board *board, u8 *result) +{ + struct fluke_priv *priv = board->private_data; + + return nec7210_parallel_poll(board, &priv->nec7210_priv, result); +} + +static void fluke_parallel_poll_configure(struct gpib_board *board, u8 configuration) +{ + struct fluke_priv *priv = board->private_data; + + nec7210_parallel_poll_configure(board, &priv->nec7210_priv, configuration); +} + +static void fluke_parallel_poll_response(struct gpib_board *board, int ist) +{ + struct fluke_priv *priv = board->private_data; + + nec7210_parallel_poll_response(board, &priv->nec7210_priv, ist); +} + +static void fluke_serial_poll_response(struct gpib_board *board, u8 status) +{ + struct fluke_priv *priv = board->private_data; + + nec7210_serial_poll_response(board, &priv->nec7210_priv, status); +} + +static u8 fluke_serial_poll_status(struct gpib_board *board) +{ + struct fluke_priv *priv = board->private_data; + + return nec7210_serial_poll_status(board, &priv->nec7210_priv); +} + +static void fluke_return_to_local(struct gpib_board *board) +{ + struct fluke_priv *priv = board->private_data; + struct nec7210_priv *nec_priv = &priv->nec7210_priv; + + write_byte(nec_priv, AUX_RTL2, AUXMR); + udelay(1); + write_byte(nec_priv, AUX_RTL, AUXMR); +} + +static int fluke_line_status(const struct gpib_board *board) +{ + int status = VALID_ALL; + int bsr_bits; + struct fluke_priv *e_priv; + + e_priv = board->private_data; + + bsr_bits = fluke_paged_read_byte(e_priv, BUS_STATUS, BUS_STATUS_PAGE); + + if ((bsr_bits & BSR_REN_BIT) == 0) + status |= BUS_REN; + if ((bsr_bits & BSR_IFC_BIT) == 0) + status |= BUS_IFC; + if ((bsr_bits & BSR_SRQ_BIT) == 0) + status |= BUS_SRQ; + if ((bsr_bits & BSR_EOI_BIT) == 0) + status |= BUS_EOI; + if ((bsr_bits & BSR_NRFD_BIT) == 0) + status |= BUS_NRFD; + if ((bsr_bits & BSR_NDAC_BIT) == 0) + status |= BUS_NDAC; + if ((bsr_bits & BSR_DAV_BIT) == 0) + status |= BUS_DAV; + if ((bsr_bits & BSR_ATN_BIT) == 0) + status |= BUS_ATN; + + return status; +} + +static int fluke_t1_delay(struct gpib_board *board, unsigned int nano_sec) +{ + struct fluke_priv *e_priv = board->private_data; + struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; + unsigned int retval; + + retval = nec7210_t1_delay(board, nec_priv, nano_sec); + + if (nano_sec <= 350) { + write_byte(nec_priv, AUX_HI_SPEED, AUXMR); + retval = 350; + } else { + write_byte(nec_priv, AUX_LO_SPEED, AUXMR); + } + return retval; +} + +static int lacs_or_read_ready(struct gpib_board *board) +{ + const struct fluke_priv *e_priv = board->private_data; + const struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; + unsigned long flags; + int retval; + + spin_lock_irqsave(&board->spinlock, flags); + retval = test_bit(LACS_NUM, &board->status) || test_bit(READ_READY_BN, &nec_priv->state); + spin_unlock_irqrestore(&board->spinlock, flags); + return retval; +} + +/* + * Wait until it is possible for a read to do something useful. This + * is not essential, it only exists to prevent RFD holdoff from being released pointlessly. + */ +static int wait_for_read(struct gpib_board *board) +{ + struct fluke_priv *e_priv = board->private_data; + struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; + int retval = 0; + + if (wait_event_interruptible(board->wait, + lacs_or_read_ready(board) || + test_bit(DEV_CLEAR_BN, &nec_priv->state) || + test_bit(TIMO_NUM, &board->status))) + retval = -ERESTARTSYS; + + if (test_bit(TIMO_NUM, &board->status)) + retval = -ETIMEDOUT; + if (test_and_clear_bit(DEV_CLEAR_BN, &nec_priv->state)) + retval = -EINTR; + return retval; +} + +/* + * Check if the SH state machine is in SGNS. We check twice since there is a very small chance + * we could be blowing through SGNS from SIDS to SDYS if there is already a + * byte available in the handshake state machine. We are interested + * in the case where the handshake is stuck in SGNS due to no byte being + * available to the chip (and thus we can be confident a dma transfer will + * result in at least one byte making it into the chip). This matters + * because we want to be confident before sending a "send eoi" auxilary + * command that we will be able to also put the associated data byte + * in the chip before any potential timeout. + */ +static int source_handshake_is_sgns(struct fluke_priv *e_priv) +{ + int i; + + for (i = 0; i < 2; ++i) { + if ((fluke_paged_read_byte(e_priv, STATE1_REG, STATE1_PAGE) & + SOURCE_HANDSHAKE_MASK) != SOURCE_HANDSHAKE_SGNS_BITS) { + return 0; + } + } + return 1; +} + +static int source_handshake_is_sids_or_sgns(struct fluke_priv *e_priv) +{ + unsigned int source_handshake_bits; + + source_handshake_bits = fluke_paged_read_byte(e_priv, STATE1_REG, STATE1_PAGE) & + SOURCE_HANDSHAKE_MASK; + + return (source_handshake_bits == SOURCE_HANDSHAKE_SGNS_BITS) || + (source_handshake_bits == SOURCE_HANDSHAKE_SIDS_BITS); +} + +/* + * Wait until the gpib chip is ready to accept a data out byte. + * If the chip is SGNS it is probably waiting for a a byte to + * be written to it. + */ +static int wait_for_data_out_ready(struct gpib_board *board) +{ + struct fluke_priv *e_priv = board->private_data; + struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; + int retval = 0; + + if (wait_event_interruptible(board->wait, + (test_bit(TACS_NUM, &board->status) && + source_handshake_is_sgns(e_priv)) || + test_bit(DEV_CLEAR_BN, &nec_priv->state) || + test_bit(TIMO_NUM, &board->status))) + retval = -ERESTARTSYS; + if (test_bit(TIMO_NUM, &board->status)) + retval = -ETIMEDOUT; + if (test_and_clear_bit(DEV_CLEAR_BN, &nec_priv->state)) + retval = -EINTR; + return retval; +} + +static int wait_for_sids_or_sgns(struct gpib_board *board) +{ + struct fluke_priv *e_priv = board->private_data; + struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; + int retval = 0; + + if (wait_event_interruptible(board->wait, + source_handshake_is_sids_or_sgns(e_priv) || + test_bit(DEV_CLEAR_BN, &nec_priv->state) || + test_bit(TIMO_NUM, &board->status))) + retval = -ERESTARTSYS; + + if (test_bit(TIMO_NUM, &board->status)) + retval = -ETIMEDOUT; + if (test_and_clear_bit(DEV_CLEAR_BN, &nec_priv->state)) + retval = -EINTR; + return retval; +} + +static void fluke_dma_callback(void *arg) +{ + struct gpib_board *board = arg; + struct fluke_priv *e_priv = board->private_data; + struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; + unsigned long flags; + + spin_lock_irqsave(&board->spinlock, flags); + + nec7210_set_reg_bits(nec_priv, IMR1, HR_DOIE | HR_DIIE, HR_DOIE | HR_DIIE); + wake_up_interruptible(&board->wait); + + fluke_gpib_internal_interrupt(board); + clear_bit(DMA_WRITE_IN_PROGRESS_BN, &nec_priv->state); + clear_bit(DMA_READ_IN_PROGRESS_BN, &nec_priv->state); + + spin_unlock_irqrestore(&board->spinlock, flags); +} + +static int fluke_dma_write(struct gpib_board *board, u8 *buffer, size_t length, + size_t *bytes_written) +{ + struct fluke_priv *e_priv = board->private_data; + struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; + unsigned long flags; + int retval = 0; + dma_addr_t address; + struct dma_async_tx_descriptor *tx_desc; + + *bytes_written = 0; + + if (WARN_ON_ONCE(length > e_priv->dma_buffer_size)) + return -EFAULT; + dmaengine_terminate_all(e_priv->dma_channel); + // write-clear counter + writel(0x0, e_priv->write_transfer_counter); + + memcpy(e_priv->dma_buffer, buffer, length); + address = dma_map_single(board->dev, e_priv->dma_buffer, + length, DMA_TO_DEVICE); + /* program dma controller */ + retval = fluke_config_dma(board, 1); + if (retval) + goto cleanup; + + tx_desc = dmaengine_prep_slave_single(e_priv->dma_channel, address, length, DMA_MEM_TO_DEV, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!tx_desc) { + dev_err(board->gpib_dev, "failed to allocate dma transmit descriptor\n"); + retval = -ENOMEM; + goto cleanup; + } + tx_desc->callback = fluke_dma_callback; + tx_desc->callback_param = board; + + spin_lock_irqsave(&board->spinlock, flags); + nec7210_set_reg_bits(nec_priv, IMR1, HR_DOIE, 0); + nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAO, HR_DMAO); + dmaengine_submit(tx_desc); + dma_async_issue_pending(e_priv->dma_channel); + + clear_bit(WRITE_READY_BN, &nec_priv->state); + set_bit(DMA_WRITE_IN_PROGRESS_BN, &nec_priv->state); + + spin_unlock_irqrestore(&board->spinlock, flags); + + // suspend until message is sent + if (wait_event_interruptible(board->wait, + ((readl(e_priv->write_transfer_counter) & + write_transfer_counter_mask) == length) || + test_bit(BUS_ERROR_BN, &nec_priv->state) || + test_bit(DEV_CLEAR_BN, &nec_priv->state) || + test_bit(TIMO_NUM, &board->status))) { + retval = -ERESTARTSYS; + } + if (test_bit(TIMO_NUM, &board->status)) + retval = -ETIMEDOUT; + if (test_and_clear_bit(DEV_CLEAR_BN, &nec_priv->state)) + retval = -EINTR; + if (test_and_clear_bit(BUS_ERROR_BN, &nec_priv->state)) + retval = -EIO; + // disable board's dma + nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAO, 0); + + dmaengine_terminate_all(e_priv->dma_channel); + // make sure fluke_dma_callback got called + if (test_bit(DMA_WRITE_IN_PROGRESS_BN, &nec_priv->state)) + fluke_dma_callback(board); + + /* + * if everything went fine, try to wait until last byte is actually + * transmitted across gpib (but don't try _too_ hard) + */ + if (retval == 0) + retval = wait_for_sids_or_sgns(board); + + *bytes_written = readl(e_priv->write_transfer_counter) & write_transfer_counter_mask; + if (WARN_ON_ONCE(*bytes_written > length)) + return -EFAULT; + +cleanup: + dma_unmap_single(board->dev, address, length, DMA_TO_DEVICE); + return retval; +} + +static int fluke_accel_write(struct gpib_board *board, u8 *buffer, size_t length, + int send_eoi, size_t *bytes_written) +{ + struct fluke_priv *e_priv = board->private_data; + struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; + size_t remainder = length; + size_t transfer_size; + ssize_t retval = 0; + size_t dma_remainder = remainder; + + if (!e_priv->dma_channel) { + dev_err(board->gpib_dev, "No dma channel available, cannot do accel write."); + return -ENXIO; + } + + *bytes_written = 0; + if (length < 1) + return 0; + + clear_bit(DEV_CLEAR_BN, &nec_priv->state); // XXX FIXME + + if (send_eoi) + --dma_remainder; + + while (dma_remainder > 0) { + size_t num_bytes; + + retval = wait_for_data_out_ready(board); + if (retval < 0) + break; + + transfer_size = (e_priv->dma_buffer_size < dma_remainder) ? + e_priv->dma_buffer_size : dma_remainder; + retval = fluke_dma_write(board, buffer, transfer_size, &num_bytes); + *bytes_written += num_bytes; + if (retval < 0) + break; + dma_remainder -= num_bytes; + remainder -= num_bytes; + buffer += num_bytes; + if (need_resched()) + schedule(); + } + if (retval < 0) + return retval; + // handle sending of last byte with eoi + if (send_eoi) { + size_t num_bytes; + + if (WARN_ON_ONCE(remainder != 1)) + return -EFAULT; + + /* + * wait until we are sure we will be able to write the data byte + * into the chip before we send AUX_SEOI. This prevents a timeout + * scenerio where we send AUX_SEOI but then timeout without getting + * any bytes into the gpib chip. This will result in the first byte + * of the next write having a spurious EOI set on the first byte. + */ + retval = wait_for_data_out_ready(board); + if (retval < 0) + return retval; + + write_byte(nec_priv, AUX_SEOI, AUXMR); + retval = fluke_dma_write(board, buffer, remainder, &num_bytes); + *bytes_written += num_bytes; + if (retval < 0) + return retval; + remainder -= num_bytes; + } + return 0; +} + +static int fluke_get_dma_residue(struct dma_chan *chan, dma_cookie_t cookie) +{ + struct dma_tx_state state; + int result; + + result = dmaengine_pause(chan); + if (result < 0) { + pr_err("dma pause failed?\n"); + return result; + } + dmaengine_tx_status(chan, cookie, &state); + /* + * hardware doesn't support resume, so dont call this + * method unless the dma transfer is done. + */ + return state.residue; +} + +static int fluke_dma_read(struct gpib_board *board, u8 *buffer, + size_t length, int *end, size_t *bytes_read) +{ + struct fluke_priv *e_priv = board->private_data; + struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; + int retval = 0; + unsigned long flags; + int residue; + dma_addr_t bus_address; + struct dma_async_tx_descriptor *tx_desc; + dma_cookie_t dma_cookie; + int i; + static const int timeout = 10; + + *bytes_read = 0; + *end = 0; + if (length == 0) + return 0; + + bus_address = dma_map_single(board->dev, e_priv->dma_buffer, + length, DMA_FROM_DEVICE); + + /* program dma controller */ + retval = fluke_config_dma(board, 0); + if (retval) { + dma_unmap_single(board->dev, bus_address, length, DMA_FROM_DEVICE); + return retval; + } + tx_desc = dmaengine_prep_slave_single(e_priv->dma_channel, + bus_address, length, DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!tx_desc) { + dev_err(board->gpib_dev, "failed to allocate dma transmit descriptor\n"); + dma_unmap_single(NULL, bus_address, length, DMA_FROM_DEVICE); + return -EIO; + } + tx_desc->callback = fluke_dma_callback; + tx_desc->callback_param = board; + + spin_lock_irqsave(&board->spinlock, flags); + // enable nec7210 dma + nec7210_set_reg_bits(nec_priv, IMR1, HR_DIIE, 0); + nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAI, HR_DMAI); + + dma_cookie = dmaengine_submit(tx_desc); + dma_async_issue_pending(e_priv->dma_channel); + + set_bit(DMA_READ_IN_PROGRESS_BN, &nec_priv->state); + clear_bit(READ_READY_BN, &nec_priv->state); + + spin_unlock_irqrestore(&board->spinlock, flags); + // wait for data to transfer + if (wait_event_interruptible(board->wait, + test_bit(DMA_READ_IN_PROGRESS_BN, &nec_priv->state) == 0 || + test_bit(RECEIVED_END_BN, &nec_priv->state) || + test_bit(DEV_CLEAR_BN, &nec_priv->state) || + test_bit(TIMO_NUM, &board->status))) { + retval = -ERESTARTSYS; + } + if (test_bit(TIMO_NUM, &board->status)) + retval = -ETIMEDOUT; + if (test_bit(DEV_CLEAR_BN, &nec_priv->state)) + retval = -EINTR; + + /* + * If we woke up because of end, wait until the dma transfer has pulled + * the data byte associated with the end before we cancel the dma transfer. + */ + if (test_bit(RECEIVED_END_BN, &nec_priv->state)) { + for (i = 0; i < timeout; ++i) { + if (test_bit(DMA_READ_IN_PROGRESS_BN, &nec_priv->state) == 0) + break; + if ((read_byte(nec_priv, ADR0) & DATA_IN_STATUS) == 0) + break; + usleep_range(10, 15); + } + if (i == timeout) + pr_warn("fluke_gpib: timeout waiting for dma to transfer end data byte.\n"); + } + + // stop the dma transfer + nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAI, 0); + /* + * delay a little just to make sure any bytes in dma controller's fifo get + * written to memory before we disable it + */ + usleep_range(10, 15); + residue = fluke_get_dma_residue(e_priv->dma_channel, dma_cookie); + if (WARN_ON_ONCE(residue > length || residue < 0)) + return -EFAULT; + *bytes_read += length - residue; + dmaengine_terminate_all(e_priv->dma_channel); + // make sure fluke_dma_callback got called + if (test_bit(DMA_READ_IN_PROGRESS_BN, &nec_priv->state)) + fluke_dma_callback(board); + + dma_unmap_single(board->dev, bus_address, length, DMA_FROM_DEVICE); + memcpy(buffer, e_priv->dma_buffer, *bytes_read); + + /* + * If we got an end interrupt, figure out if it was + * associated with the last byte we dma'd or with a + * byte still sitting on the cb7210. + */ + spin_lock_irqsave(&board->spinlock, flags); + if (test_bit(READ_READY_BN, &nec_priv->state) == 0) { + /* + * There is no byte sitting on the cb7210. If we + * saw an end interrupt, we need to deal with it now + */ + if (test_and_clear_bit(RECEIVED_END_BN, &nec_priv->state)) + *end = 1; + } + spin_unlock_irqrestore(&board->spinlock, flags); + + return retval; +} + +static int fluke_accel_read(struct gpib_board *board, u8 *buffer, size_t length, + int *end, size_t *bytes_read) +{ + struct fluke_priv *e_priv = board->private_data; + struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; + size_t remain = length; + size_t transfer_size; + int retval = 0; + size_t dma_nbytes; + + *end = 0; + *bytes_read = 0; + + smp_mb__before_atomic(); + clear_bit(DEV_CLEAR_BN, &nec_priv->state); // XXX FIXME + smp_mb__after_atomic(); + + retval = wait_for_read(board); + if (retval < 0) + return retval; + + nec7210_release_rfd_holdoff(board, nec_priv); + + while (remain > 0) { + transfer_size = (e_priv->dma_buffer_size < remain) ? + e_priv->dma_buffer_size : remain; + retval = fluke_dma_read(board, buffer, transfer_size, end, &dma_nbytes); + remain -= dma_nbytes; + buffer += dma_nbytes; + *bytes_read += dma_nbytes; + if (*end) + break; + if (retval < 0) + return retval; + if (need_resched()) + schedule(); + } + + return retval; +} + +static struct gpib_interface fluke_unaccel_interface = { + .name = "fluke_unaccel", + .attach = fluke_attach_holdoff_all, + .detach = fluke_detach, + .read = fluke_read, + .write = fluke_write, + .command = fluke_command, + .take_control = fluke_take_control, + .go_to_standby = fluke_go_to_standby, + .request_system_control = fluke_request_system_control, + .interface_clear = fluke_interface_clear, + .remote_enable = fluke_remote_enable, + .enable_eos = fluke_enable_eos, + .disable_eos = fluke_disable_eos, + .parallel_poll = fluke_parallel_poll, + .parallel_poll_configure = fluke_parallel_poll_configure, + .parallel_poll_response = fluke_parallel_poll_response, + .line_status = fluke_line_status, + .update_status = fluke_update_status, + .primary_address = fluke_primary_address, + .secondary_address = fluke_secondary_address, + .serial_poll_response = fluke_serial_poll_response, + .serial_poll_status = fluke_serial_poll_status, + .t1_delay = fluke_t1_delay, + .return_to_local = fluke_return_to_local, +}; + +/* + * fluke_hybrid uses dma for writes but not for reads. Added + * to deal with occasional corruption of bytes seen when doing dma + * reads. From looking at the cb7210 vhdl, I believe the corruption + * is due to a hardware bug triggered by the cpu reading a cb7210 + * } + * register just as the dma controller is also doing a read. + */ + +static struct gpib_interface fluke_hybrid_interface = { + .name = "fluke_hybrid", + .attach = fluke_attach_holdoff_all, + .detach = fluke_detach, + .read = fluke_read, + .write = fluke_accel_write, + .command = fluke_command, + .take_control = fluke_take_control, + .go_to_standby = fluke_go_to_standby, + .request_system_control = fluke_request_system_control, + .interface_clear = fluke_interface_clear, + .remote_enable = fluke_remote_enable, + .enable_eos = fluke_enable_eos, + .disable_eos = fluke_disable_eos, + .parallel_poll = fluke_parallel_poll, + .parallel_poll_configure = fluke_parallel_poll_configure, + .parallel_poll_response = fluke_parallel_poll_response, + .line_status = fluke_line_status, + .update_status = fluke_update_status, + .primary_address = fluke_primary_address, + .secondary_address = fluke_secondary_address, + .serial_poll_response = fluke_serial_poll_response, + .serial_poll_status = fluke_serial_poll_status, + .t1_delay = fluke_t1_delay, + .return_to_local = fluke_return_to_local, +}; + +static struct gpib_interface fluke_interface = { + .name = "fluke", + .attach = fluke_attach_holdoff_end, + .detach = fluke_detach, + .read = fluke_accel_read, + .write = fluke_accel_write, + .command = fluke_command, + .take_control = fluke_take_control, + .go_to_standby = fluke_go_to_standby, + .request_system_control = fluke_request_system_control, + .interface_clear = fluke_interface_clear, + .remote_enable = fluke_remote_enable, + .enable_eos = fluke_enable_eos, + .disable_eos = fluke_disable_eos, + .parallel_poll = fluke_parallel_poll, + .parallel_poll_configure = fluke_parallel_poll_configure, + .parallel_poll_response = fluke_parallel_poll_response, + .line_status = fluke_line_status, + .update_status = fluke_update_status, + .primary_address = fluke_primary_address, + .secondary_address = fluke_secondary_address, + .serial_poll_response = fluke_serial_poll_response, + .serial_poll_status = fluke_serial_poll_status, + .t1_delay = fluke_t1_delay, + .return_to_local = fluke_return_to_local, +}; + +irqreturn_t fluke_gpib_internal_interrupt(struct gpib_board *board) +{ + int status0, status1, status2; + struct fluke_priv *priv = board->private_data; + struct nec7210_priv *nec_priv = &priv->nec7210_priv; + int retval = IRQ_NONE; + + if (read_byte(nec_priv, ADR0) & DATA_IN_STATUS) + set_bit(READ_READY_BN, &nec_priv->state); + + status0 = fluke_paged_read_byte(priv, ISR0_IMR0, ISR0_IMR0_PAGE); + status1 = read_byte(nec_priv, ISR1); + status2 = read_byte(nec_priv, ISR2); + + if (status0 & FLUKE_IFCI_BIT) { + push_gpib_event(board, EVENT_IFC); + retval = IRQ_HANDLED; + } + + if (nec7210_interrupt_have_status(board, nec_priv, status1, status2) == IRQ_HANDLED) + retval = IRQ_HANDLED; + + if (read_byte(nec_priv, ADR0) & DATA_IN_STATUS) { + if (test_bit(RFD_HOLDOFF_BN, &nec_priv->state)) + set_bit(READ_READY_BN, &nec_priv->state); + else + clear_bit(READ_READY_BN, &nec_priv->state); + } + + if (retval == IRQ_HANDLED) + wake_up_interruptible(&board->wait); + + return retval; +} + +static irqreturn_t fluke_gpib_interrupt(int irq, void *arg) +{ + struct gpib_board *board = arg; + unsigned long flags; + irqreturn_t retval; + + spin_lock_irqsave(&board->spinlock, flags); + retval = fluke_gpib_internal_interrupt(board); + spin_unlock_irqrestore(&board->spinlock, flags); + return retval; +} + +static int fluke_allocate_private(struct gpib_board *board) +{ + struct fluke_priv *priv; + + board->private_data = kmalloc(sizeof(struct fluke_priv), GFP_KERNEL); + if (!board->private_data) + return -ENOMEM; + priv = board->private_data; + memset(priv, 0, sizeof(struct fluke_priv)); + init_nec7210_private(&priv->nec7210_priv); + priv->dma_buffer_size = 0x7ff; + priv->dma_buffer = kmalloc(priv->dma_buffer_size, GFP_KERNEL); + if (!priv->dma_buffer) + return -ENOMEM; + return 0; +} + +static void fluke_generic_detach(struct gpib_board *board) +{ + if (board->private_data) { + struct fluke_priv *e_priv = board->private_data; + + kfree(e_priv->dma_buffer); + kfree(board->private_data); + board->private_data = NULL; + } +} + +// generic part of attach functions shared by all cb7210 boards +static int fluke_generic_attach(struct gpib_board *board) +{ + struct fluke_priv *e_priv; + struct nec7210_priv *nec_priv; + int retval; + + board->status = 0; + + retval = fluke_allocate_private(board); + if (retval < 0) + return retval; + e_priv = board->private_data; + nec_priv = &e_priv->nec7210_priv; + nec_priv->read_byte = fluke_locking_read_byte; + nec_priv->write_byte = fluke_locking_write_byte; + nec_priv->offset = fluke_reg_offset; + nec_priv->type = CB7210; + return 0; +} + +static int fluke_config_dma(struct gpib_board *board, int output) +{ + struct fluke_priv *e_priv = board->private_data; + struct dma_slave_config config; + + config.src_maxburst = 1; + config.dst_maxburst = 1; + config.device_fc = true; + + if (output) { + config.direction = DMA_MEM_TO_DEV; + config.src_addr = 0; + config.dst_addr = e_priv->dma_port_res->start; + config.src_addr_width = 1; + config.dst_addr_width = 1; + } else { + config.direction = DMA_DEV_TO_MEM; + config.src_addr = e_priv->dma_port_res->start; + config.dst_addr = 0; + config.src_addr_width = 1; + config.dst_addr_width = 1; + } + return dmaengine_slave_config(e_priv->dma_channel, &config); +} + +static int fluke_init(struct fluke_priv *e_priv, struct gpib_board *board, int handshake_mode) +{ + struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; + + nec7210_board_reset(nec_priv, board); + write_byte(nec_priv, AUX_LO_SPEED, AUXMR); + /* + * set clock register for driving frequency + * ICR should be set to clock in megahertz (1-15) and to zero + * for clocks faster than 15 MHz (max 20MHz) + */ + write_byte(nec_priv, ICR | 10, AUXMR); + nec7210_set_handshake_mode(board, nec_priv, handshake_mode); + + nec7210_board_online(nec_priv, board); + + /* poll so we can detect ATN changes */ + if (gpib_request_pseudo_irq(board, fluke_gpib_interrupt)) { + dev_err(board->gpib_dev, "failed to allocate pseudo_irq\n"); + return -EINVAL; + } + + fluke_paged_write_byte(e_priv, FLUKE_IFCIE_BIT, ISR0_IMR0, ISR0_IMR0_PAGE); + return 0; +} + +/* + * This function is passed to dma_request_channel() in order to + * select the pl330 dma channel which has been hardwired to + * the gpib controller. + */ +static bool gpib_dma_channel_filter(struct dma_chan *chan, void *filter_param) +{ + // select the channel which is wired to the gpib chip + return chan->chan_id == 0; +} + +static int fluke_attach_impl(struct gpib_board *board, const struct gpib_board_config *config, + unsigned int handshake_mode) +{ + struct fluke_priv *e_priv; + struct nec7210_priv *nec_priv; + int isr_flags = 0; + int retval; + int irq; + struct resource *res; + dma_cap_mask_t dma_cap; + + if (!fluke_gpib_pdev) { + dev_err(board->gpib_dev, "No fluke device was found, attach failed.\n"); + return -ENODEV; + } + + retval = fluke_generic_attach(board); + if (retval) + return retval; + + e_priv = board->private_data; + nec_priv = &e_priv->nec7210_priv; + nec_priv->offset = fluke_reg_offset; + board->dev = &fluke_gpib_pdev->dev; + + res = platform_get_resource(fluke_gpib_pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&fluke_gpib_pdev->dev, "Unable to locate mmio resource\n"); + return -ENODEV; + } + + if (request_mem_region(res->start, + resource_size(res), + fluke_gpib_pdev->name) == NULL) { + dev_err(&fluke_gpib_pdev->dev, "cannot claim registers\n"); + return -ENXIO; + } + e_priv->gpib_iomem_res = res; + + nec_priv->mmiobase = ioremap(e_priv->gpib_iomem_res->start, + resource_size(e_priv->gpib_iomem_res)); + if (!nec_priv->mmiobase) { + dev_err(&fluke_gpib_pdev->dev, "Could not map I/O memory\n"); + return -ENOMEM; + } + + res = platform_get_resource(fluke_gpib_pdev, IORESOURCE_MEM, 1); + if (!res) { + dev_err(&fluke_gpib_pdev->dev, "Unable to locate mmio resource for gpib dma port\n"); + return -ENODEV; + } + if (request_mem_region(res->start, + resource_size(res), + fluke_gpib_pdev->name) == NULL) { + dev_err(&fluke_gpib_pdev->dev, "cannot claim registers\n"); + return -ENXIO; + } + e_priv->dma_port_res = res; + + res = platform_get_resource(fluke_gpib_pdev, IORESOURCE_MEM, 2); + if (!res) { + dev_err(&fluke_gpib_pdev->dev, "Unable to locate mmio resource for write transfer counter\n"); + return -ENODEV; + } + + if (request_mem_region(res->start, + resource_size(res), + fluke_gpib_pdev->name) == NULL) { + dev_err(&fluke_gpib_pdev->dev, "cannot claim registers\n"); + return -ENXIO; + } + e_priv->write_transfer_counter_res = res; + + e_priv->write_transfer_counter = ioremap(e_priv->write_transfer_counter_res->start, + resource_size(e_priv->write_transfer_counter_res)); + if (!e_priv->write_transfer_counter) { + dev_err(&fluke_gpib_pdev->dev, "Could not map I/O memory\n"); + return -ENOMEM; + } + + irq = platform_get_irq(fluke_gpib_pdev, 0); + if (irq < 0) + return -EBUSY; + retval = request_irq(irq, fluke_gpib_interrupt, isr_flags, fluke_gpib_pdev->name, board); + if (retval) { + dev_err(&fluke_gpib_pdev->dev, + "cannot register interrupt handler err=%d\n", + retval); + return retval; + } + e_priv->irq = irq; + + dma_cap_zero(dma_cap); + dma_cap_set(DMA_SLAVE, dma_cap); + e_priv->dma_channel = dma_request_channel(dma_cap, gpib_dma_channel_filter, NULL); + if (!e_priv->dma_channel) { + dev_err(board->gpib_dev, "failed to allocate a dma channel.\n"); + /* + * we don't error out here because unaccel interface will still + * work without dma + */ + } + + return fluke_init(e_priv, board, handshake_mode); +} + +int fluke_attach_holdoff_all(struct gpib_board *board, const struct gpib_board_config *config) +{ + return fluke_attach_impl(board, config, HR_HLDA); +} + +int fluke_attach_holdoff_end(struct gpib_board *board, const struct gpib_board_config *config) +{ + return fluke_attach_impl(board, config, HR_HLDE); +} + +void fluke_detach(struct gpib_board *board) +{ + struct fluke_priv *e_priv = board->private_data; + struct nec7210_priv *nec_priv; + + if (e_priv) { + if (e_priv->dma_channel) + dma_release_channel(e_priv->dma_channel); + gpib_free_pseudo_irq(board); + nec_priv = &e_priv->nec7210_priv; + + if (nec_priv->mmiobase) { + fluke_paged_write_byte(e_priv, 0, ISR0_IMR0, ISR0_IMR0_PAGE); + nec7210_board_reset(nec_priv, board); + } + if (e_priv->irq) + free_irq(e_priv->irq, board); + if (e_priv->write_transfer_counter_res) { + release_mem_region(e_priv->write_transfer_counter_res->start, + resource_size(e_priv->write_transfer_counter_res)); + } + if (e_priv->dma_port_res) { + release_mem_region(e_priv->dma_port_res->start, + resource_size(e_priv->dma_port_res)); + } + if (e_priv->gpib_iomem_res) + release_mem_region(e_priv->gpib_iomem_res->start, + resource_size(e_priv->gpib_iomem_res)); + } + fluke_generic_detach(board); +} + +static int fluke_gpib_probe(struct platform_device *pdev) +{ + fluke_gpib_pdev = pdev; + return 0; +} + +static const struct of_device_id fluke_gpib_of_match[] = { + { .compatible = "flk,fgpib-4.0"}, + { {0} } +}; +MODULE_DEVICE_TABLE(of, fluke_gpib_of_match); + +static struct platform_driver fluke_gpib_platform_driver = { + .driver = { + .name = DRV_NAME, + .of_match_table = fluke_gpib_of_match, + }, + .probe = &fluke_gpib_probe +}; + +static int __init fluke_init_module(void) +{ + int result; + + result = platform_driver_register(&fluke_gpib_platform_driver); + if (result) { + pr_err("platform_driver_register failed: error = %d\n", result); + return result; + } + + result = gpib_register_driver(&fluke_unaccel_interface, THIS_MODULE); + if (result) { + pr_err("gpib_register_driver failed: error = %d\n", result); + goto err_unaccel; + } + + result = gpib_register_driver(&fluke_hybrid_interface, THIS_MODULE); + if (result) { + pr_err("gpib_register_driver failed: error = %d\n", result); + goto err_hybrid; + } + + result = gpib_register_driver(&fluke_interface, THIS_MODULE); + if (result) { + pr_err("gpib_register_driver failed: error = %d\n", result); + goto err_interface; + } + + return 0; + +err_interface: + gpib_unregister_driver(&fluke_hybrid_interface); +err_hybrid: + gpib_unregister_driver(&fluke_unaccel_interface); +err_unaccel: + platform_driver_unregister(&fluke_gpib_platform_driver); + + return result; +} + +static void __exit fluke_exit_module(void) +{ + gpib_unregister_driver(&fluke_unaccel_interface); + gpib_unregister_driver(&fluke_hybrid_interface); + gpib_unregister_driver(&fluke_interface); + platform_driver_unregister(&fluke_gpib_platform_driver); +} + +module_init(fluke_init_module); +module_exit(fluke_exit_module); diff --git a/drivers/gpib/eastwood/fluke_gpib.h b/drivers/gpib/eastwood/fluke_gpib.h new file mode 100644 index 000000000000..493c200d0bbf --- /dev/null +++ b/drivers/gpib/eastwood/fluke_gpib.h @@ -0,0 +1,146 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/*************************************************************************** + * Author: Frank Mori Hess + * copyright: (C) 2006, 2010, 2015 Fluke Corporation + ***************************************************************************/ + +#include +#include +#include +#include +#include +#include "nec7210.h" + +struct fluke_priv { + struct nec7210_priv nec7210_priv; + struct resource *gpib_iomem_res; + struct resource *write_transfer_counter_res; + struct resource *dma_port_res; + int irq; + struct dma_chan *dma_channel; + u8 *dma_buffer; + int dma_buffer_size; + void __iomem *write_transfer_counter; +}; + +// cb7210 specific registers and bits +enum cb7210_regs { + STATE1_REG = 0x4, + ISR0_IMR0 = 0x6, + BUS_STATUS = 0x7 +}; + +enum cb7210_page_in { + ISR0_IMR0_PAGE = 1, + BUS_STATUS_PAGE = 1, + STATE1_PAGE = 1 +}; + +/* IMR0 -- Interrupt Mode Register 0 */ +enum imr0_bits { + FLUKE_IFCIE_BIT = 0x8, /* interface clear interrupt */ +}; + +/* ISR0 -- Interrupt Status Register 0 */ +enum isr0_bits { + FLUKE_IFCI_BIT = 0x8, /* interface clear interrupt */ +}; + +enum state1_bits { + SOURCE_HANDSHAKE_SIDS_BITS = 0x0, /* source idle state */ + SOURCE_HANDSHAKE_SGNS_BITS = 0x1, /* source generate state */ + SOURCE_HANDSHAKE_SDYS_BITS = 0x2, /* source delay state */ + SOURCE_HANDSHAKE_STRS_BITS = 0x5, /* source transfer state */ + SOURCE_HANDSHAKE_MASK = 0x7 +}; + +/* + * we customized the cb7210 vhdl to give the "data in" status + * on the unused bit 7 of the address0 register. + */ +enum cb7210_address0 { + DATA_IN_STATUS = 0x80 +}; + +static inline int cb7210_page_in_bits(unsigned int page) +{ + return 0x50 | (page & 0xf); +} + +// don't use without locking nec_priv->register_page_lock +static inline u8 fluke_read_byte_nolock(struct nec7210_priv *nec_priv, + int register_num) +{ + u8 retval; + + retval = readl(nec_priv->mmiobase + register_num * nec_priv->offset); + return retval; +} + +// don't use without locking nec_priv->register_page_lock +static inline void fluke_write_byte_nolock(struct nec7210_priv *nec_priv, u8 data, + int register_num) +{ + writel(data, nec_priv->mmiobase + register_num * nec_priv->offset); +} + +static inline u8 fluke_paged_read_byte(struct fluke_priv *e_priv, + unsigned int register_num, unsigned int page) +{ + struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; + u8 retval; + unsigned long flags; + + spin_lock_irqsave(&nec_priv->register_page_lock, flags); + fluke_write_byte_nolock(nec_priv, cb7210_page_in_bits(page), AUXMR); + udelay(1); + /* chip auto clears the page after a read */ + retval = fluke_read_byte_nolock(nec_priv, register_num); + spin_unlock_irqrestore(&nec_priv->register_page_lock, flags); + return retval; +} + +static inline void fluke_paged_write_byte(struct fluke_priv *e_priv, u8 data, + unsigned int register_num, unsigned int page) +{ + struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; + unsigned long flags; + + spin_lock_irqsave(&nec_priv->register_page_lock, flags); + fluke_write_byte_nolock(nec_priv, cb7210_page_in_bits(page), AUXMR); + udelay(1); + fluke_write_byte_nolock(nec_priv, data, register_num); + spin_unlock_irqrestore(&nec_priv->register_page_lock, flags); +} + +enum bus_status_bits { + BSR_ATN_BIT = 0x1, + BSR_EOI_BIT = 0x2, + BSR_SRQ_BIT = 0x4, + BSR_IFC_BIT = 0x8, + BSR_REN_BIT = 0x10, + BSR_DAV_BIT = 0x20, + BSR_NRFD_BIT = 0x40, + BSR_NDAC_BIT = 0x80, +}; + +enum cb7210_aux_cmds { +/* + * AUX_RTL2 is an undocumented aux command which causes cb7210 to assert + * (and keep asserted) local rtl message. This is used in conjunction + * with the (stupid) cb7210 implementation + * of the normal nec7210 AUX_RTL aux command, which + * causes the rtl message to toggle between on and off. + */ + AUX_RTL2 = 0xd, + AUX_NBAF = 0xe, // new byte available false (also clears seoi) + AUX_LO_SPEED = 0x40, + AUX_HI_SPEED = 0x41, +}; + +enum { + fluke_reg_offset = 4, + fluke_num_regs = 8, + write_transfer_counter_mask = 0x7ff, +}; diff --git a/drivers/gpib/fmh_gpib/Makefile b/drivers/gpib/fmh_gpib/Makefile new file mode 100644 index 000000000000..cc4d9e7cd5cd --- /dev/null +++ b/drivers/gpib/fmh_gpib/Makefile @@ -0,0 +1,2 @@ + +obj-$(CONFIG_GPIB_FMH) += fmh_gpib.o diff --git a/drivers/gpib/fmh_gpib/fmh_gpib.c b/drivers/gpib/fmh_gpib/fmh_gpib.c new file mode 100644 index 000000000000..f7bfb4a8e553 --- /dev/null +++ b/drivers/gpib/fmh_gpib/fmh_gpib.c @@ -0,0 +1,1754 @@ +// SPDX-License-Identifier: GPL-2.0 + +/*************************************************************************** + * GPIB Driver for fmh_gpib_core, see + * https://github.com/fmhess/fmh_gpib_core + * + * More specifically, it is a driver for the hardware arrangement described by + * src/examples/fmh_gpib_top.vhd in the fmh_gpib_core repository. + * + * Author: Frank Mori Hess + * Copyright: (C) 2006, 2010, 2015 Fluke Corporation + * (C) 2017 Frank Mori Hess + ***************************************************************************/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#define dev_fmt pr_fmt +#define DRV_NAME KBUILD_MODNAME + +#include "fmh_gpib.h" + +#include "gpibP.h" +#include +#include +#include +#include +#include +#include +#include + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("GPIB Driver for fmh_gpib_core"); +MODULE_AUTHOR("Frank Mori Hess "); + +static irqreturn_t fmh_gpib_interrupt(int irq, void *arg); +static int fmh_gpib_attach_holdoff_all(struct gpib_board *board, + const struct gpib_board_config *config); +static int fmh_gpib_attach_holdoff_end(struct gpib_board *board, + const struct gpib_board_config *config); +static void fmh_gpib_detach(struct gpib_board *board); +static int fmh_gpib_pci_attach_holdoff_all(struct gpib_board *board, + const struct gpib_board_config *config); +static int fmh_gpib_pci_attach_holdoff_end(struct gpib_board *board, + const struct gpib_board_config *config); +static void fmh_gpib_pci_detach(struct gpib_board *board); +static int fmh_gpib_config_dma(struct gpib_board *board, int output); +static irqreturn_t fmh_gpib_internal_interrupt(struct gpib_board *board); +static struct platform_driver fmh_gpib_platform_driver; +static struct pci_driver fmh_gpib_pci_driver; + +// wrappers for interface functions +static int fmh_gpib_read(struct gpib_board *board, u8 *buffer, size_t length, + int *end, size_t *bytes_read) +{ + struct fmh_priv *priv = board->private_data; + + return nec7210_read(board, &priv->nec7210_priv, buffer, length, end, bytes_read); +} + +static int fmh_gpib_write(struct gpib_board *board, u8 *buffer, size_t length, + int send_eoi, size_t *bytes_written) +{ + struct fmh_priv *priv = board->private_data; + + return nec7210_write(board, &priv->nec7210_priv, buffer, length, send_eoi, bytes_written); +} + +static int fmh_gpib_command(struct gpib_board *board, u8 *buffer, size_t length, + size_t *bytes_written) +{ + struct fmh_priv *priv = board->private_data; + + return nec7210_command(board, &priv->nec7210_priv, buffer, length, bytes_written); +} + +static int fmh_gpib_take_control(struct gpib_board *board, int synchronous) +{ + struct fmh_priv *priv = board->private_data; + + return nec7210_take_control(board, &priv->nec7210_priv, synchronous); +} + +static int fmh_gpib_go_to_standby(struct gpib_board *board) +{ + struct fmh_priv *priv = board->private_data; + + return nec7210_go_to_standby(board, &priv->nec7210_priv); +} + +static int fmh_gpib_request_system_control(struct gpib_board *board, int request_control) +{ + struct fmh_priv *priv = board->private_data; + struct nec7210_priv *nec_priv = &priv->nec7210_priv; + + return nec7210_request_system_control(board, nec_priv, request_control); +} + +static void fmh_gpib_interface_clear(struct gpib_board *board, int assert) +{ + struct fmh_priv *priv = board->private_data; + + nec7210_interface_clear(board, &priv->nec7210_priv, assert); +} + +static void fmh_gpib_remote_enable(struct gpib_board *board, int enable) +{ + struct fmh_priv *priv = board->private_data; + + nec7210_remote_enable(board, &priv->nec7210_priv, enable); +} + +static int fmh_gpib_enable_eos(struct gpib_board *board, u8 eos_byte, int compare_8_bits) +{ + struct fmh_priv *priv = board->private_data; + + return nec7210_enable_eos(board, &priv->nec7210_priv, eos_byte, compare_8_bits); +} + +static void fmh_gpib_disable_eos(struct gpib_board *board) +{ + struct fmh_priv *priv = board->private_data; + + nec7210_disable_eos(board, &priv->nec7210_priv); +} + +static unsigned int fmh_gpib_update_status(struct gpib_board *board, unsigned int clear_mask) +{ + struct fmh_priv *priv = board->private_data; + + return nec7210_update_status(board, &priv->nec7210_priv, clear_mask); +} + +static int fmh_gpib_primary_address(struct gpib_board *board, unsigned int address) +{ + struct fmh_priv *priv = board->private_data; + + return nec7210_primary_address(board, &priv->nec7210_priv, address); +} + +static int fmh_gpib_secondary_address(struct gpib_board *board, unsigned int address, int enable) +{ + struct fmh_priv *priv = board->private_data; + + return nec7210_secondary_address(board, &priv->nec7210_priv, address, enable); +} + +static int fmh_gpib_parallel_poll(struct gpib_board *board, u8 *result) +{ + struct fmh_priv *priv = board->private_data; + + return nec7210_parallel_poll(board, &priv->nec7210_priv, result); +} + +static void fmh_gpib_parallel_poll_configure(struct gpib_board *board, u8 configuration) +{ + struct fmh_priv *priv = board->private_data; + + nec7210_parallel_poll_configure(board, &priv->nec7210_priv, configuration); +} + +static void fmh_gpib_parallel_poll_response(struct gpib_board *board, int ist) +{ + struct fmh_priv *priv = board->private_data; + + nec7210_parallel_poll_response(board, &priv->nec7210_priv, ist); +} + +static void fmh_gpib_local_parallel_poll_mode(struct gpib_board *board, int local) +{ + struct fmh_priv *priv = board->private_data; + + if (local) { + write_byte(&priv->nec7210_priv, AUX_I_REG | LOCAL_PPOLL_MODE_BIT, AUXMR); + } else { + /* + * For fmh_gpib_core, remote parallel poll config mode is unaffected by the + * state of the disable bit of the parallel poll register (unlike the tnt4882). + * So, we don't need to worry about that. + */ + write_byte(&priv->nec7210_priv, AUX_I_REG | 0x0, AUXMR); + } +} + +static void fmh_gpib_serial_poll_response2(struct gpib_board *board, u8 status, + int new_reason_for_service) +{ + struct fmh_priv *priv = board->private_data; + unsigned long flags; + const int MSS = status & request_service_bit; + const int reqt = MSS && new_reason_for_service; + const int reqf = MSS == 0; + + spin_lock_irqsave(&board->spinlock, flags); + if (reqt) { + priv->nec7210_priv.srq_pending = 1; + clear_bit(SPOLL_NUM, &board->status); + } else if (reqf) { + priv->nec7210_priv.srq_pending = 0; + } + + if (reqt) { + /* + * It may seem like a race to issue reqt before updating + * the status byte, but it is not. The chip does not + * issue the reqt until the SPMR is written to at + * a later time. + */ + write_byte(&priv->nec7210_priv, AUX_REQT, AUXMR); + } else if (reqf) { + write_byte(&priv->nec7210_priv, AUX_REQF, AUXMR); + } + /* + * We need to always zero bit 6 of the status byte before writing it to + * the SPMR to insure we are using + * serial poll mode SP1, and not accidentally triggering mode SP3. + */ + write_byte(&priv->nec7210_priv, status & ~request_service_bit, SPMR); + spin_unlock_irqrestore(&board->spinlock, flags); +} + +static u8 fmh_gpib_serial_poll_status(struct gpib_board *board) +{ + struct fmh_priv *priv = board->private_data; + + return nec7210_serial_poll_status(board, &priv->nec7210_priv); +} + +static void fmh_gpib_return_to_local(struct gpib_board *board) +{ + struct fmh_priv *priv = board->private_data; + struct nec7210_priv *nec_priv = &priv->nec7210_priv; + + write_byte(nec_priv, AUX_RTL2, AUXMR); + udelay(1); + write_byte(nec_priv, AUX_RTL, AUXMR); +} + +static int fmh_gpib_line_status(const struct gpib_board *board) +{ + int status = VALID_ALL; + int bsr_bits; + struct fmh_priv *e_priv; + struct nec7210_priv *nec_priv; + + e_priv = board->private_data; + nec_priv = &e_priv->nec7210_priv; + + bsr_bits = read_byte(nec_priv, BUS_STATUS_REG); + + if ((bsr_bits & BSR_REN_BIT) == 0) + status |= BUS_REN; + if ((bsr_bits & BSR_IFC_BIT) == 0) + status |= BUS_IFC; + if ((bsr_bits & BSR_SRQ_BIT) == 0) + status |= BUS_SRQ; + if ((bsr_bits & BSR_EOI_BIT) == 0) + status |= BUS_EOI; + if ((bsr_bits & BSR_NRFD_BIT) == 0) + status |= BUS_NRFD; + if ((bsr_bits & BSR_NDAC_BIT) == 0) + status |= BUS_NDAC; + if ((bsr_bits & BSR_DAV_BIT) == 0) + status |= BUS_DAV; + if ((bsr_bits & BSR_ATN_BIT) == 0) + status |= BUS_ATN; + + return status; +} + +static int fmh_gpib_t1_delay(struct gpib_board *board, unsigned int nano_sec) +{ + struct fmh_priv *e_priv = board->private_data; + struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; + unsigned int retval; + + retval = nec7210_t1_delay(board, nec_priv, nano_sec); + + if (nano_sec <= 350) { + write_byte(nec_priv, AUX_HI_SPEED, AUXMR); + retval = 350; + } else { + write_byte(nec_priv, AUX_LO_SPEED, AUXMR); + } + return retval; +} + +static int lacs_or_read_ready(struct gpib_board *board) +{ + const struct fmh_priv *e_priv = board->private_data; + const struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; + int retval = 0; + unsigned long flags; + + spin_lock_irqsave(&board->spinlock, flags); + retval = test_bit(LACS_NUM, &board->status) || + test_bit(READ_READY_BN, &nec_priv->state); + spin_unlock_irqrestore(&board->spinlock, flags); + + return retval; +} + +static int wait_for_read(struct gpib_board *board) +{ + struct fmh_priv *e_priv = board->private_data; + struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; + int retval = 0; + + if (wait_event_interruptible(board->wait, + lacs_or_read_ready(board) || + test_bit(DEV_CLEAR_BN, &nec_priv->state) || + test_bit(TIMO_NUM, &board->status))) + retval = -ERESTARTSYS; + + if (test_bit(TIMO_NUM, &board->status)) + retval = -ETIMEDOUT; + if (test_and_clear_bit(DEV_CLEAR_BN, &nec_priv->state)) + retval = -EINTR; + return retval; +} + +static int wait_for_rx_fifo_half_full_or_end(struct gpib_board *board) +{ + struct fmh_priv *e_priv = board->private_data; + struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; + int retval = 0; + + if (wait_event_interruptible(board->wait, + (fifos_read(e_priv, FIFO_CONTROL_STATUS_REG) & + RX_FIFO_HALF_FULL) || + test_bit(RECEIVED_END_BN, &nec_priv->state) || + test_bit(DEV_CLEAR_BN, &nec_priv->state) || + test_bit(TIMO_NUM, &board->status))) + retval = -ERESTARTSYS; + + if (test_bit(TIMO_NUM, &board->status)) + retval = -ETIMEDOUT; + if (test_and_clear_bit(DEV_CLEAR_BN, &nec_priv->state)) + retval = -EINTR; + return retval; +} + +/* + * Wait until the gpib chip is ready to accept a data out byte. + */ +static int wait_for_data_out_ready(struct gpib_board *board) +{ + struct fmh_priv *e_priv = board->private_data; + struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; + int retval = 0; + + if (wait_event_interruptible(board->wait, + (test_bit(TACS_NUM, &board->status) && + (read_byte(nec_priv, EXT_STATUS_1_REG) & + DATA_OUT_STATUS_BIT)) || + test_bit(DEV_CLEAR_BN, &nec_priv->state) || + test_bit(TIMO_NUM, &board->status))) + retval = -ERESTARTSYS; + + if (test_bit(TIMO_NUM, &board->status)) + retval = -ETIMEDOUT; + if (test_and_clear_bit(DEV_CLEAR_BN, &nec_priv->state)) + retval = -EINTR; + + return retval; +} + +static void fmh_gpib_dma_callback(void *arg) +{ + struct gpib_board *board = arg; + struct fmh_priv *e_priv = board->private_data; + struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; + unsigned long flags; + + spin_lock_irqsave(&board->spinlock, flags); + + nec7210_set_reg_bits(nec_priv, IMR1, HR_DOIE | HR_DIIE, HR_DOIE | HR_DIIE); + wake_up_interruptible(&board->wait); + + fmh_gpib_internal_interrupt(board); + + clear_bit(DMA_WRITE_IN_PROGRESS_BN, &nec_priv->state); + clear_bit(DMA_READ_IN_PROGRESS_BN, &nec_priv->state); + + spin_unlock_irqrestore(&board->spinlock, flags); +} + +/* + * returns true when all the bytes of a write have been transferred to + * the chip and successfully transferred out over the gpib bus. + */ +static int fmh_gpib_all_bytes_are_sent(struct fmh_priv *e_priv) +{ + if (fifos_read(e_priv, FIFO_XFER_COUNTER_REG) & fifo_xfer_counter_mask) + return 0; + + if ((read_byte(&e_priv->nec7210_priv, EXT_STATUS_1_REG) & DATA_OUT_STATUS_BIT) == 0) + return 0; + + return 1; +} + +static int fmh_gpib_dma_write(struct gpib_board *board, u8 *buffer, size_t length, + size_t *bytes_written) +{ + struct fmh_priv *e_priv = board->private_data; + struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; + unsigned long flags; + int retval = 0; + dma_addr_t address; + struct dma_async_tx_descriptor *tx_desc; + + *bytes_written = 0; + if (WARN_ON_ONCE(length > e_priv->dma_buffer_size)) + return -EFAULT; + dmaengine_terminate_all(e_priv->dma_channel); + memcpy(e_priv->dma_buffer, buffer, length); + address = dma_map_single(board->dev, e_priv->dma_buffer, length, DMA_TO_DEVICE); + if (dma_mapping_error(board->dev, address)) + dev_err(board->gpib_dev, "dma mapping error in dma write!\n"); + /* program dma controller */ + retval = fmh_gpib_config_dma(board, 1); + if (retval) + goto cleanup; + + tx_desc = dmaengine_prep_slave_single(e_priv->dma_channel, address, length, DMA_MEM_TO_DEV, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!tx_desc) { + dev_err(board->gpib_dev, "failed to allocate dma transmit descriptor\n"); + retval = -ENOMEM; + goto cleanup; + } + tx_desc->callback = fmh_gpib_dma_callback; + tx_desc->callback_param = board; + + spin_lock_irqsave(&board->spinlock, flags); + fifos_write(e_priv, length & fifo_xfer_counter_mask, FIFO_XFER_COUNTER_REG); + fifos_write(e_priv, TX_FIFO_DMA_REQUEST_ENABLE | TX_FIFO_CLEAR, FIFO_CONTROL_STATUS_REG); + nec7210_set_reg_bits(nec_priv, IMR1, HR_DOIE, 0); + nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAO, HR_DMAO); + + dmaengine_submit(tx_desc); + dma_async_issue_pending(e_priv->dma_channel); + clear_bit(WRITE_READY_BN, &nec_priv->state); + set_bit(DMA_WRITE_IN_PROGRESS_BN, &nec_priv->state); + + spin_unlock_irqrestore(&board->spinlock, flags); + + // suspend until message is sent + if (wait_event_interruptible(board->wait, + fmh_gpib_all_bytes_are_sent(e_priv) || + test_bit(BUS_ERROR_BN, &nec_priv->state) || + test_bit(DEV_CLEAR_BN, &nec_priv->state) || + test_bit(TIMO_NUM, &board->status))) + retval = -ERESTARTSYS; + + if (test_bit(TIMO_NUM, &board->status)) + retval = -ETIMEDOUT; + if (test_and_clear_bit(DEV_CLEAR_BN, &nec_priv->state)) + retval = -EINTR; + if (test_and_clear_bit(BUS_ERROR_BN, &nec_priv->state)) + retval = -EIO; + // disable board's dma + nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAO, 0); + fifos_write(e_priv, 0, FIFO_CONTROL_STATUS_REG); + + dmaengine_terminate_all(e_priv->dma_channel); + // make sure fmh_gpib_dma_callback got called + if (test_bit(DMA_WRITE_IN_PROGRESS_BN, &nec_priv->state)) + fmh_gpib_dma_callback(board); + + *bytes_written = length - (fifos_read(e_priv, FIFO_XFER_COUNTER_REG) & + fifo_xfer_counter_mask); + if (WARN_ON_ONCE(*bytes_written > length)) + return -EFAULT; +cleanup: + dma_unmap_single(board->dev, address, length, DMA_TO_DEVICE); + return retval; +} + +static int fmh_gpib_accel_write(struct gpib_board *board, u8 *buffer, + size_t length, int send_eoi, size_t *bytes_written) +{ + struct fmh_priv *e_priv = board->private_data; + struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; + size_t remainder = length; + size_t transfer_size; + ssize_t retval = 0; + size_t dma_remainder = remainder; + + if (!e_priv->dma_channel) { + dev_err(board->gpib_dev, "No dma channel available, cannot do accel write."); + return -ENXIO; + } + + *bytes_written = 0; + if (length < 1) + return 0; + + smp_mb__before_atomic(); + clear_bit(DEV_CLEAR_BN, &nec_priv->state); // XXX FIXME + smp_mb__after_atomic(); + + if (send_eoi) + --dma_remainder; + + while (dma_remainder > 0) { + size_t num_bytes; + + retval = wait_for_data_out_ready(board); + if (retval < 0) + break; + + transfer_size = (e_priv->dma_buffer_size < dma_remainder) ? + e_priv->dma_buffer_size : dma_remainder; + retval = fmh_gpib_dma_write(board, buffer, transfer_size, &num_bytes); + *bytes_written += num_bytes; + if (retval < 0) + break; + dma_remainder -= num_bytes; + remainder -= num_bytes; + buffer += num_bytes; + if (need_resched()) + schedule(); + } + if (retval < 0) + return retval; + // handle sending of last byte with eoi + if (send_eoi) { + size_t num_bytes; + + if (WARN_ON_ONCE(remainder != 1)) + return -EFAULT; + + /* + * wait until we are sure we will be able to write the data byte + * into the chip before we send AUX_SEOI. This prevents a timeout + * scenario where we send AUX_SEOI but then timeout without getting + * any bytes into the gpib chip. This will result in the first byte + * of the next write having a spurious EOI set on the first byte. + */ + retval = wait_for_data_out_ready(board); + if (retval < 0) + return retval; + + write_byte(nec_priv, AUX_SEOI, AUXMR); + retval = fmh_gpib_dma_write(board, buffer, remainder, &num_bytes); + *bytes_written += num_bytes; + if (retval < 0) + return retval; + remainder -= num_bytes; + } + return 0; +} + +static int fmh_gpib_get_dma_residue(struct dma_chan *chan, dma_cookie_t cookie) +{ + struct dma_tx_state state; + int result; + + result = dmaengine_pause(chan); + if (result < 0) { + pr_err("dma pause failed?\n"); + return result; + } + dmaengine_tx_status(chan, cookie, &state); + /* + * dma330 hardware doesn't support resume, so dont call this + * method unless the dma transfer is done. + */ + return state.residue; +} + +static int wait_for_tx_fifo_half_empty(struct gpib_board *board) +{ + struct fmh_priv *e_priv = board->private_data; + struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; + int retval = 0; + + if (wait_event_interruptible(board->wait, + (test_bit(TACS_NUM, &board->status) && + (fifos_read(e_priv, FIFO_CONTROL_STATUS_REG) & + TX_FIFO_HALF_EMPTY)) || + test_bit(DEV_CLEAR_BN, &nec_priv->state) || + test_bit(TIMO_NUM, &board->status))) + retval = -ERESTARTSYS; + + if (test_bit(TIMO_NUM, &board->status)) + retval = -ETIMEDOUT; + if (test_and_clear_bit(DEV_CLEAR_BN, &nec_priv->state)) + retval = -EINTR; + + return retval; +} + +/* + * supports writing a chunk of data whose length must fit into the hardware'd xfer counter, + * called in a loop by fmh_gpib_fifo_write() + */ +static int fmh_gpib_fifo_write_countable(struct gpib_board *board, u8 *buffer, + size_t length, int send_eoi, size_t *bytes_written) +{ + struct fmh_priv *e_priv = board->private_data; + struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; + int retval = 0; + unsigned int remainder; + + *bytes_written = 0; + if (WARN_ON_ONCE(length > fifo_xfer_counter_mask)) + return -EFAULT; + + fifos_write(e_priv, length & fifo_xfer_counter_mask, FIFO_XFER_COUNTER_REG); + fifos_write(e_priv, TX_FIFO_CLEAR, FIFO_CONTROL_STATUS_REG); + nec7210_set_reg_bits(nec_priv, IMR1, HR_DOIE, 0); + nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAO, HR_DMAO); + + remainder = length; + while (remainder > 0) { + int i; + + fifos_write(e_priv, TX_FIFO_HALF_EMPTY_INTERRUPT_ENABLE, FIFO_CONTROL_STATUS_REG); + retval = wait_for_tx_fifo_half_empty(board); + if (retval < 0) + goto cleanup; + + for (i = 0; i < fmh_gpib_half_fifo_size(e_priv) && remainder > 0; ++i) { + unsigned int data_value = *buffer; + + if (send_eoi && remainder == 1) + data_value |= FIFO_DATA_EOI_FLAG; + fifos_write(e_priv, data_value, FIFO_DATA_REG); + ++buffer; + --remainder; + } + } + + // suspend until last byte is sent + nec7210_set_reg_bits(nec_priv, IMR1, HR_DOIE, HR_DOIE); + if (wait_event_interruptible(board->wait, + fmh_gpib_all_bytes_are_sent(e_priv) || + test_bit(BUS_ERROR_BN, &nec_priv->state) || + test_bit(DEV_CLEAR_BN, &nec_priv->state) || + test_bit(TIMO_NUM, &board->status))) + retval = -ERESTARTSYS; + + if (test_bit(TIMO_NUM, &board->status)) + retval = -ETIMEDOUT; + if (test_and_clear_bit(DEV_CLEAR_BN, &nec_priv->state)) + retval = -EINTR; + if (test_and_clear_bit(BUS_ERROR_BN, &nec_priv->state)) + retval = -EIO; + +cleanup: + nec7210_set_reg_bits(nec_priv, IMR1, HR_DOIE, 0); + nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAO, 0); + fifos_write(e_priv, 0, FIFO_CONTROL_STATUS_REG); + + *bytes_written = length - (fifos_read(e_priv, FIFO_XFER_COUNTER_REG) & + fifo_xfer_counter_mask); + if (WARN_ON_ONCE(*bytes_written > length)) + return -EFAULT; + + return retval; +} + +static int fmh_gpib_fifo_write(struct gpib_board *board, u8 *buffer, size_t length, + int send_eoi, size_t *bytes_written) +{ + struct fmh_priv *e_priv = board->private_data; + struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; + size_t remainder = length; + size_t transfer_size; + ssize_t retval = 0; + + *bytes_written = 0; + if (length < 1) + return 0; + + clear_bit(DEV_CLEAR_BN, &nec_priv->state); // XXX FIXME + + while (remainder > 0) { + size_t num_bytes; + int last_pass; + + retval = wait_for_data_out_ready(board); + if (retval < 0) + break; + + if (fifo_xfer_counter_mask < remainder) { + // round transfer size to a multiple of half fifo size + transfer_size = (fifo_xfer_counter_mask / + fmh_gpib_half_fifo_size(e_priv)) * + fmh_gpib_half_fifo_size(e_priv); + last_pass = 0; + } else { + transfer_size = remainder; + last_pass = 1; + } + retval = fmh_gpib_fifo_write_countable(board, buffer, transfer_size, + last_pass && send_eoi, &num_bytes); + *bytes_written += num_bytes; + if (retval < 0) + break; + remainder -= num_bytes; + buffer += num_bytes; + if (need_resched()) + schedule(); + } + + return retval; +} + +static int fmh_gpib_dma_read(struct gpib_board *board, u8 *buffer, + size_t length, int *end, size_t *bytes_read) +{ + struct fmh_priv *e_priv = board->private_data; + struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; + int retval = 0; + unsigned long flags; + int residue; + int wait_retval; + dma_addr_t bus_address; + struct dma_async_tx_descriptor *tx_desc; + dma_cookie_t dma_cookie; + + *bytes_read = 0; + *end = 0; + if (length == 0) + return 0; + + bus_address = dma_map_single(board->dev, e_priv->dma_buffer, + length, DMA_FROM_DEVICE); + if (dma_mapping_error(board->dev, bus_address)) + dev_err(board->gpib_dev, "dma mapping error in dma read!"); + + /* program dma controller */ + retval = fmh_gpib_config_dma(board, 0); + if (retval) { + dma_unmap_single(board->dev, bus_address, length, DMA_FROM_DEVICE); + return retval; + } + tx_desc = dmaengine_prep_slave_single(e_priv->dma_channel, bus_address, + length, DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!tx_desc) { + dev_err(board->gpib_dev, "failed to allocate dma transmit descriptor\n"); + dma_unmap_single(board->dev, bus_address, length, DMA_FROM_DEVICE); + return -EIO; + } + tx_desc->callback = fmh_gpib_dma_callback; + tx_desc->callback_param = board; + + spin_lock_irqsave(&board->spinlock, flags); + // enable nec7210 dma + fifos_write(e_priv, length & fifo_xfer_counter_mask, FIFO_XFER_COUNTER_REG); + fifos_write(e_priv, RX_FIFO_DMA_REQUEST_ENABLE | RX_FIFO_CLEAR, FIFO_CONTROL_STATUS_REG); + nec7210_set_reg_bits(nec_priv, IMR1, HR_DIIE, 0); + nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAI, HR_DMAI); + + dma_cookie = dmaengine_submit(tx_desc); + dma_async_issue_pending(e_priv->dma_channel); + + set_bit(DMA_READ_IN_PROGRESS_BN, &nec_priv->state); + + spin_unlock_irqrestore(&board->spinlock, flags); + + // wait for data to transfer + wait_retval = wait_event_interruptible(board->wait, + test_bit(DMA_READ_IN_PROGRESS_BN, &nec_priv->state) + == 0 || + test_bit(RECEIVED_END_BN, &nec_priv->state) || + test_bit(DEV_CLEAR_BN, &nec_priv->state) || + test_bit(TIMO_NUM, &board->status)); + if (wait_retval) + retval = -ERESTARTSYS; + + if (test_bit(TIMO_NUM, &board->status)) + retval = -ETIMEDOUT; + if (test_bit(DEV_CLEAR_BN, &nec_priv->state)) + retval = -EINTR; + // stop the dma transfer + nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAI, 0); + fifos_write(e_priv, 0, FIFO_CONTROL_STATUS_REG); + /* + * give time for pl330 to transfer any in-flight data, since + * pl330 will throw it away when dmaengine_pause is called. + */ + usleep_range(10, 15); + residue = fmh_gpib_get_dma_residue(e_priv->dma_channel, dma_cookie); + if (WARN_ON_ONCE(residue > length || residue < 0)) + return -EFAULT; + *bytes_read += length - residue; + dmaengine_terminate_all(e_priv->dma_channel); + // make sure fmh_gpib_dma_callback got called + if (test_bit(DMA_READ_IN_PROGRESS_BN, &nec_priv->state)) + fmh_gpib_dma_callback(board); + + dma_unmap_single(board->dev, bus_address, length, DMA_FROM_DEVICE); + memcpy(buffer, e_priv->dma_buffer, *bytes_read); + + /* Manually read any dregs out of fifo. */ + while ((fifos_read(e_priv, FIFO_CONTROL_STATUS_REG) & RX_FIFO_EMPTY) == 0) { + if ((*bytes_read) >= length) { + dev_err(board->dev, "unexpected extra bytes in rx fifo, discarding! bytes_read=%d length=%d residue=%d\n", + (int)(*bytes_read), (int)length, (int)residue); + break; + } + buffer[(*bytes_read)++] = fifos_read(e_priv, FIFO_DATA_REG) & fifo_data_mask; + } + + /* + * If we got an end interrupt, figure out if it was + * associated with the last byte we dma'd or with a + * byte still sitting on the cb7210. + */ + spin_lock_irqsave(&board->spinlock, flags); + if (*bytes_read > 0 && test_bit(READ_READY_BN, &nec_priv->state) == 0) { + /* + * If there is no byte sitting on the cb7210 and we + * saw an end, we need to deal with it now + */ + if (test_and_clear_bit(RECEIVED_END_BN, &nec_priv->state)) + *end = 1; + } + spin_unlock_irqrestore(&board->spinlock, flags); + + return retval; +} + +static void fmh_gpib_release_rfd_holdoff(struct gpib_board *board, struct fmh_priv *e_priv) +{ + struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; + unsigned int ext_status_1; + unsigned long flags; + + spin_lock_irqsave(&board->spinlock, flags); + + ext_status_1 = read_byte(nec_priv, EXT_STATUS_1_REG); + + /* + * if there is an end byte sitting on the chip, don't release + * holdoff. We want it left set after we read out the end + * byte. + */ + if ((ext_status_1 & (DATA_IN_STATUS_BIT | END_STATUS_BIT)) != + (DATA_IN_STATUS_BIT | END_STATUS_BIT)) { + if (ext_status_1 & RFD_HOLDOFF_STATUS_BIT) + write_byte(nec_priv, AUX_FH, AUXMR); + + /* + * Check if an end byte raced in before we executed the AUX_FH command. + * If it did, we want to make sure the rfd holdoff is in effect. The end + * byte can arrive since + * AUX_RFD_HOLDOFF_ASAP doesn't immediately force the acceptor handshake + * to leave ACRS. + */ + if ((read_byte(nec_priv, EXT_STATUS_1_REG) & + (RFD_HOLDOFF_STATUS_BIT | DATA_IN_STATUS_BIT | END_STATUS_BIT)) == + (DATA_IN_STATUS_BIT | END_STATUS_BIT)) { + write_byte(nec_priv, AUX_RFD_HOLDOFF_ASAP, AUXMR); + set_bit(RFD_HOLDOFF_BN, &nec_priv->state); + } else { + clear_bit(RFD_HOLDOFF_BN, &nec_priv->state); + } + } + spin_unlock_irqrestore(&board->spinlock, flags); +} + +static int fmh_gpib_accel_read(struct gpib_board *board, u8 *buffer, size_t length, + int *end, size_t *bytes_read) +{ + struct fmh_priv *e_priv = board->private_data; + struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; + size_t remain = length; + size_t transfer_size; + int retval = 0; + size_t dma_nbytes; + unsigned long flags; + + smp_mb__before_atomic(); + clear_bit(DEV_CLEAR_BN, &nec_priv->state); // XXX FIXME + smp_mb__after_atomic(); + *end = 0; + *bytes_read = 0; + + retval = wait_for_read(board); + if (retval < 0) + return retval; + + fmh_gpib_release_rfd_holdoff(board, e_priv); + while (remain > 0) { + transfer_size = (e_priv->dma_buffer_size < remain) ? + e_priv->dma_buffer_size : remain; + retval = fmh_gpib_dma_read(board, buffer, transfer_size, end, &dma_nbytes); + remain -= dma_nbytes; + buffer += dma_nbytes; + *bytes_read += dma_nbytes; + if (*end) + break; + if (retval < 0) + break; + if (need_resched()) + schedule(); + } + + spin_lock_irqsave(&board->spinlock, flags); + if (test_bit(RFD_HOLDOFF_BN, &nec_priv->state) == 0) { + write_byte(nec_priv, AUX_RFD_HOLDOFF_ASAP, AUXMR); + set_bit(RFD_HOLDOFF_BN, &nec_priv->state); + } + spin_unlock_irqrestore(&board->spinlock, flags); + + return retval; +} + +/* + * Read a chunk of data whose length is within the limits of the hardware's + * xfer counter. Called in a loop from fmh_gpib_fifo_read(). + */ +static int fmh_gpib_fifo_read_countable(struct gpib_board *board, u8 *buffer, + size_t length, int *end, size_t *bytes_read) +{ + struct fmh_priv *e_priv = board->private_data; + struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; + int retval = 0; + + *bytes_read = 0; + *end = 0; + if (length == 0) + return 0; + + fifos_write(e_priv, length & fifo_xfer_counter_mask, FIFO_XFER_COUNTER_REG); + fifos_write(e_priv, RX_FIFO_CLEAR, FIFO_CONTROL_STATUS_REG); + nec7210_set_reg_bits(nec_priv, IMR1, HR_DIIE, 0); + nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAI, HR_DMAI); + + while (*bytes_read < length && *end == 0) { + int i; + + fifos_write(e_priv, RX_FIFO_HALF_FULL_INTERRUPT_ENABLE, FIFO_CONTROL_STATUS_REG); + retval = wait_for_rx_fifo_half_full_or_end(board); + if (retval < 0) + goto cleanup; + + for (i = 0; i < fmh_gpib_half_fifo_size(e_priv) && *end == 0; ++i) { + unsigned int data_value; + + data_value = fifos_read(e_priv, FIFO_DATA_REG); + buffer[(*bytes_read)++] = data_value & fifo_data_mask; + if (data_value & FIFO_DATA_EOI_FLAG) + *end = 1; + } + } + +cleanup: + // stop the transfer + nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAI, 0); + fifos_write(e_priv, 0, FIFO_CONTROL_STATUS_REG); + + /* Manually read any dregs out of fifo. */ + while ((fifos_read(e_priv, FIFO_CONTROL_STATUS_REG) & RX_FIFO_EMPTY) == 0) { + unsigned int data_value; + + if ((*bytes_read) >= length) { + dev_err(board->dev, "unexpected extra bytes in rx fifo, discarding! bytes_read=%d length=%d\n", + (int)(*bytes_read), (int)length); + break; + } + data_value = fifos_read(e_priv, FIFO_DATA_REG); + buffer[(*bytes_read)++] = data_value & fifo_data_mask; + if (data_value & FIFO_DATA_EOI_FLAG) + *end = 1; + } + + return retval; +} + +static int fmh_gpib_fifo_read(struct gpib_board *board, u8 *buffer, size_t length, + int *end, size_t *bytes_read) +{ + struct fmh_priv *e_priv = board->private_data; + struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; + size_t remain = length; + size_t transfer_size; + int retval = 0; + size_t nbytes; + unsigned long flags; + + clear_bit(DEV_CLEAR_BN, &nec_priv->state); // XXX FIXME + *end = 0; + *bytes_read = 0; + + /* + * Do a little prep with data in interrupt so that following wait_for_read() + * will wake up if a data byte is received. + */ + nec7210_set_reg_bits(nec_priv, IMR1, HR_DIIE, HR_DIIE); + fmh_gpib_interrupt(0, board); + + retval = wait_for_read(board); + if (retval < 0) + return retval; + + fmh_gpib_release_rfd_holdoff(board, e_priv); + while (remain > 0) { + if (fifo_xfer_counter_mask < remain) { + // round transfer size to a multiple of half fifo size + transfer_size = (fifo_xfer_counter_mask / + fmh_gpib_half_fifo_size(e_priv)) * + fmh_gpib_half_fifo_size(e_priv); + } else { + transfer_size = remain; + } + retval = fmh_gpib_fifo_read_countable(board, buffer, transfer_size, end, &nbytes); + remain -= nbytes; + buffer += nbytes; + *bytes_read += nbytes; + if (*end) + break; + if (retval < 0) + break; + if (need_resched()) + schedule(); + } + + if (*end == 0) { + spin_lock_irqsave(&board->spinlock, flags); + write_byte(nec_priv, AUX_RFD_HOLDOFF_ASAP, AUXMR); + set_bit(RFD_HOLDOFF_BN, &nec_priv->state); + spin_unlock_irqrestore(&board->spinlock, flags); + } + + return retval; +} + +static struct gpib_interface fmh_gpib_unaccel_interface = { + .name = "fmh_gpib_unaccel", + .attach = fmh_gpib_attach_holdoff_all, + .detach = fmh_gpib_detach, + .read = fmh_gpib_read, + .write = fmh_gpib_write, + .command = fmh_gpib_command, + .take_control = fmh_gpib_take_control, + .go_to_standby = fmh_gpib_go_to_standby, + .request_system_control = fmh_gpib_request_system_control, + .interface_clear = fmh_gpib_interface_clear, + .remote_enable = fmh_gpib_remote_enable, + .enable_eos = fmh_gpib_enable_eos, + .disable_eos = fmh_gpib_disable_eos, + .parallel_poll = fmh_gpib_parallel_poll, + .parallel_poll_configure = fmh_gpib_parallel_poll_configure, + .parallel_poll_response = fmh_gpib_parallel_poll_response, + .local_parallel_poll_mode = fmh_gpib_local_parallel_poll_mode, + .line_status = fmh_gpib_line_status, + .update_status = fmh_gpib_update_status, + .primary_address = fmh_gpib_primary_address, + .secondary_address = fmh_gpib_secondary_address, + .serial_poll_response2 = fmh_gpib_serial_poll_response2, + .serial_poll_status = fmh_gpib_serial_poll_status, + .t1_delay = fmh_gpib_t1_delay, + .return_to_local = fmh_gpib_return_to_local, +}; + +static struct gpib_interface fmh_gpib_interface = { + .name = "fmh_gpib", + .attach = fmh_gpib_attach_holdoff_end, + .detach = fmh_gpib_detach, + .read = fmh_gpib_accel_read, + .write = fmh_gpib_accel_write, + .command = fmh_gpib_command, + .take_control = fmh_gpib_take_control, + .go_to_standby = fmh_gpib_go_to_standby, + .request_system_control = fmh_gpib_request_system_control, + .interface_clear = fmh_gpib_interface_clear, + .remote_enable = fmh_gpib_remote_enable, + .enable_eos = fmh_gpib_enable_eos, + .disable_eos = fmh_gpib_disable_eos, + .parallel_poll = fmh_gpib_parallel_poll, + .parallel_poll_configure = fmh_gpib_parallel_poll_configure, + .parallel_poll_response = fmh_gpib_parallel_poll_response, + .local_parallel_poll_mode = fmh_gpib_local_parallel_poll_mode, + .line_status = fmh_gpib_line_status, + .update_status = fmh_gpib_update_status, + .primary_address = fmh_gpib_primary_address, + .secondary_address = fmh_gpib_secondary_address, + .serial_poll_response2 = fmh_gpib_serial_poll_response2, + .serial_poll_status = fmh_gpib_serial_poll_status, + .t1_delay = fmh_gpib_t1_delay, + .return_to_local = fmh_gpib_return_to_local, +}; + +static struct gpib_interface fmh_gpib_pci_interface = { + .name = "fmh_gpib_pci", + .attach = fmh_gpib_pci_attach_holdoff_end, + .detach = fmh_gpib_pci_detach, + .read = fmh_gpib_fifo_read, + .write = fmh_gpib_fifo_write, + .command = fmh_gpib_command, + .take_control = fmh_gpib_take_control, + .go_to_standby = fmh_gpib_go_to_standby, + .request_system_control = fmh_gpib_request_system_control, + .interface_clear = fmh_gpib_interface_clear, + .remote_enable = fmh_gpib_remote_enable, + .enable_eos = fmh_gpib_enable_eos, + .disable_eos = fmh_gpib_disable_eos, + .parallel_poll = fmh_gpib_parallel_poll, + .parallel_poll_configure = fmh_gpib_parallel_poll_configure, + .parallel_poll_response = fmh_gpib_parallel_poll_response, + .local_parallel_poll_mode = fmh_gpib_local_parallel_poll_mode, + .line_status = fmh_gpib_line_status, + .update_status = fmh_gpib_update_status, + .primary_address = fmh_gpib_primary_address, + .secondary_address = fmh_gpib_secondary_address, + .serial_poll_response2 = fmh_gpib_serial_poll_response2, + .serial_poll_status = fmh_gpib_serial_poll_status, + .t1_delay = fmh_gpib_t1_delay, + .return_to_local = fmh_gpib_return_to_local, +}; + +static struct gpib_interface fmh_gpib_pci_unaccel_interface = { + .name = "fmh_gpib_pci_unaccel", + .attach = fmh_gpib_pci_attach_holdoff_all, + .detach = fmh_gpib_pci_detach, + .read = fmh_gpib_read, + .write = fmh_gpib_write, + .command = fmh_gpib_command, + .take_control = fmh_gpib_take_control, + .go_to_standby = fmh_gpib_go_to_standby, + .request_system_control = fmh_gpib_request_system_control, + .interface_clear = fmh_gpib_interface_clear, + .remote_enable = fmh_gpib_remote_enable, + .enable_eos = fmh_gpib_enable_eos, + .disable_eos = fmh_gpib_disable_eos, + .parallel_poll = fmh_gpib_parallel_poll, + .parallel_poll_configure = fmh_gpib_parallel_poll_configure, + .parallel_poll_response = fmh_gpib_parallel_poll_response, + .local_parallel_poll_mode = fmh_gpib_local_parallel_poll_mode, + .line_status = fmh_gpib_line_status, + .update_status = fmh_gpib_update_status, + .primary_address = fmh_gpib_primary_address, + .secondary_address = fmh_gpib_secondary_address, + .serial_poll_response2 = fmh_gpib_serial_poll_response2, + .serial_poll_status = fmh_gpib_serial_poll_status, + .t1_delay = fmh_gpib_t1_delay, + .return_to_local = fmh_gpib_return_to_local, +}; + +irqreturn_t fmh_gpib_internal_interrupt(struct gpib_board *board) +{ + unsigned int status0, status1, status2, ext_status_1, fifo_status; + struct fmh_priv *priv = board->private_data; + struct nec7210_priv *nec_priv = &priv->nec7210_priv; + int retval = IRQ_NONE; + + status0 = read_byte(nec_priv, ISR0_IMR0_REG); + status1 = read_byte(nec_priv, ISR1); + status2 = read_byte(nec_priv, ISR2); + fifo_status = fifos_read(priv, FIFO_CONTROL_STATUS_REG); + + if (status0 & IFC_INTERRUPT_BIT) { + push_gpib_event(board, EVENT_IFC); + retval = IRQ_HANDLED; + } + + if (nec7210_interrupt_have_status(board, nec_priv, status1, status2) == IRQ_HANDLED) + retval = IRQ_HANDLED; + + ext_status_1 = read_byte(nec_priv, EXT_STATUS_1_REG); + + if (ext_status_1 & DATA_IN_STATUS_BIT) + set_bit(READ_READY_BN, &nec_priv->state); + else + clear_bit(READ_READY_BN, &nec_priv->state); + + if (ext_status_1 & DATA_OUT_STATUS_BIT) + set_bit(WRITE_READY_BN, &nec_priv->state); + else + clear_bit(WRITE_READY_BN, &nec_priv->state); + + if (ext_status_1 & COMMAND_OUT_STATUS_BIT) + set_bit(COMMAND_READY_BN, &nec_priv->state); + else + clear_bit(COMMAND_READY_BN, &nec_priv->state); + + if (ext_status_1 & RFD_HOLDOFF_STATUS_BIT) + set_bit(RFD_HOLDOFF_BN, &nec_priv->state); + else + clear_bit(RFD_HOLDOFF_BN, &nec_priv->state); + + if (ext_status_1 & END_STATUS_BIT) { + /* + * only set RECEIVED_END while there is still a data + * byte sitting in the chip, to avoid spuriously + * setting it multiple times after it has been cleared + * during a read. + */ + if (ext_status_1 & DATA_IN_STATUS_BIT) + set_bit(RECEIVED_END_BN, &nec_priv->state); + } else { + clear_bit(RECEIVED_END_BN, &nec_priv->state); + } + + if ((fifo_status & TX_FIFO_HALF_EMPTY_INTERRUPT_IS_ENABLED) && + (fifo_status & TX_FIFO_HALF_EMPTY)) { + /* + * We really only want to clear the + * TX_FIFO_HALF_EMPTY_INTERRUPT_ENABLE bit in the + * FIFO_CONTROL_STATUS_REG. Since we are not being + * careful, this also has a side effect of disabling + * DMA requests and the RX fifo interrupt. That is + * fine though, since they should never be in use at + * the same time as the TX fifo interrupt. + */ + fifos_write(priv, 0x0, FIFO_CONTROL_STATUS_REG); + retval = IRQ_HANDLED; + } + + if ((fifo_status & RX_FIFO_HALF_FULL_INTERRUPT_IS_ENABLED) && + (fifo_status & RX_FIFO_HALF_FULL)) { + /* + * We really only want to clear the + * RX_FIFO_HALF_FULL_INTERRUPT_ENABLE bit in the + * FIFO_CONTROL_STATUS_REG. Since we are not being + * careful, this also has a side effect of disabling + * DMA requests and the TX fifo interrupt. That is + * fine though, since they should never be in use at + * the same time as the RX fifo interrupt. + */ + fifos_write(priv, 0x0, FIFO_CONTROL_STATUS_REG); + retval = IRQ_HANDLED; + } + + if (retval == IRQ_HANDLED) + wake_up_interruptible(&board->wait); + + return retval; +} + +irqreturn_t fmh_gpib_interrupt(int irq, void *arg) +{ + struct gpib_board *board = arg; + unsigned long flags; + irqreturn_t retval; + + spin_lock_irqsave(&board->spinlock, flags); + retval = fmh_gpib_internal_interrupt(board); + spin_unlock_irqrestore(&board->spinlock, flags); + return retval; +} + +static int fmh_gpib_allocate_private(struct gpib_board *board) +{ + struct fmh_priv *priv; + + board->private_data = kmalloc(sizeof(struct fmh_priv), GFP_KERNEL); + if (!board->private_data) + return -ENOMEM; + priv = board->private_data; + memset(priv, 0, sizeof(struct fmh_priv)); + init_nec7210_private(&priv->nec7210_priv); + priv->dma_buffer_size = 0x800; + priv->dma_buffer = kmalloc(priv->dma_buffer_size, GFP_KERNEL); + if (!priv->dma_buffer) + return -ENOMEM; + return 0; +} + +static void fmh_gpib_generic_detach(struct gpib_board *board) +{ + if (board->private_data) { + struct fmh_priv *e_priv = board->private_data; + + kfree(e_priv->dma_buffer); + kfree(board->private_data); + board->private_data = NULL; + } + if (board->dev) + dev_set_drvdata(board->dev, NULL); +} + +// generic part of attach functions +static int fmh_gpib_generic_attach(struct gpib_board *board) +{ + struct fmh_priv *e_priv; + struct nec7210_priv *nec_priv; + int retval; + + board->status = 0; + + retval = fmh_gpib_allocate_private(board); + if (retval < 0) + return retval; + e_priv = board->private_data; + nec_priv = &e_priv->nec7210_priv; + nec_priv->read_byte = gpib_cs_read_byte; + nec_priv->write_byte = gpib_cs_write_byte; + nec_priv->offset = 1; + nec_priv->type = CB7210; + return 0; +} + +static int fmh_gpib_config_dma(struct gpib_board *board, int output) +{ + struct fmh_priv *e_priv = board->private_data; + struct dma_slave_config config; + + config.device_fc = true; + + if (e_priv->dma_burst_length < 1) { + config.src_maxburst = 1; + config.dst_maxburst = 1; + } else { + config.src_maxburst = e_priv->dma_burst_length; + config.dst_maxburst = e_priv->dma_burst_length; + } + + config.src_addr_width = 1; + config.dst_addr_width = 1; + + if (output) { + config.direction = DMA_MEM_TO_DEV; + config.src_addr = 0; + config.dst_addr = e_priv->dma_port_res->start + FIFO_DATA_REG * fifo_reg_offset; + } else { + config.direction = DMA_DEV_TO_MEM; + config.src_addr = e_priv->dma_port_res->start + FIFO_DATA_REG * fifo_reg_offset; + config.dst_addr = 0; + } + return dmaengine_slave_config(e_priv->dma_channel, &config); +} + +static int fmh_gpib_init(struct fmh_priv *e_priv, struct gpib_board *board, int handshake_mode) +{ + struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; + unsigned long flags; + unsigned int fifo_status_bits; + + fifos_write(e_priv, RX_FIFO_CLEAR | TX_FIFO_CLEAR, FIFO_CONTROL_STATUS_REG); + + nec7210_board_reset(nec_priv, board); + write_byte(nec_priv, AUX_LO_SPEED, AUXMR); + nec7210_set_handshake_mode(board, nec_priv, handshake_mode); + + /* Hueristically check if hardware supports fifo half full/empty interrupts */ + fifo_status_bits = fifos_read(e_priv, FIFO_CONTROL_STATUS_REG); + e_priv->supports_fifo_interrupts = (fifo_status_bits & TX_FIFO_EMPTY) && + (fifo_status_bits & TX_FIFO_HALF_EMPTY); + + nec7210_board_online(nec_priv, board); + + write_byte(nec_priv, IFC_INTERRUPT_ENABLE_BIT | ATN_INTERRUPT_ENABLE_BIT, ISR0_IMR0_REG); + + spin_lock_irqsave(&board->spinlock, flags); + write_byte(nec_priv, AUX_RFD_HOLDOFF_ASAP, AUXMR); + set_bit(RFD_HOLDOFF_BN, &nec_priv->state); + spin_unlock_irqrestore(&board->spinlock, flags); + return 0; +} + +/* Match callback for driver_find_device */ +static int fmh_gpib_device_match(struct device *dev, const void *data) +{ + const struct gpib_board_config *config = data; + + if (dev_get_drvdata(dev)) + return 0; + + if (gpib_match_device_path(dev, config->device_path) == 0) + return 0; + + // driver doesn't support selection by serial number + if (config->serial_number) + return 0; + + dev_dbg(dev, "matched: %s\n", of_node_full_name(dev_of_node((dev)))); + return 1; +} + +static int fmh_gpib_attach_impl(struct gpib_board *board, const struct gpib_board_config *config, + unsigned int handshake_mode, int acquire_dma) +{ + struct fmh_priv *e_priv; + struct nec7210_priv *nec_priv; + int retval; + int irq; + struct resource *res; + struct platform_device *pdev; + + board->dev = driver_find_device(&fmh_gpib_platform_driver.driver, + NULL, (const void *)config, &fmh_gpib_device_match); + if (!board->dev) { + dev_err(board->gpib_dev, "No matching fmh_gpib_core device was found, attach failed."); + return -ENODEV; + } + // currently only used to mark the device as already attached + dev_set_drvdata(board->dev, board); + pdev = to_platform_device(board->dev); + + retval = fmh_gpib_generic_attach(board); + if (retval) + return retval; + + e_priv = board->private_data; + nec_priv = &e_priv->nec7210_priv; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "gpib_control_status"); + if (!res) { + dev_err(board->dev, "Unable to locate mmio resource\n"); + return -ENODEV; + } + + if (request_mem_region(res->start, + resource_size(res), + pdev->name) == NULL) { + dev_err(board->dev, "cannot claim registers\n"); + return -ENXIO; + } + e_priv->gpib_iomem_res = res; + + nec_priv->mmiobase = ioremap(e_priv->gpib_iomem_res->start, + resource_size(e_priv->gpib_iomem_res)); + if (!nec_priv->mmiobase) { + dev_err(board->dev, "Could not map I/O memory\n"); + return -ENOMEM; + } + dev_dbg(board->dev, "iobase %pr remapped to %p\n", + e_priv->gpib_iomem_res, nec_priv->mmiobase); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dma_fifos"); + if (!res) { + dev_err(board->dev, "Unable to locate mmio resource for gpib dma port\n"); + return -ENODEV; + } + if (request_mem_region(res->start, + resource_size(res), + pdev->name) == NULL) { + dev_err(board->dev, "cannot claim registers\n"); + return -ENXIO; + } + e_priv->dma_port_res = res; + e_priv->fifo_base = ioremap(e_priv->dma_port_res->start, + resource_size(e_priv->dma_port_res)); + if (!e_priv->fifo_base) { + dev_err(board->dev, "Could not map I/O memory for fifos\n"); + return -ENOMEM; + } + dev_dbg(board->dev, "dma fifos 0x%lx remapped to %p, length=%ld\n", + (unsigned long)e_priv->dma_port_res->start, e_priv->fifo_base, + (unsigned long)resource_size(e_priv->dma_port_res)); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return -EBUSY; + retval = request_irq(irq, fmh_gpib_interrupt, IRQF_SHARED, pdev->name, board); + if (retval) { + dev_err(board->dev, + "cannot register interrupt handler err=%d\n", + retval); + return retval; + } + e_priv->irq = irq; + + if (acquire_dma) { + e_priv->dma_channel = dma_request_slave_channel(board->dev, "rxtx"); + if (!e_priv->dma_channel) { + dev_err(board->dev, "failed to acquire dma channel \"rxtx\".\n"); + return -EIO; + } + } + /* + * in the future we might want to know the half-fifo size + * (dma_burst_length) even when not using dma, so go ahead an + * initialize it unconditionally. + */ + e_priv->dma_burst_length = fifos_read(e_priv, FIFO_MAX_BURST_LENGTH_REG) & + fifo_max_burst_length_mask; + + return fmh_gpib_init(e_priv, board, handshake_mode); +} + +int fmh_gpib_attach_holdoff_all(struct gpib_board *board, const struct gpib_board_config *config) +{ + return fmh_gpib_attach_impl(board, config, HR_HLDA, 0); +} + +int fmh_gpib_attach_holdoff_end(struct gpib_board *board, const struct gpib_board_config *config) +{ + return fmh_gpib_attach_impl(board, config, HR_HLDE, 1); +} + +void fmh_gpib_detach(struct gpib_board *board) +{ + struct fmh_priv *e_priv = board->private_data; + struct nec7210_priv *nec_priv; + + if (e_priv) { + if (e_priv->dma_channel) + dma_release_channel(e_priv->dma_channel); + nec_priv = &e_priv->nec7210_priv; + + if (e_priv->irq) + free_irq(e_priv->irq, board); + if (e_priv->fifo_base) + fifos_write(e_priv, 0, FIFO_CONTROL_STATUS_REG); + if (nec_priv->mmiobase) { + write_byte(nec_priv, 0, ISR0_IMR0_REG); + nec7210_board_reset(nec_priv, board); + } + if (e_priv->fifo_base) + iounmap(e_priv->fifo_base); + if (nec_priv->mmiobase) + iounmap(nec_priv->mmiobase); + if (e_priv->dma_port_res) { + release_mem_region(e_priv->dma_port_res->start, + resource_size(e_priv->dma_port_res)); + } + if (e_priv->gpib_iomem_res) + release_mem_region(e_priv->gpib_iomem_res->start, + resource_size(e_priv->gpib_iomem_res)); + } + fmh_gpib_generic_detach(board); + + if (board->dev) { + put_device(board->dev); + board->dev = NULL; + } +} + +static int fmh_gpib_pci_attach_impl(struct gpib_board *board, + const struct gpib_board_config *config, + unsigned int handshake_mode) +{ + struct fmh_priv *e_priv; + struct nec7210_priv *nec_priv; + int retval; + struct pci_dev *pci_device; + + retval = fmh_gpib_generic_attach(board); + if (retval) + return retval; + + e_priv = board->private_data; + nec_priv = &e_priv->nec7210_priv; + + // find board + pci_device = gpib_pci_get_device(config, BOGUS_PCI_VENDOR_ID_FLUKE, + BOGUS_PCI_DEVICE_ID_FLUKE_BLADERUNNER, NULL); + if (!pci_device) { + dev_err(board->gpib_dev, "No matching fmh_gpib_core pci device was found, attach failed."); + return -ENODEV; + } + board->dev = &pci_device->dev; + + // bladerunner prototype has offset of 4 between gpib control/status registers + nec_priv->offset = 4; + + if (pci_enable_device(pci_device)) { + dev_err(board->dev, "error enabling pci device\n"); + return -EIO; + } + if (pci_request_regions(pci_device, KBUILD_MODNAME)) { + dev_err(board->dev, "pci_request_regions failed\n"); + return -EIO; + } + e_priv->gpib_iomem_res = &pci_device->resource[gpib_control_status_pci_resource_index]; + e_priv->dma_port_res = &pci_device->resource[gpib_fifo_pci_resource_index]; + + nec_priv->mmiobase = ioremap(pci_resource_start(pci_device, + gpib_control_status_pci_resource_index), + pci_resource_len(pci_device, + gpib_control_status_pci_resource_index)); + dev_dbg(board->dev, "base address for gpib control/status registers remapped to 0x%p\n", + nec_priv->mmiobase); + + if (e_priv->dma_port_res->flags & IORESOURCE_MEM) { + e_priv->fifo_base = ioremap(pci_resource_start(pci_device, + gpib_fifo_pci_resource_index), + pci_resource_len(pci_device, + gpib_fifo_pci_resource_index)); + dev_dbg(board->dev, "base address for gpib fifo registers remapped to 0x%p\n", + e_priv->fifo_base); + } else { + e_priv->fifo_base = NULL; + dev_dbg(board->dev, "hardware has no gpib fifo registers.\n"); + } + + if (pci_device->irq) { + retval = request_irq(pci_device->irq, fmh_gpib_interrupt, IRQF_SHARED, + KBUILD_MODNAME, board); + if (retval) { + dev_err(board->dev, "cannot register interrupt handler err=%d\n", retval); + return retval; + } + } + e_priv->irq = pci_device->irq; + + e_priv->dma_burst_length = fifos_read(e_priv, FIFO_MAX_BURST_LENGTH_REG) & + fifo_max_burst_length_mask; + + return fmh_gpib_init(e_priv, board, handshake_mode); +} + +int fmh_gpib_pci_attach_holdoff_all(struct gpib_board *board, + const struct gpib_board_config *config) +{ + return fmh_gpib_pci_attach_impl(board, config, HR_HLDA); +} + +int fmh_gpib_pci_attach_holdoff_end(struct gpib_board *board, + const struct gpib_board_config *config) +{ + int retval; + struct fmh_priv *e_priv; + + retval = fmh_gpib_pci_attach_impl(board, config, HR_HLDE); + e_priv = board->private_data; + if (retval == 0 && e_priv && e_priv->supports_fifo_interrupts == 0) { + dev_err(board->gpib_dev, "your fmh_gpib_core does not appear to support fifo interrupts. Try the fmh_gpib_pci_unaccel board type instead."); + return -EIO; + } + return retval; +} + +void fmh_gpib_pci_detach(struct gpib_board *board) +{ + struct fmh_priv *e_priv = board->private_data; + struct nec7210_priv *nec_priv; + + if (e_priv) { + nec_priv = &e_priv->nec7210_priv; + + if (e_priv->irq) + free_irq(e_priv->irq, board); + if (e_priv->fifo_base) + fifos_write(e_priv, 0, FIFO_CONTROL_STATUS_REG); + if (nec_priv->mmiobase) { + write_byte(nec_priv, 0, ISR0_IMR0_REG); + nec7210_board_reset(nec_priv, board); + } + if (e_priv->fifo_base) + iounmap(e_priv->fifo_base); + if (nec_priv->mmiobase) + iounmap(nec_priv->mmiobase); + if (e_priv->dma_port_res || e_priv->gpib_iomem_res) + pci_release_regions(to_pci_dev(board->dev)); + if (board->dev) + pci_dev_put(to_pci_dev(board->dev)); + } + fmh_gpib_generic_detach(board); +} + +static int fmh_gpib_platform_probe(struct platform_device *pdev) +{ + return 0; +} + +static const struct of_device_id fmh_gpib_of_match[] = { + { .compatible = "fmhess,fmh_gpib_core"}, + { {0} } +}; +MODULE_DEVICE_TABLE(of, fmh_gpib_of_match); + +static struct platform_driver fmh_gpib_platform_driver = { + .driver = { + .name = DRV_NAME, + .of_match_table = fmh_gpib_of_match, + }, + .probe = &fmh_gpib_platform_probe +}; + +static int fmh_gpib_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + return 0; +} + +static const struct pci_device_id fmh_gpib_pci_match[] = { + { BOGUS_PCI_VENDOR_ID_FLUKE, BOGUS_PCI_DEVICE_ID_FLUKE_BLADERUNNER, 0, 0, 0 }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, fmh_gpib_pci_match); + +static struct pci_driver fmh_gpib_pci_driver = { + .name = DRV_NAME, + .id_table = fmh_gpib_pci_match, + .probe = &fmh_gpib_pci_probe +}; + +static int __init fmh_gpib_init_module(void) +{ + int result; + + result = platform_driver_register(&fmh_gpib_platform_driver); + if (result) { + pr_err("platform_driver_register failed: error = %d\n", result); + return result; + } + + result = pci_register_driver(&fmh_gpib_pci_driver); + if (result) { + pr_err("pci_register_driver failed: error = %d\n", result); + goto err_pci_driver; + } + + result = gpib_register_driver(&fmh_gpib_unaccel_interface, THIS_MODULE); + if (result) { + pr_err("gpib_register_driver failed: error = %d\n", result); + goto err_unaccel; + } + + result = gpib_register_driver(&fmh_gpib_interface, THIS_MODULE); + if (result) { + pr_err("gpib_register_driver failed: error = %d\n", result); + goto err_interface; + } + + result = gpib_register_driver(&fmh_gpib_pci_unaccel_interface, THIS_MODULE); + if (result) { + pr_err("gpib_register_driver failed: error = %d\n", result); + goto err_pci_unaccel; + } + + result = gpib_register_driver(&fmh_gpib_pci_interface, THIS_MODULE); + if (result) { + pr_err("gpib_register_driver failed: error = %d\n", result); + goto err_pci; + } + + return 0; + +err_pci: + gpib_unregister_driver(&fmh_gpib_pci_unaccel_interface); +err_pci_unaccel: + gpib_unregister_driver(&fmh_gpib_interface); +err_interface: + gpib_unregister_driver(&fmh_gpib_unaccel_interface); +err_unaccel: + pci_unregister_driver(&fmh_gpib_pci_driver); +err_pci_driver: + platform_driver_unregister(&fmh_gpib_platform_driver); + + return result; +} + +static void __exit fmh_gpib_exit_module(void) +{ + gpib_unregister_driver(&fmh_gpib_pci_interface); + gpib_unregister_driver(&fmh_gpib_pci_unaccel_interface); + gpib_unregister_driver(&fmh_gpib_unaccel_interface); + gpib_unregister_driver(&fmh_gpib_interface); + + pci_unregister_driver(&fmh_gpib_pci_driver); + platform_driver_unregister(&fmh_gpib_platform_driver); +} + +module_init(fmh_gpib_init_module); +module_exit(fmh_gpib_exit_module); diff --git a/drivers/gpib/fmh_gpib/fmh_gpib.h b/drivers/gpib/fmh_gpib/fmh_gpib.h new file mode 100644 index 000000000000..e7602d7e1401 --- /dev/null +++ b/drivers/gpib/fmh_gpib/fmh_gpib.h @@ -0,0 +1,177 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/*************************************************************************** + * Author: Frank Mori Hess + * Copyright: (C) 2006, 2010, 2015 Fluke Corporation + * (C) 2017 Frank Mori Hess + ***************************************************************************/ + +#include +#include +#include +#include +#include "nec7210.h" + +static const int fifo_reg_offset = 2; + +static const int gpib_control_status_pci_resource_index; +static const int gpib_fifo_pci_resource_index = 1; + +/* We don't have a real pci vendor/device id, the following will need to be + * patched to match prototype hardware. + */ +#define BOGUS_PCI_VENDOR_ID_FLUKE 0xffff +#define BOGUS_PCI_DEVICE_ID_FLUKE_BLADERUNNER 0x0 + +struct fmh_priv { + struct nec7210_priv nec7210_priv; + struct resource *gpib_iomem_res; + struct resource *write_transfer_counter_res; + struct resource *dma_port_res; + int irq; + struct dma_chan *dma_channel; + u8 *dma_buffer; + int dma_buffer_size; + int dma_burst_length; + void __iomem *fifo_base; + unsigned supports_fifo_interrupts : 1; +}; + +static inline int fmh_gpib_half_fifo_size(struct fmh_priv *priv) +{ + return priv->dma_burst_length; +} + +// registers beyond the nec7210 register set +enum fmh_gpib_regs { + EXT_STATUS_1_REG = 0x9, + STATE1_REG = 0xc, + ISR0_IMR0_REG = 0xe, + BUS_STATUS_REG = 0xf +}; + +/* IMR0 -- Interrupt Mode Register 0 */ +enum imr0_bits { + ATN_INTERRUPT_ENABLE_BIT = 0x4, + IFC_INTERRUPT_ENABLE_BIT = 0x8 +}; + +/* ISR0 -- Interrupt Status Register 0 */ +enum isr0_bits { + ATN_INTERRUPT_BIT = 0x4, + IFC_INTERRUPT_BIT = 0x8 +}; + +enum state1_bits { + SOURCE_HANDSHAKE_SIDS_BITS = 0x0, /* source idle state */ + SOURCE_HANDSHAKE_SGNS_BITS = 0x1, /* source generate state */ + SOURCE_HANDSHAKE_SDYS_BITS = 0x2, /* source delay state */ + SOURCE_HANDSHAKE_STRS_BITS = 0x5, /* source transfer state */ + SOURCE_HANDSHAKE_MASK = 0x7 +}; + +enum fmh_gpib_auxmr_bits { + AUX_I_REG = 0xe0, +}; + +enum aux_reg_i_bits { + LOCAL_PPOLL_MODE_BIT = 0x4 +}; + +enum ext_status_1_bits { + DATA_IN_STATUS_BIT = 0x01, + DATA_OUT_STATUS_BIT = 0x02, + COMMAND_OUT_STATUS_BIT = 0x04, + RFD_HOLDOFF_STATUS_BIT = 0x08, + END_STATUS_BIT = 0x10 +}; + +/* dma fifo reg and bits */ +enum dma_fifo_regs { + FIFO_DATA_REG = 0x0, + FIFO_CONTROL_STATUS_REG = 0x1, + FIFO_XFER_COUNTER_REG = 0x2, + FIFO_MAX_BURST_LENGTH_REG = 0x3 +}; + +enum fifo_data_bits { + FIFO_DATA_EOI_FLAG = 0x100 +}; + +enum fifo_control_bits { + TX_FIFO_DMA_REQUEST_ENABLE = 0x0001, + TX_FIFO_CLEAR = 0x0002, + TX_FIFO_HALF_EMPTY_INTERRUPT_ENABLE = 0x0008, + RX_FIFO_DMA_REQUEST_ENABLE = 0x0100, + RX_FIFO_CLEAR = 0x0200, + RX_FIFO_HALF_FULL_INTERRUPT_ENABLE = 0x0800 +}; + +enum fifo_status_bits { + TX_FIFO_EMPTY = 0x0001, + TX_FIFO_FULL = 0x0002, + TX_FIFO_HALF_EMPTY = 0x0004, + TX_FIFO_HALF_EMPTY_INTERRUPT_IS_ENABLED = 0x0008, + TX_FIFO_DMA_REQUEST_IS_ENABLED = 0x0010, + RX_FIFO_EMPTY = 0x0100, + RX_FIFO_FULL = 0x0200, + RX_FIFO_HALF_FULL = 0x0400, + RX_FIFO_HALF_FULL_INTERRUPT_IS_ENABLED = 0x0800, + RX_FIFO_DMA_REQUEST_IS_ENABLED = 0x1000 +}; + +static const unsigned int fifo_data_mask = 0x00ff; +static const unsigned int fifo_xfer_counter_mask = 0x0fff; +static const unsigned int fifo_max_burst_length_mask = 0x00ff; + +static inline u8 gpib_cs_read_byte(struct nec7210_priv *nec_priv, + unsigned int register_num) +{ + return readb(nec_priv->mmiobase + register_num * nec_priv->offset); +} + +static inline void gpib_cs_write_byte(struct nec7210_priv *nec_priv, u8 data, + unsigned int register_num) +{ + writeb(data, nec_priv->mmiobase + register_num * nec_priv->offset); +} + +static inline uint16_t fifos_read(struct fmh_priv *fmh_priv, int register_num) +{ + if (!fmh_priv->fifo_base) + return 0; + return readw(fmh_priv->fifo_base + register_num * fifo_reg_offset); +} + +static inline void fifos_write(struct fmh_priv *fmh_priv, uint16_t data, int register_num) +{ + if (!fmh_priv->fifo_base) + return; + writew(data, fmh_priv->fifo_base + register_num * fifo_reg_offset); +} + +enum bus_status_bits { + BSR_ATN_BIT = 0x01, + BSR_EOI_BIT = 0x02, + BSR_SRQ_BIT = 0x04, + BSR_IFC_BIT = 0x08, + BSR_REN_BIT = 0x10, + BSR_DAV_BIT = 0x20, + BSR_NRFD_BIT = 0x40, + BSR_NDAC_BIT = 0x80, +}; + +enum fmh_gpib_aux_cmds { + /* AUX_RTL2 is an auxiliary command which causes the cb7210 to assert + * (and keep asserted) the local rtl message. This is used in conjunction + * with the normal nec7210 AUX_RTL command, which + * pulses the rtl message, having the effect of clearing rtl if it was left + * asserted by AUX_RTL2. + */ + AUX_RTL2 = 0x0d, + AUX_RFD_HOLDOFF_ASAP = 0x15, + AUX_REQT = 0x18, + AUX_REQF = 0x19, + AUX_LO_SPEED = 0x40, + AUX_HI_SPEED = 0x41 +}; diff --git a/drivers/gpib/gpio/Makefile b/drivers/gpib/gpio/Makefile new file mode 100644 index 000000000000..00ea52abdda7 --- /dev/null +++ b/drivers/gpib/gpio/Makefile @@ -0,0 +1,4 @@ + +obj-$(CONFIG_GPIB_GPIO) += gpib_bitbang.o + + diff --git a/drivers/gpib/gpio/gpib_bitbang.c b/drivers/gpib/gpio/gpib_bitbang.c new file mode 100644 index 000000000000..374cd61355e9 --- /dev/null +++ b/drivers/gpib/gpio/gpib_bitbang.c @@ -0,0 +1,1469 @@ +// SPDX-License-Identifier: GPL-2.0 + +/************************************************************************* + * This code has been developed at the Institute of Sensor and Actuator * + * Systems (Technical University of Vienna, Austria) to enable the GPIO * + * lines (e.g. of a raspberry pi) to function as a GPIO master device * + * * + * authors : Thomas Klima * + * Marcello Carla' * + * Dave Penkler * + * * + * copyright : (C) 2016 Thomas Klima * + * * + *************************************************************************/ + +/* + * limitations: + * works only on RPi + * cannot function as non-CIC system controller with SN7516x because + * SN75161B cannot simultaneously make ATN input with IFC and REN as + * outputs. + * not implemented: + * parallel poll + * return2local + * device support (non master operation) + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#define dev_fmt pr_fmt +#define NAME KBUILD_MODNAME + +#define ENABLE_IRQ(IRQ, TYPE) irq_set_irq_type(IRQ, TYPE) +#define DISABLE_IRQ(IRQ) irq_set_irq_type(IRQ, IRQ_TYPE_NONE) + +/* + * Debug print levels: + * 0 = load/unload info and errors that make the driver fail; + * 1 = + warnings for unforeseen events that may break the current + * operation and lead to a timeout, but do not affect the + * driver integrity (mainly unexpected interrupts); + * 2 = + trace of function calls; + * 3 = + trace of protocol codes; + * 4 = + trace of interrupt operation. + */ +#define dbg_printk(level, frm, ...) \ + do { if (debug >= (level)) \ + dev_dbg(board->gpib_dev, frm, ## __VA_ARGS__); } \ + while (0) + +#define LINVAL gpiod_get_value(DAV), \ + gpiod_get_value(NRFD), \ + gpiod_get_value(NDAC), \ + gpiod_get_value(SRQ) +#define LINFMT "DAV: %d NRFD:%d NDAC: %d SRQ: %d" + +#include "gpibP.h" +#include "gpib_state_machines.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int sn7516x_used = 1, sn7516x; +module_param(sn7516x_used, int, 0660); + +#define PINMAP_0 "elektronomikon" +#define PINMAP_1 "gpib4pi-1.1" +#define PINMAP_2 "yoga" +static char *pin_map = PINMAP_0; +module_param(pin_map, charp, 0660); +MODULE_PARM_DESC(pin_map, " valid values: " PINMAP_0 " " PINMAP_1 " " PINMAP_2); + +/********************************************** + * Signal pairing and pin wiring between the * + * Raspberry-Pi connector and the GPIB bus * + * * + * signal pin wiring * + * GPIB Pi-gpio GPIB -> RPi * + ********************************************** + */ +enum lines_t { + D01_pin_nr = 20, /* 1 -> 38 */ + D02_pin_nr = 26, /* 2 -> 37 */ + D03_pin_nr = 16, /* 3 -> 36 */ + D04_pin_nr = 19, /* 4 -> 35 */ + D05_pin_nr = 13, /* 13 -> 33 */ + D06_pin_nr = 12, /* 14 -> 32 */ + D07_pin_nr = 6, /* 15 -> 31 */ + D08_pin_nr = 5, /* 16 -> 29 */ + EOI_pin_nr = 9, /* 5 -> 21 */ + DAV_pin_nr = 10, /* 6 -> 19 */ + NRFD_pin_nr = 24, /* 7 -> 18 */ + NDAC_pin_nr = 23, /* 8 -> 16 */ + IFC_pin_nr = 22, /* 9 -> 15 */ + SRQ_pin_nr = 11, /* 10 -> 23 */ + _ATN_pin_nr = 25, /* 11 -> 22 */ + REN_pin_nr = 27, /* 17 -> 13 */ +/* + * GROUND PINS + * 12,18,19,20,21,22,23,24 => 14,20,25,30,34,39 + */ + +/* + * These lines are used to control the external + * SN75160/161 driver chips when used. + * When not used there is reduced fan out; + * currently tested with up to 4 devices. + */ + +/* Pi GPIO RPI 75161B 75160B Description */ + PE_pin_nr = 7, /* 26 -> nc 11 Pullup Enable */ + DC_pin_nr = 8, /* 24 -> 12 nc Direction control */ + TE_pin_nr = 18, /* 12 -> 2 1 Talk Enable */ + ACT_LED_pin_nr = 4, /* 7 -> LED */ + +/* YOGA adapter uses different pinout to ease layout */ + YOGA_D03_pin_nr = 13, + YOGA_D04_pin_nr = 12, + YOGA_D05_pin_nr = 21, + YOGA_D06_pin_nr = 19, +}; + +/* + * GPIO descriptors and pins - WARNING: STRICTLY KEEP ITEMS ORDER + */ + +#define GPIB_PINS 16 +#define SN7516X_PINS 4 +#define NUM_PINS (GPIB_PINS + SN7516X_PINS) + +#define ACT_LED_ON do { \ + if (ACT_LED) \ + gpiod_direction_output(ACT_LED, 1); \ + } while (0) +#define ACT_LED_OFF do { \ + if (ACT_LED) \ + gpiod_direction_output(ACT_LED, 0); \ + } while (0) + +static struct gpio_desc *all_descriptors[GPIB_PINS + SN7516X_PINS]; + +#define D01 all_descriptors[0] +#define D02 all_descriptors[1] +#define D03 all_descriptors[2] +#define D04 all_descriptors[3] +#define D05 all_descriptors[4] +#define D06 all_descriptors[5] +#define D07 all_descriptors[6] +#define D08 all_descriptors[7] + +#define EOI all_descriptors[8] +#define NRFD all_descriptors[9] +#define IFC all_descriptors[10] +#define _ATN all_descriptors[11] +#define REN all_descriptors[12] +#define DAV all_descriptors[13] +#define NDAC all_descriptors[14] +#define SRQ all_descriptors[15] + +#define PE all_descriptors[16] +#define DC all_descriptors[17] +#define TE all_descriptors[18] +#define ACT_LED all_descriptors[19] + +/* YOGA adapter uses a global enable for the buffer chips, re-using the TE pin */ +#define YOGA_ENABLE TE + +static int gpios_vector[] = { + D01_pin_nr, + D02_pin_nr, + D03_pin_nr, + D04_pin_nr, + D05_pin_nr, + D06_pin_nr, + D07_pin_nr, + D08_pin_nr, + + EOI_pin_nr, + NRFD_pin_nr, + IFC_pin_nr, + _ATN_pin_nr, + REN_pin_nr, + DAV_pin_nr, + NDAC_pin_nr, + SRQ_pin_nr, + + PE_pin_nr, + DC_pin_nr, + TE_pin_nr, + ACT_LED_pin_nr +}; + +/* Lookup table for general GPIOs */ + +static struct gpiod_lookup_table gpib_gpio_table_1 = { + // for bcm2835/6 + .dev_id = "", // device id of board device + .table = { + GPIO_LOOKUP_IDX("GPIO_GCLK", U16_MAX, NULL, 4, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("GPIO5", U16_MAX, NULL, 5, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("GPIO6", U16_MAX, NULL, 6, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("SPI_CE1_N", U16_MAX, NULL, 7, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("SPI_CE0_N", U16_MAX, NULL, 8, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("SPI_MISO", U16_MAX, NULL, 9, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("SPI_MOSI", U16_MAX, NULL, 10, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("SPI_SCLK", U16_MAX, NULL, 11, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("GPIO12", U16_MAX, NULL, 12, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("GPIO13", U16_MAX, NULL, 13, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("GPIO16", U16_MAX, NULL, 16, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("GPIO17", U16_MAX, NULL, 17, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("GPIO18", U16_MAX, NULL, 18, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("GPIO19", U16_MAX, NULL, 19, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("GPIO20", U16_MAX, NULL, 20, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("GPIO21", U16_MAX, NULL, 21, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("GPIO22", U16_MAX, NULL, 22, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("GPIO23", U16_MAX, NULL, 23, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("GPIO24", U16_MAX, NULL, 24, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("GPIO25", U16_MAX, NULL, 25, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("GPIO26", U16_MAX, NULL, 26, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("GPIO27", U16_MAX, NULL, 27, GPIO_ACTIVE_HIGH), + { } + }, +}; + +static struct gpiod_lookup_table gpib_gpio_table_0 = { + .dev_id = "", // device id of board device + .table = { + // for bcm27xx based pis (b b+ 2b 3b 3b+ 4 5) + GPIO_LOOKUP_IDX("GPIO4", U16_MAX, NULL, 4, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("GPIO5", U16_MAX, NULL, 5, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("GPIO6", U16_MAX, NULL, 6, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("GPIO7", U16_MAX, NULL, 7, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("GPIO8", U16_MAX, NULL, 8, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("GPIO9", U16_MAX, NULL, 9, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("GPIO10", U16_MAX, NULL, 10, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("GPIO11", U16_MAX, NULL, 11, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("GPIO12", U16_MAX, NULL, 12, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("GPIO13", U16_MAX, NULL, 13, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("GPIO16", U16_MAX, NULL, 16, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("GPIO17", U16_MAX, NULL, 17, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("GPIO18", U16_MAX, NULL, 18, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("GPIO19", U16_MAX, NULL, 19, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("GPIO20", U16_MAX, NULL, 20, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("GPIO21", U16_MAX, NULL, 21, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("GPIO22", U16_MAX, NULL, 22, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("GPIO23", U16_MAX, NULL, 23, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("GPIO24", U16_MAX, NULL, 24, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("GPIO25", U16_MAX, NULL, 25, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("GPIO26", U16_MAX, NULL, 26, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("GPIO27", U16_MAX, NULL, 27, GPIO_ACTIVE_HIGH), + { } + }, +}; + +static struct gpiod_lookup_table *lookup_tables[] = { + &gpib_gpio_table_0, + &gpib_gpio_table_1, + NULL +}; + +/* struct which defines private_data for gpio driver */ + +struct bb_priv { + int irq_NRFD; + int irq_NDAC; + int irq_DAV; + int irq_SRQ; + int dav_mode; /* dav interrupt mode 0/1 -> edge/levels */ + int nrfd_mode; /* nrfd interrupt mode 0/1 -> edge/levels */ + int ndac_mode; /* nrfd interrupt mode 0/1 -> edge/levels */ + int dav_tx; /* keep trace of DAV status while sending */ + int dav_rx; /* keep trace of DAV status while receiving */ + u8 eos; /* eos character */ + short eos_flags; /* eos mode */ + short eos_check; /* eos check required in current operation ... */ + short eos_check_8; /* ... with byte comparison */ + short eos_mask_7; /* ... with 7 bit masked character */ + short int end; + int request; + int count; + int direction; + int t1_delay; + u8 *rbuf; + u8 *wbuf; + int end_flag; + int r_busy; /* 0==idle 1==busy */ + int w_busy; + int write_done; + int cmd; /* 1 = cmd write in progress */ + size_t w_cnt; + size_t length; + u8 *w_buf; + spinlock_t rw_lock; /* protect mods to rw_lock */ + int phase; + int ndac_idle; + int ndac_seq; + int nrfd_idle; + int nrfd_seq; + int dav_seq; + long all_irqs; + int dav_idle; + + enum talker_function_state talker_state; + enum listener_function_state listener_state; +}; + +static inline long usec_diff(struct timespec64 *a, struct timespec64 *b); +static void bb_buffer_print(struct gpib_board *board, unsigned char *buffer, size_t length, + int cmd, int eoi); +static void set_data_lines(u8 byte); +static u8 get_data_lines(void); +static void set_data_lines_input(void); +static void set_data_lines_output(void); +static inline int check_for_eos(struct bb_priv *priv, u8 byte); +static void set_atn(struct gpib_board *board, int atn_asserted); + +static inline void SET_DIR_WRITE(struct bb_priv *priv); +static inline void SET_DIR_READ(struct bb_priv *priv); + +#define DIR_READ 0 +#define DIR_WRITE 1 + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("GPIB helper functions for bitbanging I/O"); + +/**** global variables ****/ +static int debug; +module_param(debug, int, 0644); + +static char printable(char x) +{ + if (x < 32 || x > 126) + return ' '; + return x; +} + +/*************************************************************************** + * * + * READ * + * * + ***************************************************************************/ + +static int bb_read(struct gpib_board *board, u8 *buffer, size_t length, + int *end, size_t *bytes_read) +{ + struct bb_priv *priv = board->private_data; + unsigned long flags; + int retval = 0; + + ACT_LED_ON; + SET_DIR_READ(priv); + + dbg_printk(2, "board: %p lock %d length: %zu\n", + board, mutex_is_locked(&board->user_mutex), length); + + priv->end = 0; + priv->count = 0; + priv->rbuf = buffer; + if (length == 0) + goto read_end; + priv->request = length; + priv->eos_check = (priv->eos_flags & REOS) == 0; /* do eos check */ + priv->eos_check_8 = priv->eos_flags & BIN; /* over 8 bits */ + priv->eos_mask_7 = priv->eos & 0x7f; /* with this 7 bit eos */ + + dbg_printk(3, ".........." LINFMT "\n", LINVAL); + + spin_lock_irqsave(&priv->rw_lock, flags); + priv->dav_mode = 1; + priv->dav_rx = 1; + ENABLE_IRQ(priv->irq_DAV, IRQ_TYPE_LEVEL_LOW); + priv->end_flag = 0; + gpiod_set_value(NRFD, 1); // ready for data + priv->r_busy = 1; + priv->phase = 100; + spin_unlock_irqrestore(&priv->rw_lock, flags); + + /* wait for the interrupt routines finish their work */ + + retval = wait_event_interruptible(board->wait, + (priv->end_flag || board->status & TIMO)); + + dbg_printk(3, "awake from wait queue: %d\n", retval); + + if (retval == 0 && board->status & TIMO) { + retval = -ETIMEDOUT; + dbg_printk(1, "timeout\n"); + } else if (retval) { + retval = -ERESTARTSYS; + } + + DISABLE_IRQ(priv->irq_DAV); + spin_lock_irqsave(&priv->rw_lock, flags); + gpiod_set_value(NRFD, 0); // DIR_READ line state + priv->r_busy = 0; + spin_unlock_irqrestore(&priv->rw_lock, flags); + +read_end: + ACT_LED_OFF; + *bytes_read = priv->count; + *end = priv->end; + priv->r_busy = 0; + dbg_printk(2, "return: %d eoi|eos: %d count: %d\n\n", retval, priv->end, priv->count); + return retval; +} + +/*************************************************************************** + * * + * READ interrupt routine (DAV line) * + * * + ***************************************************************************/ + +static irqreturn_t bb_DAV_interrupt(int irq, void *arg) +{ + struct gpib_board *board = arg; + struct bb_priv *priv = board->private_data; + int val; + unsigned long flags; + + spin_lock_irqsave(&priv->rw_lock, flags); + + priv->all_irqs++; + + if (priv->dav_mode) { + ENABLE_IRQ(priv->irq_DAV, IRQ_TYPE_EDGE_BOTH); + priv->dav_mode = 0; + } + + if (priv->r_busy == 0) { + dbg_printk(1, "interrupt while idle after %d at %d\n", + priv->count, priv->phase); + priv->dav_idle++; + priv->phase = 200; + goto dav_exit; /* idle */ + } + + val = gpiod_get_value(DAV); + if (val == priv->dav_rx) { + dbg_printk(1, "out of order DAV interrupt %d/%d after %zu/%zu at %d cmd %d " + LINFMT ".\n", val, priv->dav_rx, priv->w_cnt, priv->length, + priv->phase, priv->cmd, LINVAL); + priv->dav_seq++; + } + priv->dav_rx = val; + + dbg_printk(3, "> irq: %d DAV: %d st: %4lx dir: %d busy: %d:%d\n", + irq, val, board->status, priv->direction, priv->r_busy, priv->w_busy); + + if (val == 0) { + gpiod_set_value(NRFD, 0); // not ready for data + priv->rbuf[priv->count++] = get_data_lines(); + priv->end = !gpiod_get_value(EOI); + gpiod_set_value(NDAC, 1); // data accepted + priv->end |= check_for_eos(priv, priv->rbuf[priv->count - 1]); + priv->end_flag = ((priv->count >= priv->request) || priv->end); + priv->phase = 210; + } else { + gpiod_set_value(NDAC, 0); // data not accepted + if (priv->end_flag) { + priv->r_busy = 0; + wake_up_interruptible(&board->wait); + priv->phase = 220; + } else { + gpiod_set_value(NRFD, 1); // ready for data + priv->phase = 230; + } + } + +dav_exit: + spin_unlock_irqrestore(&priv->rw_lock, flags); + dbg_printk(3, "< irq: %d count %d\n", irq, priv->count); + return IRQ_HANDLED; +} + +/*************************************************************************** + * * + * WRITE * + * * + ***************************************************************************/ + +static int bb_write(struct gpib_board *board, u8 *buffer, size_t length, + int send_eoi, size_t *bytes_written) +{ + unsigned long flags; + int retval = 0; + + struct bb_priv *priv = board->private_data; + + ACT_LED_ON; + + priv->w_cnt = 0; + priv->w_buf = buffer; + dbg_printk(2, "board %p lock %d length: %zu\n", + board, mutex_is_locked(&board->user_mutex), length); + + if (debug > 1) + bb_buffer_print(board, buffer, length, priv->cmd, send_eoi); + priv->count = 0; + priv->phase = 300; + + if (length == 0) + goto write_end; + priv->end = send_eoi; + priv->length = length; + + SET_DIR_WRITE(priv); + + dbg_printk(2, "Enabling interrupts - NRFD: %d NDAC: %d\n", + gpiod_get_value(NRFD), gpiod_get_value(NDAC)); + + if (gpiod_get_value(NRFD) && gpiod_get_value(NDAC)) { /* check for listener */ + retval = -ENOTCONN; + goto write_end; + } + + spin_lock_irqsave(&priv->rw_lock, flags); + priv->w_busy = 1; /* make the interrupt routines active */ + priv->write_done = 0; + priv->nrfd_mode = 1; + priv->ndac_mode = 1; + priv->dav_tx = 1; + ENABLE_IRQ(priv->irq_NDAC, IRQ_TYPE_LEVEL_HIGH); + ENABLE_IRQ(priv->irq_NRFD, IRQ_TYPE_LEVEL_HIGH); + spin_unlock_irqrestore(&priv->rw_lock, flags); + + /* wait for the interrupt routines finish their work */ + + retval = wait_event_interruptible(board->wait, + priv->write_done || (board->status & TIMO)); + + dbg_printk(3, "awake from wait queue: %d\n", retval); + + if (retval == 0) { + if (board->status & TIMO) { + retval = -ETIMEDOUT; + dbg_printk(1, "timeout after %zu/%zu at %d " LINFMT " eoi: %d\n", + priv->w_cnt, length, priv->phase, LINVAL, send_eoi); + } else { + retval = priv->w_cnt; + } + } else { + retval = -ERESTARTSYS; + } + + DISABLE_IRQ(priv->irq_NRFD); + DISABLE_IRQ(priv->irq_NDAC); + + spin_lock_irqsave(&priv->rw_lock, flags); + priv->w_busy = 0; + gpiod_set_value(DAV, 1); // DIR_WRITE line state + gpiod_set_value(EOI, 1); // De-assert EOI (in case) + spin_unlock_irqrestore(&priv->rw_lock, flags); + +write_end: + *bytes_written = priv->w_cnt; + ACT_LED_OFF; + dbg_printk(2, "sent %zu bytes\r\n\r\n", *bytes_written); + priv->phase = 310; + return retval; +} + +/*************************************************************************** + * * + * WRITE interrupt routine (NRFD line) * + * * + ***************************************************************************/ + +static irqreturn_t bb_NRFD_interrupt(int irq, void *arg) +{ + struct gpib_board *board = arg; + struct bb_priv *priv = board->private_data; + unsigned long flags; + int nrfd; + + spin_lock_irqsave(&priv->rw_lock, flags); + + nrfd = gpiod_get_value(NRFD); + priv->all_irqs++; + + dbg_printk(3, "> irq: %d NRFD: %d NDAC: %d st: %4lx dir: %d busy: %d:%d\n", + irq, nrfd, gpiod_get_value(NDAC), board->status, priv->direction, + priv->w_busy, priv->r_busy); + + if (priv->nrfd_mode) { + ENABLE_IRQ(priv->irq_NRFD, IRQ_TYPE_EDGE_RISING); + priv->nrfd_mode = 0; + } + + if (priv->w_busy == 0) { + dbg_printk(1, "interrupt while idle after %zu/%zu at %d\n", + priv->w_cnt, priv->length, priv->phase); + priv->nrfd_idle++; + goto nrfd_exit; /* idle */ + } + if (nrfd == 0) { + dbg_printk(1, "out of order interrupt after %zu/%zu at %d cmd %d " LINFMT ".\n", + priv->w_cnt, priv->length, priv->phase, priv->cmd, LINVAL); + priv->phase = 400; + priv->nrfd_seq++; + goto nrfd_exit; + } + if (!priv->dav_tx) { + dbg_printk(1, "DAV low after %zu/%zu cmd %d " LINFMT ". No action.\n", + priv->w_cnt, priv->length, priv->cmd, LINVAL); + priv->dav_seq++; + goto nrfd_exit; + } + + if (priv->w_cnt >= priv->length) { // test for missed NDAC end of transfer + dev_err(board->gpib_dev, "Unexpected NRFD exit\n"); + priv->write_done = 1; + priv->w_busy = 0; + wake_up_interruptible(&board->wait); + goto nrfd_exit; + } + + dbg_printk(3, "sending %zu\n", priv->w_cnt); + + set_data_lines(priv->w_buf[priv->w_cnt++]); // put the data on the lines + + if (priv->w_cnt == priv->length && priv->end) { + dbg_printk(3, "Asserting EOI\n"); + gpiod_set_value(EOI, 0); // Assert EOI + } + + gpiod_set_value(DAV, 0); // Data available + priv->dav_tx = 0; + priv->phase = 410; + +nrfd_exit: + spin_unlock_irqrestore(&priv->rw_lock, flags); + + return IRQ_HANDLED; +} + +/*************************************************************************** + * * + * WRITE interrupt routine (NDAC line) * + * * + ***************************************************************************/ + +static irqreturn_t bb_NDAC_interrupt(int irq, void *arg) +{ + struct gpib_board *board = arg; + struct bb_priv *priv = board->private_data; + unsigned long flags; + int ndac; + + spin_lock_irqsave(&priv->rw_lock, flags); + + ndac = gpiod_get_value(NDAC); + priv->all_irqs++; + dbg_printk(3, "> irq: %d NRFD: %d NDAC: %d st: %4lx dir: %d busy: %d:%d\n", + irq, gpiod_get_value(NRFD), ndac, board->status, priv->direction, + priv->w_busy, priv->r_busy); + + if (priv->ndac_mode) { + ENABLE_IRQ(priv->irq_NDAC, IRQ_TYPE_EDGE_RISING); + priv->ndac_mode = 0; + } + + if (priv->w_busy == 0) { + dbg_printk(1, "interrupt while idle.\n"); + priv->ndac_idle++; + goto ndac_exit; + } + if (ndac == 0) { + dbg_printk(1, "out of order interrupt at %zu:%d.\n", priv->w_cnt, priv->phase); + priv->phase = 500; + priv->ndac_seq++; + goto ndac_exit; + } + if (priv->dav_tx) { + dbg_printk(1, "DAV high after %zu/%zu cmd %d " LINFMT ". No action.\n", + priv->w_cnt, priv->length, priv->cmd, LINVAL); + priv->dav_seq++; + goto ndac_exit; + } + + dbg_printk(3, "accepted %zu\n", priv->w_cnt - 1); + + gpiod_set_value(DAV, 1); // Data not available + priv->dav_tx = 1; + priv->phase = 510; + + if (priv->w_cnt >= priv->length) { // test for end of transfer + priv->write_done = 1; + priv->w_busy = 0; + wake_up_interruptible(&board->wait); + } + +ndac_exit: + spin_unlock_irqrestore(&priv->rw_lock, flags); + return IRQ_HANDLED; +} + +/*************************************************************************** + * * + * interrupt routine for SRQ line * + * * + ***************************************************************************/ + +static irqreturn_t bb_SRQ_interrupt(int irq, void *arg) +{ + struct gpib_board *board = arg; + + int val = gpiod_get_value(SRQ); + + dbg_printk(3, "> %d st: %4lx\n", val, board->status); + + if (!val) + set_bit(SRQI_NUM, &board->status); /* set_bit() is atomic */ + + wake_up_interruptible(&board->wait); + + return IRQ_HANDLED; +} + +static int bb_command(struct gpib_board *board, u8 *buffer, + size_t length, size_t *bytes_written) +{ + int ret; + struct bb_priv *priv = board->private_data; + int i; + + dbg_printk(2, "%p %p\n", buffer, board->buffer); + + /* the _ATN line has already been asserted by bb_take_control() */ + + priv->cmd = 1; + + ret = bb_write(board, buffer, length, 0, bytes_written); // no eoi + + for (i = 0; i < length; i++) { + if (buffer[i] == UNT) { + priv->talker_state = talker_idle; + } else { + if (buffer[i] == UNL) { + priv->listener_state = listener_idle; + } else { + if (buffer[i] == (MTA(board->pad))) { + priv->talker_state = talker_addressed; + priv->listener_state = listener_idle; + } else if (buffer[i] == (MLA(board->pad))) { + priv->listener_state = listener_addressed; + priv->talker_state = talker_idle; + } + } + } + } + + /* the _ATN line will be released by bb_go_to_stby */ + + priv->cmd = 0; + + return ret; +} + +/*************************************************************************** + * * + * Buffer print with decode for debug/trace * + * * + ***************************************************************************/ + +static char *cmd_string[32] = { + "", // 0x00 + "GTL", // 0x01 + "", // 0x02 + "", // 0x03 + "SDC", // 0x04 + "PPC", // 0x05 + "", // 0x06 + "", // 0x07 + "GET", // 0x08 + "TCT", // 0x09 + "", // 0x0a + "", // 0x0b + "", // 0x0c + "", // 0x0d + "", // 0x0e + "", // 0x0f + "", // 0x10 + "LLO", // 0x11 + "", // 0x12 + "", // 0x13 + "DCL", // 0x14 + "PPU", // 0x15 + "", // 0x16 + "", // 0x17 + "SPE", // 0x18 + "SPD", // 0x19 + "", // 0x1a + "", // 0x1b + "", // 0x1c + "", // 0x1d + "", // 0x1e + "CFE" // 0x1f +}; + +static void bb_buffer_print(struct gpib_board *board, unsigned char *buffer, size_t length, + int cmd, int eoi) +{ + int i; + + if (cmd) { + dbg_printk(2, "\n", length); + for (i = 0; i < length; i++) { + if (buffer[i] < 0x20) { + dbg_printk(3, "0x%x=%s\n", buffer[i], cmd_string[buffer[i]]); + } else if (buffer[i] == 0x3f) { + dbg_printk(3, "0x%x=%s\n", buffer[i], "UNL"); + } else if (buffer[i] == 0x5f) { + dbg_printk(3, "0x%x=%s\n", buffer[i], "UNT"); + } else if (buffer[i] < 0x60) { + dbg_printk(3, "0x%x=%s%d\n", buffer[i], + (buffer[i] & 0x40) ? "TLK" : "LSN", buffer[i] & 0x1F); + } else { + dbg_printk(3, "0x%x\n", buffer[i]); + } + } + } else { + dbg_printk(2, "\n", length, (eoi) ? "w.EOI" : " "); + for (i = 0; i < length; i++) + dbg_printk(2, "%3d 0x%x->%c\n", i, buffer[i], printable(buffer[i])); + } +} + +/*************************************************************************** + * * + * STATUS Management * + * * + ***************************************************************************/ +static void set_atn(struct gpib_board *board, int atn_asserted) +{ + struct bb_priv *priv = board->private_data; + + if (priv->listener_state != listener_idle && + priv->talker_state != talker_idle) { + dev_err(board->gpib_dev, "listener/talker state machine conflict\n"); + } + if (atn_asserted) { + if (priv->listener_state == listener_active) + priv->listener_state = listener_addressed; + if (priv->talker_state == talker_active) + priv->talker_state = talker_addressed; + SET_DIR_WRITE(priv); // need to be able to read bus NRFD/NDAC + } else { + if (priv->listener_state == listener_addressed) { + priv->listener_state = listener_active; + SET_DIR_READ(priv); // make sure holdoff is active when we unassert ATN + } + if (priv->talker_state == talker_addressed) + priv->talker_state = talker_active; + } + gpiod_direction_output(_ATN, !atn_asserted); +} + +static int bb_take_control(struct gpib_board *board, int synchronous) +{ + dbg_printk(2, "%d\n", synchronous); + set_atn(board, 1); + return 0; +} + +static int bb_go_to_standby(struct gpib_board *board) +{ + dbg_printk(2, "\n"); + set_atn(board, 0); + return 0; +} + +static int bb_request_system_control(struct gpib_board *board, int request_control) +{ + struct bb_priv *priv = board->private_data; + + dbg_printk(2, "%d\n", request_control); + if (!request_control) + return -EINVAL; + + gpiod_direction_output(REN, 1); /* user space must enable REN if needed */ + gpiod_direction_output(IFC, 1); /* user space must toggle IFC if needed */ + if (sn7516x) + gpiod_direction_output(DC, 0); /* enable ATN as output on SN75161/2 */ + + gpiod_direction_input(SRQ); + + ENABLE_IRQ(priv->irq_SRQ, IRQ_TYPE_EDGE_FALLING); + + return 0; +} + +static void bb_interface_clear(struct gpib_board *board, int assert) +{ + struct bb_priv *priv = board->private_data; + + dbg_printk(2, "%d\n", assert); + if (assert) { + gpiod_direction_output(IFC, 0); + priv->talker_state = talker_idle; + priv->listener_state = listener_idle; + set_bit(CIC_NUM, &board->status); + } else { + gpiod_direction_output(IFC, 1); + } +} + +static void bb_remote_enable(struct gpib_board *board, int enable) +{ + dbg_printk(2, "%d\n", enable); + if (enable) { + set_bit(REM_NUM, &board->status); + gpiod_direction_output(REN, 0); + } else { + clear_bit(REM_NUM, &board->status); + gpiod_direction_output(REN, 1); + } +} + +static int bb_enable_eos(struct gpib_board *board, u8 eos_byte, int compare_8_bits) +{ + struct bb_priv *priv = board->private_data; + + dbg_printk(2, "%s\n", "EOS_en"); + priv->eos = eos_byte; + priv->eos_flags = REOS; + if (compare_8_bits) + priv->eos_flags |= BIN; + + return 0; +} + +static void bb_disable_eos(struct gpib_board *board) +{ + struct bb_priv *priv = board->private_data; + + dbg_printk(2, "\n"); + priv->eos_flags &= ~REOS; +} + +static unsigned int bb_update_status(struct gpib_board *board, unsigned int clear_mask) +{ + struct bb_priv *priv = board->private_data; + + board->status &= ~clear_mask; + + if (gpiod_get_value(SRQ)) /* SRQ asserted low */ + clear_bit(SRQI_NUM, &board->status); + else + set_bit(SRQI_NUM, &board->status); + if (gpiod_get_value(_ATN)) /* ATN asserted low */ + clear_bit(ATN_NUM, &board->status); + else + set_bit(ATN_NUM, &board->status); + if (priv->talker_state == talker_active || + priv->talker_state == talker_addressed) + set_bit(TACS_NUM, &board->status); + else + clear_bit(TACS_NUM, &board->status); + + if (priv->listener_state == listener_active || + priv->listener_state == listener_addressed) + set_bit(LACS_NUM, &board->status); + else + clear_bit(LACS_NUM, &board->status); + + dbg_printk(2, "0x%lx mask 0x%x\n", board->status, clear_mask); + + return board->status; +} + +static int bb_primary_address(struct gpib_board *board, unsigned int address) +{ + dbg_printk(2, "%d\n", address); + board->pad = address; + return 0; +} + +static int bb_secondary_address(struct gpib_board *board, unsigned int address, int enable) +{ + dbg_printk(2, "%d %d\n", address, enable); + if (enable) + board->sad = address; + return 0; +} + +static int bb_parallel_poll(struct gpib_board *board, u8 *result) +{ + return -ENOENT; +} + +static void bb_parallel_poll_configure(struct gpib_board *board, u8 config) +{ +} + +static void bb_parallel_poll_response(struct gpib_board *board, int ist) +{ +} + +static void bb_serial_poll_response(struct gpib_board *board, u8 status) +{ +} + +static u8 bb_serial_poll_status(struct gpib_board *board) +{ + return 0; // -ENOENT; +} + +static int bb_t1_delay(struct gpib_board *board, unsigned int nano_sec) +{ + struct bb_priv *priv = board->private_data; + + if (nano_sec <= 350) + priv->t1_delay = 350; + else if (nano_sec <= 1100) + priv->t1_delay = 1100; + else + priv->t1_delay = 2000; + + dbg_printk(2, "t1 delay set to %d nanosec\n", priv->t1_delay); + + return priv->t1_delay; +} + +static void bb_return_to_local(struct gpib_board *board) +{ +} + +static int bb_line_status(const struct gpib_board *board) +{ + int line_status = VALID_ALL; + + if (gpiod_get_value(REN) == 0) + line_status |= BUS_REN; + if (gpiod_get_value(IFC) == 0) + line_status |= BUS_IFC; + if (gpiod_get_value(NDAC) == 0) + line_status |= BUS_NDAC; + if (gpiod_get_value(NRFD) == 0) + line_status |= BUS_NRFD; + if (gpiod_get_value(DAV) == 0) + line_status |= BUS_DAV; + if (gpiod_get_value(EOI) == 0) + line_status |= BUS_EOI; + if (gpiod_get_value(_ATN) == 0) + line_status |= BUS_ATN; + if (gpiod_get_value(SRQ) == 0) + line_status |= BUS_SRQ; + + dbg_printk(2, "status lines: %4x\n", line_status); + + return line_status; +} + +/*************************************************************************** + * * + * Module Management * + * * + ***************************************************************************/ + +static int allocate_private(struct gpib_board *board) +{ + board->private_data = kzalloc(sizeof(struct bb_priv), GFP_KERNEL); + if (!board->private_data) + return -1; + return 0; +} + +static void free_private(struct gpib_board *board) +{ + kfree(board->private_data); + board->private_data = NULL; +} + +static int bb_get_irq(struct gpib_board *board, char *name, + struct gpio_desc *gpio, int *irq, + irq_handler_t handler, irq_handler_t thread_fn, unsigned long flags) +{ + if (!gpio) + return -1; + gpiod_direction_input(gpio); + *irq = gpiod_to_irq(gpio); + dbg_printk(2, "IRQ %s: %d\n", name, *irq); + if (*irq < 0) { + dev_err(board->gpib_dev, "can't get IRQ for %s\n", name); + return -1; + } + if (request_threaded_irq(*irq, handler, thread_fn, flags, name, board)) { + dev_err(board->gpib_dev, "can't request IRQ for %s %d\n", name, *irq); + *irq = 0; + return -1; + } + DISABLE_IRQ(*irq); + return 0; +} + +static void bb_free_irq(struct gpib_board *board, int *irq, char *name) +{ + if (*irq) { + free_irq(*irq, board); + dbg_printk(2, "IRQ %d(%s) freed\n", *irq, name); + *irq = 0; + } +} + +static void release_gpios(void) +{ + int j; + + for (j = 0 ; j < NUM_PINS ; j++) { + if (all_descriptors[j]) { + gpiod_put(all_descriptors[j]); + all_descriptors[j] = NULL; + } + } +} + +static int allocate_gpios(struct gpib_board *board) +{ + int j; + int table_index = 0; + char name[256]; + struct gpio_desc *desc; + struct gpiod_lookup_table *lookup_table; + + if (!board->gpib_dev) { + pr_err("NULL gpib dev for board\n"); + return -ENOENT; + } + + lookup_table = lookup_tables[table_index]; + lookup_table->dev_id = dev_name(board->gpib_dev); + gpiod_add_lookup_table(lookup_table); + dbg_printk(1, "Allocating gpios using table index %d\n", table_index); + + for (j = 0 ; j < NUM_PINS ; j++) { + if (gpios_vector[j] < 0) + continue; + /* name not really used in gpiod_get_index() */ + sprintf(name, "GPIO%d", gpios_vector[j]); +try_again: + dbg_printk(1, "Allocating gpio %s pin no %d\n", name, gpios_vector[j]); + desc = gpiod_get_index(board->gpib_dev, name, gpios_vector[j], GPIOD_IN); + + if (IS_ERR(desc)) { + gpiod_remove_lookup_table(lookup_table); + table_index++; + lookup_table = lookup_tables[table_index]; + if (!lookup_table) { + dev_err(board->gpib_dev, "Unable to obtain gpio descriptor for pin %d error %ld\n", + gpios_vector[j], PTR_ERR(desc)); + goto alloc_gpios_fail; + } + dbg_printk(1, "Allocation failed, now using table_index %d\n", table_index); + lookup_table->dev_id = dev_name(board->gpib_dev); + gpiod_add_lookup_table(lookup_table); + goto try_again; + } + all_descriptors[j] = desc; + } + + gpiod_remove_lookup_table(lookup_table); + + return 0; + +alloc_gpios_fail: + release_gpios(); + return -1; +} + +static void bb_detach(struct gpib_board *board) +{ + struct bb_priv *priv = board->private_data; + + dbg_printk(2, "Enter with data %p\n", board->private_data); + if (!board->private_data) + return; + + bb_free_irq(board, &priv->irq_DAV, NAME "_DAV"); + bb_free_irq(board, &priv->irq_NRFD, NAME "_NRFD"); + bb_free_irq(board, &priv->irq_NDAC, NAME "_NDAC"); + bb_free_irq(board, &priv->irq_SRQ, NAME "_SRQ"); + + if (strcmp(PINMAP_2, pin_map) == 0) { /* YOGA */ + gpiod_set_value(YOGA_ENABLE, 0); + } + + release_gpios(); + + dbg_printk(2, "detached board: %d\n", board->minor); + dbg_printk(0, "NRFD: idle %d, seq %d, NDAC: idle %d, seq %d DAV: idle %d seq: %d all: %ld", + priv->nrfd_idle, priv->nrfd_seq, + priv->ndac_idle, priv->ndac_seq, + priv->dav_idle, priv->dav_seq, priv->all_irqs); + + free_private(board); +} + +static int bb_attach(struct gpib_board *board, const struct gpib_board_config *config) +{ + struct bb_priv *priv; + int retval = 0; + + dbg_printk(2, "%s\n", "Enter ..."); + + board->status = 0; + + if (allocate_private(board)) + return -ENOMEM; + priv = board->private_data; + priv->direction = -1; + priv->t1_delay = 2000; + priv->listener_state = listener_idle; + priv->talker_state = talker_idle; + + sn7516x = sn7516x_used; + if (strcmp(PINMAP_0, pin_map) == 0) { + if (!sn7516x) { + gpios_vector[&(PE) - &all_descriptors[0]] = -1; + gpios_vector[&(DC) - &all_descriptors[0]] = -1; + gpios_vector[&(TE) - &all_descriptors[0]] = -1; + } + } else if (strcmp(PINMAP_1, pin_map) == 0) { + if (!sn7516x) { + gpios_vector[&(PE) - &all_descriptors[0]] = -1; + gpios_vector[&(DC) - &all_descriptors[0]] = -1; + gpios_vector[&(TE) - &all_descriptors[0]] = -1; + } + gpios_vector[&(REN) - &all_descriptors[0]] = 0; /* 27 -> 0 REN on GPIB pin 0 */ + } else if (strcmp(PINMAP_2, pin_map) == 0) { /* YOGA */ + sn7516x = 0; + gpios_vector[&(D03) - &all_descriptors[0]] = YOGA_D03_pin_nr; + gpios_vector[&(D04) - &all_descriptors[0]] = YOGA_D04_pin_nr; + gpios_vector[&(D05) - &all_descriptors[0]] = YOGA_D05_pin_nr; + gpios_vector[&(D06) - &all_descriptors[0]] = YOGA_D06_pin_nr; + gpios_vector[&(PE) - &all_descriptors[0]] = -1; + gpios_vector[&(DC) - &all_descriptors[0]] = -1; + } else { + dev_err(board->gpib_dev, "Unrecognized pin map %s\n", pin_map); + goto bb_attach_fail; + } + dbg_printk(0, "Using pin map \"%s\" %s\n", pin_map, (sn7516x) ? + " with SN7516x driver support" : ""); + + if (allocate_gpios(board)) + goto bb_attach_fail; + +/* + * Configure SN7516X control lines. + * drive ATN, IFC and REN as outputs only when master + * i.e. system controller. In this mode can only be the CIC + * When not master then enable device mode ATN, IFC & REN as inputs + */ + if (sn7516x) { + gpiod_direction_output(DC, 0); + gpiod_direction_output(TE, 1); + gpiod_direction_output(PE, 1); + } +/* Set main control lines to a known state */ + gpiod_direction_output(IFC, 1); + gpiod_direction_output(REN, 1); + gpiod_direction_output(_ATN, 1); + + if (strcmp(PINMAP_2, pin_map) == 0) { /* YOGA: enable level shifters */ + gpiod_direction_output(YOGA_ENABLE, 1); + } + + spin_lock_init(&priv->rw_lock); + + /* request DAV interrupt for read */ + if (bb_get_irq(board, NAME "_DAV", DAV, &priv->irq_DAV, bb_DAV_interrupt, NULL, + IRQF_TRIGGER_NONE)) + goto bb_attach_fail_r; + + /* request NRFD interrupt for write */ + if (bb_get_irq(board, NAME "_NRFD", NRFD, &priv->irq_NRFD, bb_NRFD_interrupt, NULL, + IRQF_TRIGGER_NONE)) + goto bb_attach_fail_r; + + /* request NDAC interrupt for write */ + if (bb_get_irq(board, NAME "_NDAC", NDAC, &priv->irq_NDAC, bb_NDAC_interrupt, NULL, + IRQF_TRIGGER_NONE)) + goto bb_attach_fail_r; + + /* request SRQ interrupt for Service Request */ + if (bb_get_irq(board, NAME "_SRQ", SRQ, &priv->irq_SRQ, bb_SRQ_interrupt, NULL, + IRQF_TRIGGER_NONE)) + goto bb_attach_fail_r; + + dbg_printk(0, "attached board %d\n", board->minor); + goto bb_attach_out; + +bb_attach_fail_r: + release_gpios(); +bb_attach_fail: + retval = -1; +bb_attach_out: + return retval; +} + +static struct gpib_interface bb_interface = { + .name = NAME, + .attach = bb_attach, + .detach = bb_detach, + .read = bb_read, + .write = bb_write, + .command = bb_command, + .take_control = bb_take_control, + .go_to_standby = bb_go_to_standby, + .request_system_control = bb_request_system_control, + .interface_clear = bb_interface_clear, + .remote_enable = bb_remote_enable, + .enable_eos = bb_enable_eos, + .disable_eos = bb_disable_eos, + .parallel_poll = bb_parallel_poll, + .parallel_poll_configure = bb_parallel_poll_configure, + .parallel_poll_response = bb_parallel_poll_response, + .line_status = bb_line_status, + .update_status = bb_update_status, + .primary_address = bb_primary_address, + .secondary_address = bb_secondary_address, + .serial_poll_response = bb_serial_poll_response, + .serial_poll_status = bb_serial_poll_status, + .t1_delay = bb_t1_delay, + .return_to_local = bb_return_to_local, +}; + +static int __init bb_init_module(void) +{ + int result = gpib_register_driver(&bb_interface, THIS_MODULE); + + if (result) { + pr_err("gpib_register_driver failed: error = %d\n", result); + return result; + } + + return 0; +} + +static void __exit bb_exit_module(void) +{ + gpib_unregister_driver(&bb_interface); +} + +module_init(bb_init_module); +module_exit(bb_exit_module); + +/*************************************************************************** + * * + * UTILITY Functions * + * * + ***************************************************************************/ +inline long usec_diff(struct timespec64 *a, struct timespec64 *b) +{ + return ((a->tv_sec - b->tv_sec) * 1000000 + + (a->tv_nsec - b->tv_nsec) / 1000); +} + +static inline int check_for_eos(struct bb_priv *priv, u8 byte) +{ + if (priv->eos_check) + return 0; + + if (priv->eos_check_8) { + if (priv->eos == byte) + return 1; + } else { + if (priv->eos_mask_7 == (byte & 0x7f)) + return 1; + } + return 0; +} + +static void set_data_lines_output(void) +{ + gpiod_direction_output(D01, 1); + gpiod_direction_output(D02, 1); + gpiod_direction_output(D03, 1); + gpiod_direction_output(D04, 1); + gpiod_direction_output(D05, 1); + gpiod_direction_output(D06, 1); + gpiod_direction_output(D07, 1); + gpiod_direction_output(D08, 1); +} + +static void set_data_lines(u8 byte) +{ + gpiod_set_value(D01, !(byte & 0x01)); + gpiod_set_value(D02, !(byte & 0x02)); + gpiod_set_value(D03, !(byte & 0x04)); + gpiod_set_value(D04, !(byte & 0x08)); + gpiod_set_value(D05, !(byte & 0x10)); + gpiod_set_value(D06, !(byte & 0x20)); + gpiod_set_value(D07, !(byte & 0x40)); + gpiod_set_value(D08, !(byte & 0x80)); +} + +static u8 get_data_lines(void) +{ + u8 ret; + + ret = gpiod_get_value(D01); + ret |= gpiod_get_value(D02) << 1; + ret |= gpiod_get_value(D03) << 2; + ret |= gpiod_get_value(D04) << 3; + ret |= gpiod_get_value(D05) << 4; + ret |= gpiod_get_value(D06) << 5; + ret |= gpiod_get_value(D07) << 6; + ret |= gpiod_get_value(D08) << 7; + return ~ret; +} + +static void set_data_lines_input(void) +{ + gpiod_direction_input(D01); + gpiod_direction_input(D02); + gpiod_direction_input(D03); + gpiod_direction_input(D04); + gpiod_direction_input(D05); + gpiod_direction_input(D06); + gpiod_direction_input(D07); + gpiod_direction_input(D08); +} + +static inline void SET_DIR_WRITE(struct bb_priv *priv) +{ + if (priv->direction == DIR_WRITE) + return; + + gpiod_direction_input(NRFD); + gpiod_direction_input(NDAC); + set_data_lines_output(); + gpiod_direction_output(DAV, 1); + gpiod_direction_output(EOI, 1); + + if (sn7516x) { + gpiod_set_value(PE, 1); /* set data lines to transmit on sn75160b */ + gpiod_set_value(TE, 1); /* set NDAC and NRFD to receive and DAV to transmit */ + } + + priv->direction = DIR_WRITE; +} + +static inline void SET_DIR_READ(struct bb_priv *priv) +{ + if (priv->direction == DIR_READ) + return; + + gpiod_direction_input(DAV); + gpiod_direction_input(EOI); + + set_data_lines_input(); + + if (sn7516x) { + gpiod_set_value(PE, 0); /* set data lines to receive on sn75160b */ + gpiod_set_value(TE, 0); /* set NDAC and NRFD to transmit and DAV to receive */ + } + + gpiod_direction_output(NRFD, 0); /* hold off the talker */ + gpiod_direction_output(NDAC, 0); /* data not accepted */ + + priv->direction = DIR_READ; +} diff --git a/drivers/gpib/hp_82335/Makefile b/drivers/gpib/hp_82335/Makefile new file mode 100644 index 000000000000..305ce44ee48a --- /dev/null +++ b/drivers/gpib/hp_82335/Makefile @@ -0,0 +1,4 @@ + +obj-$(CONFIG_GPIB_HP82335) += hp82335.o + + diff --git a/drivers/gpib/hp_82335/hp82335.c b/drivers/gpib/hp_82335/hp82335.c new file mode 100644 index 000000000000..d0e47ef77c87 --- /dev/null +++ b/drivers/gpib/hp_82335/hp82335.c @@ -0,0 +1,371 @@ +// SPDX-License-Identifier: GPL-2.0 + +/*************************************************************************** + * copyright : (C) 2002 by Frank Mori Hess * + ***************************************************************************/ + +/* + * should enable ATN interrupts (and update board->status on occurrence), + * implement recovery from bus errors (if necessary) + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#define dev_fmt pr_fmt +#define DRV_NAME KBUILD_MODNAME + +#include "hp82335.h" +#include +#include +#include +#include +#include +#include +#include + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("GPIB driver for HP 82335 interface cards"); + +static int hp82335_attach(struct gpib_board *board, const struct gpib_board_config *config); +static void hp82335_detach(struct gpib_board *board); +static irqreturn_t hp82335_interrupt(int irq, void *arg); + +// wrappers for interface functions +static int hp82335_read(struct gpib_board *board, u8 *buffer, size_t length, + int *end, size_t *bytes_read) +{ + struct hp82335_priv *priv = board->private_data; + + return tms9914_read(board, &priv->tms9914_priv, buffer, length, end, bytes_read); +} + +static int hp82335_write(struct gpib_board *board, u8 *buffer, size_t length, int send_eoi, + size_t *bytes_written) +{ + struct hp82335_priv *priv = board->private_data; + + return tms9914_write(board, &priv->tms9914_priv, buffer, length, send_eoi, bytes_written); +} + +static int hp82335_command(struct gpib_board *board, u8 *buffer, size_t length, + size_t *bytes_written) +{ + struct hp82335_priv *priv = board->private_data; + + return tms9914_command(board, &priv->tms9914_priv, buffer, length, bytes_written); +} + +static int hp82335_take_control(struct gpib_board *board, int synchronous) +{ + struct hp82335_priv *priv = board->private_data; + + return tms9914_take_control(board, &priv->tms9914_priv, synchronous); +} + +static int hp82335_go_to_standby(struct gpib_board *board) +{ + struct hp82335_priv *priv = board->private_data; + + return tms9914_go_to_standby(board, &priv->tms9914_priv); +} + +static int hp82335_request_system_control(struct gpib_board *board, int request_control) +{ + struct hp82335_priv *priv = board->private_data; + + return tms9914_request_system_control(board, &priv->tms9914_priv, request_control); +} + +static void hp82335_interface_clear(struct gpib_board *board, int assert) +{ + struct hp82335_priv *priv = board->private_data; + + tms9914_interface_clear(board, &priv->tms9914_priv, assert); +} + +static void hp82335_remote_enable(struct gpib_board *board, int enable) +{ + struct hp82335_priv *priv = board->private_data; + + tms9914_remote_enable(board, &priv->tms9914_priv, enable); +} + +static int hp82335_enable_eos(struct gpib_board *board, u8 eos_byte, int compare_8_bits) +{ + struct hp82335_priv *priv = board->private_data; + + return tms9914_enable_eos(board, &priv->tms9914_priv, eos_byte, compare_8_bits); +} + +static void hp82335_disable_eos(struct gpib_board *board) +{ + struct hp82335_priv *priv = board->private_data; + + tms9914_disable_eos(board, &priv->tms9914_priv); +} + +static unsigned int hp82335_update_status(struct gpib_board *board, unsigned int clear_mask) +{ + struct hp82335_priv *priv = board->private_data; + + return tms9914_update_status(board, &priv->tms9914_priv, clear_mask); +} + +static int hp82335_primary_address(struct gpib_board *board, unsigned int address) +{ + struct hp82335_priv *priv = board->private_data; + + return tms9914_primary_address(board, &priv->tms9914_priv, address); +} + +static int hp82335_secondary_address(struct gpib_board *board, unsigned int address, int enable) +{ + struct hp82335_priv *priv = board->private_data; + + return tms9914_secondary_address(board, &priv->tms9914_priv, address, enable); +} + +static int hp82335_parallel_poll(struct gpib_board *board, u8 *result) +{ + struct hp82335_priv *priv = board->private_data; + + return tms9914_parallel_poll(board, &priv->tms9914_priv, result); +} + +static void hp82335_parallel_poll_configure(struct gpib_board *board, u8 config) +{ + struct hp82335_priv *priv = board->private_data; + + tms9914_parallel_poll_configure(board, &priv->tms9914_priv, config); +} + +static void hp82335_parallel_poll_response(struct gpib_board *board, int ist) +{ + struct hp82335_priv *priv = board->private_data; + + tms9914_parallel_poll_response(board, &priv->tms9914_priv, ist); +} + +static void hp82335_serial_poll_response(struct gpib_board *board, u8 status) +{ + struct hp82335_priv *priv = board->private_data; + + tms9914_serial_poll_response(board, &priv->tms9914_priv, status); +} + +static u8 hp82335_serial_poll_status(struct gpib_board *board) +{ + struct hp82335_priv *priv = board->private_data; + + return tms9914_serial_poll_status(board, &priv->tms9914_priv); +} + +static int hp82335_line_status(const struct gpib_board *board) +{ + struct hp82335_priv *priv = board->private_data; + + return tms9914_line_status(board, &priv->tms9914_priv); +} + +static int hp82335_t1_delay(struct gpib_board *board, unsigned int nano_sec) +{ + struct hp82335_priv *priv = board->private_data; + + return tms9914_t1_delay(board, &priv->tms9914_priv, nano_sec); +} + +static void hp82335_return_to_local(struct gpib_board *board) +{ + struct hp82335_priv *priv = board->private_data; + + tms9914_return_to_local(board, &priv->tms9914_priv); +} + +static struct gpib_interface hp82335_interface = { + .name = "hp82335", + .attach = hp82335_attach, + .detach = hp82335_detach, + .read = hp82335_read, + .write = hp82335_write, + .command = hp82335_command, + .request_system_control = hp82335_request_system_control, + .take_control = hp82335_take_control, + .go_to_standby = hp82335_go_to_standby, + .interface_clear = hp82335_interface_clear, + .remote_enable = hp82335_remote_enable, + .enable_eos = hp82335_enable_eos, + .disable_eos = hp82335_disable_eos, + .parallel_poll = hp82335_parallel_poll, + .parallel_poll_configure = hp82335_parallel_poll_configure, + .parallel_poll_response = hp82335_parallel_poll_response, + .local_parallel_poll_mode = NULL, // XXX + .line_status = hp82335_line_status, + .update_status = hp82335_update_status, + .primary_address = hp82335_primary_address, + .secondary_address = hp82335_secondary_address, + .serial_poll_response = hp82335_serial_poll_response, + .serial_poll_status = hp82335_serial_poll_status, + .t1_delay = hp82335_t1_delay, + .return_to_local = hp82335_return_to_local, +}; + +static int hp82335_allocate_private(struct gpib_board *board) +{ + board->private_data = kzalloc(sizeof(struct hp82335_priv), GFP_KERNEL); + if (!board->private_data) + return -1; + return 0; +} + +static void hp82335_free_private(struct gpib_board *board) +{ + kfree(board->private_data); + board->private_data = NULL; +} + +static inline unsigned int tms9914_to_hp82335_offset(unsigned int register_num) +{ + return 0x1ff8 + register_num; +} + +static u8 hp82335_read_byte(struct tms9914_priv *priv, unsigned int register_num) +{ + return tms9914_iomem_read_byte(priv, tms9914_to_hp82335_offset(register_num)); +} + +static void hp82335_write_byte(struct tms9914_priv *priv, u8 data, unsigned int register_num) +{ + tms9914_iomem_write_byte(priv, data, tms9914_to_hp82335_offset(register_num)); +} + +static void hp82335_clear_interrupt(struct hp82335_priv *hp_priv) +{ + struct tms9914_priv *tms_priv = &hp_priv->tms9914_priv; + + writeb(0, tms_priv->mmiobase + HPREG_INTR_CLEAR); +} + +static int hp82335_attach(struct gpib_board *board, const struct gpib_board_config *config) +{ + struct hp82335_priv *hp_priv; + struct tms9914_priv *tms_priv; + int retval; + const unsigned long upper_iomem_base = config->ibbase + hp82335_rom_size; + + board->status = 0; + + if (hp82335_allocate_private(board)) + return -ENOMEM; + hp_priv = board->private_data; + tms_priv = &hp_priv->tms9914_priv; + tms_priv->read_byte = hp82335_read_byte; + tms_priv->write_byte = hp82335_write_byte; + tms_priv->offset = 1; + + switch (config->ibbase) { + case 0xc4000: + case 0xc8000: + case 0xcc000: + case 0xd0000: + case 0xd4000: + case 0xd8000: + case 0xdc000: + case 0xe0000: + case 0xe4000: + case 0xe8000: + case 0xec000: + case 0xf0000: + case 0xf4000: + case 0xf8000: + case 0xfc000: + break; + default: + dev_err(board->gpib_dev, "invalid base io address 0x%x\n", config->ibbase); + return -EINVAL; + } + if (!request_mem_region(upper_iomem_base, hp82335_upper_iomem_size, "hp82335")) { + dev_err(board->gpib_dev, "failed to allocate io memory region 0x%lx-0x%lx\n", + upper_iomem_base, upper_iomem_base + hp82335_upper_iomem_size - 1); + return -EBUSY; + } + hp_priv->raw_iobase = upper_iomem_base; + tms_priv->mmiobase = ioremap(upper_iomem_base, hp82335_upper_iomem_size); + + retval = request_irq(config->ibirq, hp82335_interrupt, 0, DRV_NAME, board); + if (retval) { + dev_err(board->gpib_dev, "can't request IRQ %d\n", config->ibirq); + return retval; + } + hp_priv->irq = config->ibirq; + + tms9914_board_reset(tms_priv); + + hp82335_clear_interrupt(hp_priv); + + writeb(INTR_ENABLE, tms_priv->mmiobase + HPREG_CCR); + + tms9914_online(board, tms_priv); + + return 0; +} + +static void hp82335_detach(struct gpib_board *board) +{ + struct hp82335_priv *hp_priv = board->private_data; + struct tms9914_priv *tms_priv; + + if (hp_priv) { + tms_priv = &hp_priv->tms9914_priv; + if (hp_priv->irq) + free_irq(hp_priv->irq, board); + if (tms_priv->mmiobase) { + writeb(0, tms_priv->mmiobase + HPREG_CCR); + tms9914_board_reset(tms_priv); + iounmap(tms_priv->mmiobase); + } + if (hp_priv->raw_iobase) + release_mem_region(hp_priv->raw_iobase, hp82335_upper_iomem_size); + } + hp82335_free_private(board); +} + +static int __init hp82335_init_module(void) +{ + int result = gpib_register_driver(&hp82335_interface, THIS_MODULE); + + if (result) { + pr_err("gpib_register_driver failed: error = %d\n", result); + return result; + } + + return 0; +} + +static void __exit hp82335_exit_module(void) +{ + gpib_unregister_driver(&hp82335_interface); +} + +module_init(hp82335_init_module); +module_exit(hp82335_exit_module); + +/* + * GPIB interrupt service routines + */ + +static irqreturn_t hp82335_interrupt(int irq, void *arg) +{ + int status1, status2; + struct gpib_board *board = arg; + struct hp82335_priv *priv = board->private_data; + unsigned long flags; + irqreturn_t retval; + + spin_lock_irqsave(&board->spinlock, flags); + status1 = read_byte(&priv->tms9914_priv, ISR0); + status2 = read_byte(&priv->tms9914_priv, ISR1); + hp82335_clear_interrupt(priv); + retval = tms9914_interrupt_have_status(board, &priv->tms9914_priv, status1, status2); + spin_unlock_irqrestore(&board->spinlock, flags); + return retval; +} + diff --git a/drivers/gpib/hp_82335/hp82335.h b/drivers/gpib/hp_82335/hp82335.h new file mode 100644 index 000000000000..0c252a712ec9 --- /dev/null +++ b/drivers/gpib/hp_82335/hp82335.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/*************************************************************************** + * copyright : (C) 2002 by Frank Mori Hess * + ***************************************************************************/ + +#ifndef _HP82335_H +#define _HP82335_H + +#include "tms9914.h" +#include "gpibP.h" + +// struct which defines private_data for board +struct hp82335_priv { + struct tms9914_priv tms9914_priv; + unsigned int irq; + unsigned long raw_iobase; +}; + +// size of io memory region used +static const int hp82335_rom_size = 0x2000; +static const int hp82335_upper_iomem_size = 0x2000; + +// hp82335 register offsets +enum hp_read_regs { + HPREG_CSR = 0x17f8, + HPREG_STATUS = 0x1ffc, +}; + +enum hp_write_regs { + HPREG_INTR_CLEAR = 0x17f7, + HPREG_CCR = HPREG_CSR, +}; + +enum ccr_bits { + DMA_ENABLE = (1 << 0), /* DMA enable */ + DMA_CHAN_SELECT = (1 << 1), /* DMA channel select O=3,1=2 */ + INTR_ENABLE = (1 << 2), /* interrupt enable */ + SYS_DISABLE = (1 << 3), /* system controller disable */ +}; + +enum csr_bits { + SWITCH6 = (1 << 0), /* switch 6 position */ + SWITCH5 = (1 << 1), /* switch 5 position */ + SYS_CONTROLLER = (1 << 2), /* system controller bit */ + DMA_ENABLE_STATUS = (1 << 4), /* DMA enabled */ + DMA_CHAN_STATUS = (1 << 5), /* DMA channel 0=3,1=2 */ + INTR_ENABLE_STATUS = (1 << 6), /* Interrupt enable */ + INTR_PENDING = (1 << 7), /* Interrupt Pending */ +}; + +#endif // _HP82335_H diff --git a/drivers/gpib/hp_82341/Makefile b/drivers/gpib/hp_82341/Makefile new file mode 100644 index 000000000000..21367310a17e --- /dev/null +++ b/drivers/gpib/hp_82341/Makefile @@ -0,0 +1,2 @@ + +obj-$(CONFIG_GPIB_HP82341) += hp_82341.o diff --git a/drivers/gpib/hp_82341/hp_82341.c b/drivers/gpib/hp_82341/hp_82341.c new file mode 100644 index 000000000000..1a2ad0560e14 --- /dev/null +++ b/drivers/gpib/hp_82341/hp_82341.c @@ -0,0 +1,907 @@ +// SPDX-License-Identifier: GPL-2.0 + +/*************************************************************************** + * Driver for hp 82341a/b/c/d boards. * + * Might be worth merging with Agilent 82350b driver. * + * copyright : (C) 2002, 2005 by Frank Mori Hess * + ***************************************************************************/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#define dev_fmt pr_fmt +#define DRV_NAME KBUILD_MODNAME + +#include "hp_82341.h" +#include +#include +#include +#include +#include +#include +#include + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("GPIB driver for hp 82341a/b/c/d boards"); + +static unsigned short read_and_clear_event_status(struct gpib_board *board); +static void set_transfer_counter(struct hp_82341_priv *hp_priv, int count); +static int read_transfer_counter(struct hp_82341_priv *hp_priv); +static int hp_82341_write(struct gpib_board *board, u8 *buffer, size_t length, int send_eoi, + size_t *bytes_written); +static irqreturn_t hp_82341_interrupt(int irq, void *arg); + +static int hp_82341_accel_read(struct gpib_board *board, u8 *buffer, size_t length, int *end, + size_t *bytes_read) +{ + struct hp_82341_priv *hp_priv = board->private_data; + struct tms9914_priv *tms_priv = &hp_priv->tms9914_priv; + int retval = 0; + unsigned short event_status; + int i; + int num_fifo_bytes; + // hardware doesn't support checking for end-of-string character when using fifo + if (tms_priv->eos_flags & REOS) + return tms9914_read(board, tms_priv, buffer, length, end, bytes_read); + + clear_bit(DEV_CLEAR_BN, &tms_priv->state); + + read_and_clear_event_status(board); + *end = 0; + *bytes_read = 0; + if (length == 0) + return 0; + // disable fifo for the moment + outb(DIRECTION_GPIB_TO_HOST_BIT, hp_priv->iobase[3] + BUFFER_CONTROL_REG); + /* + * Handle corner case of board not in holdoff and one byte has slipped in already. + * Also, board sometimes has problems (spurious 1 byte reads) when read fifo is + * started up with board in TACS under certain data holdoff conditions. + * Doing a 1 byte tms9914-style read avoids these problems. + */ + if (/*tms_priv->holdoff_active == 0 && */length > 1) { + size_t num_bytes; + + retval = tms9914_read(board, tms_priv, buffer, 1, end, &num_bytes); + *bytes_read += num_bytes; + if (retval < 0) + dev_err(board->gpib_dev, "tms9914_read failed retval=%i\n", retval); + if (retval < 0 || *end) + return retval; + ++buffer; + --length; + } + tms9914_set_holdoff_mode(tms_priv, TMS9914_HOLDOFF_EOI); + tms9914_release_holdoff(tms_priv); + outb(0x00, hp_priv->iobase[3] + BUFFER_FLUSH_REG); + i = 0; + num_fifo_bytes = length - 1; + while (i < num_fifo_bytes && *end == 0) { + int block_size; + int j; + int count; + + block_size = min(num_fifo_bytes - i, hp_82341_fifo_size); + set_transfer_counter(hp_priv, block_size); + outb(ENABLE_TI_BUFFER_BIT | DIRECTION_GPIB_TO_HOST_BIT, hp_priv->iobase[3] + + BUFFER_CONTROL_REG); + if (inb(hp_priv->iobase[0] + STREAM_STATUS_REG) & HALTED_STATUS_BIT) + outb(RESTART_STREAM_BIT, hp_priv->iobase[0] + STREAM_STATUS_REG); + + clear_bit(READ_READY_BN, &tms_priv->state); + + retval = wait_event_interruptible(board->wait, + ((event_status = + read_and_clear_event_status(board)) & + (TERMINAL_COUNT_EVENT_BIT | + BUFFER_END_EVENT_BIT)) || + test_bit(DEV_CLEAR_BN, &tms_priv->state) || + test_bit(TIMO_NUM, &board->status)); + if (retval) { + retval = -ERESTARTSYS; + break; + } + // have to disable buffer before we can read from buffer port + outb(DIRECTION_GPIB_TO_HOST_BIT, hp_priv->iobase[3] + BUFFER_CONTROL_REG); + count = block_size - read_transfer_counter(hp_priv); + j = 0; + while (j < count && i < num_fifo_bytes) { + unsigned short data_word = inw(hp_priv->iobase[3] + BUFFER_PORT_LOW_REG); + + buffer[i++] = data_word & 0xff; + ++j; + if (j < count && i < num_fifo_bytes) { + buffer[i++] = (data_word >> 8) & 0xff; + ++j; + } + } + if (event_status & BUFFER_END_EVENT_BIT) { + clear_bit(RECEIVED_END_BN, &tms_priv->state); + + *end = 1; + tms_priv->holdoff_active = 1; + } + if (test_bit(TIMO_NUM, &board->status)) { + retval = -ETIMEDOUT; + break; + } + if (test_bit(DEV_CLEAR_BN, &tms_priv->state)) { + retval = -EINTR; + break; + } + } + *bytes_read += i; + buffer += i; + length -= i; + if (retval < 0) + return retval; + // read last byte if we havn't received an END yet + if (*end == 0) { + size_t num_bytes; + // try to make sure we holdoff after last byte read + retval = tms9914_read(board, tms_priv, buffer, length, end, &num_bytes); + *bytes_read += num_bytes; + if (retval < 0) + return retval; + } + return 0; +} + +static int restart_write_fifo(struct gpib_board *board, struct hp_82341_priv *hp_priv) +{ + struct tms9914_priv *tms_priv = &hp_priv->tms9914_priv; + + if ((inb(hp_priv->iobase[0] + STREAM_STATUS_REG) & HALTED_STATUS_BIT) == 0) + return 0; + while (1) { + int status; + + // restart doesn't work if data holdoff is in effect + status = tms9914_line_status(board, tms_priv); + if ((status & BUS_NRFD) == 0) { + outb(RESTART_STREAM_BIT, hp_priv->iobase[0] + STREAM_STATUS_REG); + return 0; + } + if (test_bit(DEV_CLEAR_BN, &tms_priv->state)) + return -EINTR; + if (test_bit(TIMO_NUM, &board->status)) + return -ETIMEDOUT; + if (msleep_interruptible(1)) + return -EINTR; + } + return 0; +} + +static int hp_82341_accel_write(struct gpib_board *board, u8 *buffer, size_t length, + int send_eoi, size_t *bytes_written) +{ + struct hp_82341_priv *hp_priv = board->private_data; + struct tms9914_priv *tms_priv = &hp_priv->tms9914_priv; + int i, j; + unsigned short event_status; + int retval = 0; + int fifo_xfer_len = length; + + *bytes_written = 0; + if (send_eoi) + --fifo_xfer_len; + + clear_bit(DEV_CLEAR_BN, &tms_priv->state); + + read_and_clear_event_status(board); + outb(0, hp_priv->iobase[3] + BUFFER_CONTROL_REG); + outb(0x00, hp_priv->iobase[3] + BUFFER_FLUSH_REG); + for (i = 0; i < fifo_xfer_len;) { + int block_size; + + block_size = min(fifo_xfer_len - i, hp_82341_fifo_size); + set_transfer_counter(hp_priv, block_size); + // load data into board's fifo + for (j = 0; j < block_size;) { + unsigned short data_word = buffer[i++]; + ++j; + if (j < block_size) { + data_word |= buffer[i++] << 8; + ++j; + } + outw(data_word, hp_priv->iobase[3] + BUFFER_PORT_LOW_REG); + } + clear_bit(WRITE_READY_BN, &tms_priv->state); + outb(ENABLE_TI_BUFFER_BIT, hp_priv->iobase[3] + BUFFER_CONTROL_REG); + retval = restart_write_fifo(board, hp_priv); + if (retval < 0) { + dev_err(board->gpib_dev, "failed to restart write stream\n"); + break; + } + retval = wait_event_interruptible(board->wait, + ((event_status = + read_and_clear_event_status(board)) & + TERMINAL_COUNT_EVENT_BIT) || + test_bit(DEV_CLEAR_BN, &tms_priv->state) || + test_bit(TIMO_NUM, &board->status)); + outb(0, hp_priv->iobase[3] + BUFFER_CONTROL_REG); + *bytes_written += block_size - read_transfer_counter(hp_priv); + if (retval) { + retval = -ERESTARTSYS; + break; + } + if (test_bit(TIMO_NUM, &board->status)) { + retval = -ETIMEDOUT; + break; + } + if (test_bit(DEV_CLEAR_BN, &tms_priv->state)) { + retval = -EINTR; + break; + } + } + if (retval) + return retval; + if (send_eoi) { + size_t num_bytes; + + retval = hp_82341_write(board, buffer + fifo_xfer_len, 1, 1, &num_bytes); + *bytes_written += num_bytes; + if (retval < 0) + return retval; + } + return 0; +} + +static int hp_82341_attach(struct gpib_board *board, const struct gpib_board_config *config); + +static void hp_82341_detach(struct gpib_board *board); + +// wrappers for interface functions +static int hp_82341_read(struct gpib_board *board, u8 *buffer, size_t length, int *end, + size_t *bytes_read) +{ + struct hp_82341_priv *priv = board->private_data; + + return tms9914_read(board, &priv->tms9914_priv, buffer, length, end, bytes_read); +} + +static int hp_82341_write(struct gpib_board *board, u8 *buffer, size_t length, int send_eoi, + size_t *bytes_written) +{ + struct hp_82341_priv *priv = board->private_data; + + return tms9914_write(board, &priv->tms9914_priv, buffer, length, send_eoi, bytes_written); +} + +static int hp_82341_command(struct gpib_board *board, u8 *buffer, size_t length, + size_t *bytes_written) +{ + struct hp_82341_priv *priv = board->private_data; + + return tms9914_command(board, &priv->tms9914_priv, buffer, length, bytes_written); +} + +static int hp_82341_take_control(struct gpib_board *board, int synchronous) +{ + struct hp_82341_priv *priv = board->private_data; + + return tms9914_take_control(board, &priv->tms9914_priv, synchronous); +} + +static int hp_82341_go_to_standby(struct gpib_board *board) +{ + struct hp_82341_priv *priv = board->private_data; + + return tms9914_go_to_standby(board, &priv->tms9914_priv); +} + +static int hp_82341_request_system_control(struct gpib_board *board, int request_control) +{ + struct hp_82341_priv *priv = board->private_data; + + if (request_control) + priv->mode_control_bits |= SYSTEM_CONTROLLER_BIT; + else + priv->mode_control_bits &= ~SYSTEM_CONTROLLER_BIT; + outb(priv->mode_control_bits, priv->iobase[0] + MODE_CONTROL_STATUS_REG); + return tms9914_request_system_control(board, &priv->tms9914_priv, request_control); +} + +static void hp_82341_interface_clear(struct gpib_board *board, int assert) +{ + struct hp_82341_priv *priv = board->private_data; + + tms9914_interface_clear(board, &priv->tms9914_priv, assert); +} + +static void hp_82341_remote_enable(struct gpib_board *board, int enable) +{ + struct hp_82341_priv *priv = board->private_data; + + tms9914_remote_enable(board, &priv->tms9914_priv, enable); +} + +static int hp_82341_enable_eos(struct gpib_board *board, u8 eos_byte, int compare_8_bits) +{ + struct hp_82341_priv *priv = board->private_data; + + return tms9914_enable_eos(board, &priv->tms9914_priv, eos_byte, compare_8_bits); +} + +static void hp_82341_disable_eos(struct gpib_board *board) +{ + struct hp_82341_priv *priv = board->private_data; + + tms9914_disable_eos(board, &priv->tms9914_priv); +} + +static unsigned int hp_82341_update_status(struct gpib_board *board, unsigned int clear_mask) +{ + struct hp_82341_priv *priv = board->private_data; + + return tms9914_update_status(board, &priv->tms9914_priv, clear_mask); +} + +static int hp_82341_primary_address(struct gpib_board *board, unsigned int address) +{ + struct hp_82341_priv *priv = board->private_data; + + return tms9914_primary_address(board, &priv->tms9914_priv, address); +} + +static int hp_82341_secondary_address(struct gpib_board *board, unsigned int address, int enable) +{ + struct hp_82341_priv *priv = board->private_data; + + return tms9914_secondary_address(board, &priv->tms9914_priv, address, enable); +} + +static int hp_82341_parallel_poll(struct gpib_board *board, u8 *result) +{ + struct hp_82341_priv *priv = board->private_data; + + return tms9914_parallel_poll(board, &priv->tms9914_priv, result); +} + +static void hp_82341_parallel_poll_configure(struct gpib_board *board, u8 config) +{ + struct hp_82341_priv *priv = board->private_data; + + tms9914_parallel_poll_configure(board, &priv->tms9914_priv, config); +} + +static void hp_82341_parallel_poll_response(struct gpib_board *board, int ist) +{ + struct hp_82341_priv *priv = board->private_data; + + tms9914_parallel_poll_response(board, &priv->tms9914_priv, ist); +} + +static void hp_82341_serial_poll_response(struct gpib_board *board, u8 status) +{ + struct hp_82341_priv *priv = board->private_data; + + tms9914_serial_poll_response(board, &priv->tms9914_priv, status); +} + +static u8 hp_82341_serial_poll_status(struct gpib_board *board) +{ + struct hp_82341_priv *priv = board->private_data; + + return tms9914_serial_poll_status(board, &priv->tms9914_priv); +} + +static int hp_82341_line_status(const struct gpib_board *board) +{ + struct hp_82341_priv *priv = board->private_data; + + return tms9914_line_status(board, &priv->tms9914_priv); +} + +static int hp_82341_t1_delay(struct gpib_board *board, unsigned int nano_sec) +{ + struct hp_82341_priv *priv = board->private_data; + + return tms9914_t1_delay(board, &priv->tms9914_priv, nano_sec); +} + +static void hp_82341_return_to_local(struct gpib_board *board) +{ + struct hp_82341_priv *priv = board->private_data; + + tms9914_return_to_local(board, &priv->tms9914_priv); +} + +static struct gpib_interface hp_82341_unaccel_interface = { + .name = "hp_82341_unaccel", + .attach = hp_82341_attach, + .detach = hp_82341_detach, + .read = hp_82341_read, + .write = hp_82341_write, + .command = hp_82341_command, + .request_system_control = hp_82341_request_system_control, + .take_control = hp_82341_take_control, + .go_to_standby = hp_82341_go_to_standby, + .interface_clear = hp_82341_interface_clear, + .remote_enable = hp_82341_remote_enable, + .enable_eos = hp_82341_enable_eos, + .disable_eos = hp_82341_disable_eos, + .parallel_poll = hp_82341_parallel_poll, + .parallel_poll_configure = hp_82341_parallel_poll_configure, + .parallel_poll_response = hp_82341_parallel_poll_response, + .local_parallel_poll_mode = NULL, // XXX + .line_status = hp_82341_line_status, + .update_status = hp_82341_update_status, + .primary_address = hp_82341_primary_address, + .secondary_address = hp_82341_secondary_address, + .serial_poll_response = hp_82341_serial_poll_response, + .serial_poll_status = hp_82341_serial_poll_status, + .t1_delay = hp_82341_t1_delay, + .return_to_local = hp_82341_return_to_local, +}; + +static struct gpib_interface hp_82341_interface = { + .name = "hp_82341", + .attach = hp_82341_attach, + .detach = hp_82341_detach, + .read = hp_82341_accel_read, + .write = hp_82341_accel_write, + .command = hp_82341_command, + .request_system_control = hp_82341_request_system_control, + .take_control = hp_82341_take_control, + .go_to_standby = hp_82341_go_to_standby, + .interface_clear = hp_82341_interface_clear, + .remote_enable = hp_82341_remote_enable, + .enable_eos = hp_82341_enable_eos, + .disable_eos = hp_82341_disable_eos, + .parallel_poll = hp_82341_parallel_poll, + .parallel_poll_configure = hp_82341_parallel_poll_configure, + .parallel_poll_response = hp_82341_parallel_poll_response, + .local_parallel_poll_mode = NULL, // XXX + .line_status = hp_82341_line_status, + .update_status = hp_82341_update_status, + .primary_address = hp_82341_primary_address, + .secondary_address = hp_82341_secondary_address, + .serial_poll_response = hp_82341_serial_poll_response, + .t1_delay = hp_82341_t1_delay, + .return_to_local = hp_82341_return_to_local, +}; + +static int hp_82341_allocate_private(struct gpib_board *board) +{ + board->private_data = kzalloc(sizeof(struct hp_82341_priv), GFP_KERNEL); + if (!board->private_data) + return -ENOMEM; + return 0; +} + +static void hp_82341_free_private(struct gpib_board *board) +{ + kfree(board->private_data); + board->private_data = NULL; +} + +static u8 hp_82341_read_byte(struct tms9914_priv *priv, unsigned int register_num) +{ + return inb(priv->iobase + register_num); +} + +static void hp_82341_write_byte(struct tms9914_priv *priv, u8 data, unsigned int register_num) +{ + outb(data, priv->iobase + register_num); +} + +static int hp_82341_find_isapnp_board(struct pnp_dev **dev) +{ + *dev = pnp_find_dev(NULL, ISAPNP_VENDOR('H', 'W', 'P'), + ISAPNP_FUNCTION(0x1411), NULL); + if (!*dev || !(*dev)->card) { + pr_err("failed to find isapnp board\n"); + return -ENODEV; + } + if (pnp_device_attach(*dev) < 0) { + pr_err("board already active, skipping\n"); + return -EBUSY; + } + if (pnp_activate_dev(*dev) < 0) { + pnp_device_detach(*dev); + pr_err("failed to activate(), aborting\n"); + return -EAGAIN; + } + if (!pnp_port_valid(*dev, 0) || !pnp_irq_valid(*dev, 0)) { + pnp_device_detach(*dev); + pr_err("invalid port or irq, aborting\n"); + return -ENOMEM; + } + return 0; +} + +static int xilinx_ready(struct hp_82341_priv *hp_priv) +{ + switch (hp_priv->hw_version) { + case HW_VERSION_82341C: + if (inb(hp_priv->iobase[0] + CONFIG_CONTROL_STATUS_REG) & XILINX_READY_BIT) + return 1; + else + return 0; + break; + case HW_VERSION_82341D: + if (isapnp_read_byte(PIO_DATA_REG) & HP_82341D_XILINX_READY_BIT) + return 1; + else + return 0; + default: + pr_err("bug! unknown hw_version\n"); + break; + } + return 0; +} + +static int xilinx_done(struct hp_82341_priv *hp_priv) +{ + switch (hp_priv->hw_version) { + case HW_VERSION_82341C: + if (inb(hp_priv->iobase[0] + CONFIG_CONTROL_STATUS_REG) & DONE_PGL_BIT) + return 1; + else + return 0; + case HW_VERSION_82341D: + if (isapnp_read_byte(PIO_DATA_REG) & HP_82341D_XILINX_DONE_BIT) + return 1; + else + return 0; + default: + pr_err("bug! unknown hw_version\n"); + break; + } + return 0; +} + +static int irq_valid(struct hp_82341_priv *hp_priv, int irq) +{ + switch (hp_priv->hw_version) { + case HW_VERSION_82341C: + switch (irq) { + case 3: + case 5: + case 7: + case 9: + case 10: + case 11: + case 12: + case 15: + return 1; + default: + pr_err("invalid irq=%i for 82341C, irq must be 3, 5, 7, 9, 10, 11, 12, or 15.\n", + irq); + return 0; + } + break; + case HW_VERSION_82341D: + return 1; + default: + pr_err("bug! unknown hw_version\n"); + break; + } + return 0; +} + +static int hp_82341_load_firmware_array(struct hp_82341_priv *hp_priv, + const unsigned char *firmware_data, + unsigned int firmware_length) +{ + int i, j; + static const int timeout = 100; + + for (i = 0; i < firmware_length; ++i) { + for (j = 0; j < timeout; ++j) { + if (need_resched()) + schedule(); + if (xilinx_ready(hp_priv)) + break; + usleep_range(10, 15); + } + if (j == timeout) { + pr_err("timed out waiting for Xilinx ready.\n"); + return -ETIMEDOUT; + } + outb(firmware_data[i], hp_priv->iobase[0] + XILINX_DATA_REG); + } + for (j = 0; j < timeout; ++j) { + if (xilinx_done(hp_priv)) + break; + if (need_resched()) + schedule(); + usleep_range(10, 15); + } + if (j == timeout) { + pr_err("timed out waiting for Xilinx done.\n"); + return -ETIMEDOUT; + } + return 0; +} + +static int hp_82341_load_firmware(struct hp_82341_priv *hp_priv, + const struct gpib_board_config *config) +{ + if (config->init_data_length == 0) { + if (xilinx_done(hp_priv)) + return 0; + pr_err("board needs be initialized with firmware upload.\n" + "\tUse the --init-data option of gpib_config.\n"); + return -EINVAL; + } + switch (hp_priv->hw_version) { + case HW_VERSION_82341C: + if (config->init_data_length != hp_82341c_firmware_length) { + pr_err("bad firmware length=%i for 82341c (expected %i).\n", + config->init_data_length, hp_82341c_firmware_length); + return -EINVAL; + } + break; + case HW_VERSION_82341D: + if (config->init_data_length != hp_82341d_firmware_length) { + pr_err("bad firmware length=%i for 82341d (expected %i).\n", + config->init_data_length, hp_82341d_firmware_length); + return -EINVAL; + } + break; + default: + pr_err("bug! unknown hw_version\n"); + break; + } + return hp_82341_load_firmware_array(hp_priv, config->init_data, config->init_data_length); +} + +static void set_xilinx_not_prog(struct hp_82341_priv *hp_priv, int assert) +{ + switch (hp_priv->hw_version) { + case HW_VERSION_82341C: + if (assert) + hp_priv->config_control_bits |= DONE_PGL_BIT; + else + hp_priv->config_control_bits &= ~DONE_PGL_BIT; + outb(hp_priv->config_control_bits, hp_priv->iobase[0] + CONFIG_CONTROL_STATUS_REG); + break; + case HW_VERSION_82341D: + if (assert) + isapnp_write_byte(PIO_DATA_REG, HP_82341D_NOT_PROG_BIT); + else + isapnp_write_byte(PIO_DATA_REG, 0x0); + break; + default: + break; + } +} + +// clear xilinx firmware +static int clear_xilinx(struct hp_82341_priv *hp_priv) +{ + set_xilinx_not_prog(hp_priv, 1); + if (msleep_interruptible(1)) + return -EINTR; + set_xilinx_not_prog(hp_priv, 0); + if (msleep_interruptible(1)) + return -EINTR; + set_xilinx_not_prog(hp_priv, 1); + if (msleep_interruptible(1)) + return -EINTR; + return 0; +} + +static int hp_82341_attach(struct gpib_board *board, const struct gpib_board_config *config) +{ + struct hp_82341_priv *hp_priv; + struct tms9914_priv *tms_priv; + u32 start_addr; + u32 iobase; + int irq; + int i; + int retval; + + board->status = 0; + if (hp_82341_allocate_private(board)) + return -ENOMEM; + hp_priv = board->private_data; + tms_priv = &hp_priv->tms9914_priv; + tms_priv->read_byte = hp_82341_read_byte; + tms_priv->write_byte = hp_82341_write_byte; + tms_priv->offset = 1; + + if (config->ibbase == 0) { + struct pnp_dev *dev; + int retval = hp_82341_find_isapnp_board(&dev); + + if (retval < 0) + return retval; + hp_priv->pnp_dev = dev; + iobase = pnp_port_start(dev, 0); + irq = pnp_irq(dev, 0); + hp_priv->hw_version = HW_VERSION_82341D; + hp_priv->io_region_offset = 0x8; + } else { + iobase = config->ibbase; + irq = config->ibirq; + hp_priv->hw_version = HW_VERSION_82341C; + hp_priv->io_region_offset = 0x400; + } + for (i = 0; i < hp_82341_num_io_regions; ++i) { + start_addr = iobase + i * hp_priv->io_region_offset; + if (!request_region(start_addr, hp_82341_region_iosize, DRV_NAME)) { + dev_err(board->gpib_dev, "failed to allocate io ports 0x%x-0x%x\n", + start_addr, + start_addr + hp_82341_region_iosize - 1); + return -EIO; + } + hp_priv->iobase[i] = start_addr; + } + tms_priv->iobase = hp_priv->iobase[2]; + if (hp_priv->hw_version == HW_VERSION_82341D) { + retval = isapnp_cfg_begin(hp_priv->pnp_dev->card->number, + hp_priv->pnp_dev->number); + if (retval < 0) { + dev_err(board->gpib_dev, "isapnp_cfg_begin returned error\n"); + return retval; + } + isapnp_write_byte(PIO_DIRECTION_REG, HP_82341D_XILINX_READY_BIT | + HP_82341D_XILINX_DONE_BIT); + } + retval = clear_xilinx(hp_priv); + if (retval < 0) + return retval; + retval = hp_82341_load_firmware(hp_priv, config); + if (hp_priv->hw_version == HW_VERSION_82341D) + isapnp_cfg_end(); + if (retval < 0) + return retval; + if (irq_valid(hp_priv, irq) == 0) + return -EINVAL; + if (request_irq(irq, hp_82341_interrupt, 0, DRV_NAME, board)) { + dev_err(board->gpib_dev, "failed to allocate IRQ %d\n", irq); + return -EIO; + } + hp_priv->irq = irq; + hp_priv->config_control_bits &= ~IRQ_SELECT_MASK; + hp_priv->config_control_bits |= IRQ_SELECT_BITS(irq); + outb(hp_priv->config_control_bits, hp_priv->iobase[0] + CONFIG_CONTROL_STATUS_REG); + hp_priv->mode_control_bits |= ENABLE_IRQ_CONFIG_BIT; + outb(hp_priv->mode_control_bits, hp_priv->iobase[0] + MODE_CONTROL_STATUS_REG); + tms9914_board_reset(tms_priv); + outb(ENABLE_BUFFER_END_EVENT_BIT | ENABLE_TERMINAL_COUNT_EVENT_BIT | + ENABLE_TI_INTERRUPT_EVENT_BIT, hp_priv->iobase[0] + EVENT_ENABLE_REG); + outb(ENABLE_BUFFER_END_INTERRUPT_BIT | ENABLE_TERMINAL_COUNT_INTERRUPT_BIT | + ENABLE_TI_INTERRUPT_BIT, hp_priv->iobase[0] + INTERRUPT_ENABLE_REG); + // write clear event register + outb((TI_INTERRUPT_EVENT_BIT | POINTERS_EQUAL_EVENT_BIT | + BUFFER_END_EVENT_BIT | TERMINAL_COUNT_EVENT_BIT), + hp_priv->iobase[0] + EVENT_STATUS_REG); + + tms9914_online(board, tms_priv); + + return 0; +} + +static void hp_82341_detach(struct gpib_board *board) +{ + struct hp_82341_priv *hp_priv = board->private_data; + struct tms9914_priv *tms_priv; + int i; + + if (hp_priv) { + tms_priv = &hp_priv->tms9914_priv; + if (hp_priv->iobase[0]) { + outb(0, hp_priv->iobase[0] + INTERRUPT_ENABLE_REG); + if (tms_priv->iobase) + tms9914_board_reset(tms_priv); + if (hp_priv->irq) + free_irq(hp_priv->irq, board); + } + for (i = 0; i < hp_82341_num_io_regions; ++i) { + if (hp_priv->iobase[i]) + release_region(hp_priv->iobase[i], hp_82341_region_iosize); + } + if (hp_priv->pnp_dev) + pnp_device_detach(hp_priv->pnp_dev); + } + hp_82341_free_private(board); +} + +#if 0 +/* unused, will be needed when the driver is turned into a pnp_driver */ +static const struct pnp_device_id hp_82341_pnp_table[] = { + {.id = "HWP1411"}, + {.id = ""} +}; +MODULE_DEVICE_TABLE(pnp, hp_82341_pnp_table); +#endif + +static int __init hp_82341_init_module(void) +{ + int ret; + + ret = gpib_register_driver(&hp_82341_unaccel_interface, THIS_MODULE); + if (ret) { + pr_err("gpib_register_driver failed: error = %d\n", ret); + return ret; + } + + ret = gpib_register_driver(&hp_82341_interface, THIS_MODULE); + if (ret) { + pr_err("gpib_register_driver failed: error = %d\n", ret); + gpib_unregister_driver(&hp_82341_unaccel_interface); + return ret; + } + + return 0; +} + +static void __exit hp_82341_exit_module(void) +{ + gpib_unregister_driver(&hp_82341_interface); + gpib_unregister_driver(&hp_82341_unaccel_interface); +} + +module_init(hp_82341_init_module); +module_exit(hp_82341_exit_module); + +/* + * GPIB interrupt service routines + */ +static unsigned short read_and_clear_event_status(struct gpib_board *board) +{ + struct hp_82341_priv *hp_priv = board->private_data; + unsigned long flags; + unsigned short status; + + spin_lock_irqsave(&board->spinlock, flags); + status = hp_priv->event_status_bits; + hp_priv->event_status_bits = 0; + spin_unlock_irqrestore(&board->spinlock, flags); + return status; +} + +static irqreturn_t hp_82341_interrupt(int irq, void *arg) +{ + int status1, status2; + struct gpib_board *board = arg; + struct hp_82341_priv *hp_priv = board->private_data; + struct tms9914_priv *tms_priv = &hp_priv->tms9914_priv; + unsigned long flags; + irqreturn_t retval = IRQ_NONE; + int event_status; + + spin_lock_irqsave(&board->spinlock, flags); + event_status = inb(hp_priv->iobase[0] + EVENT_STATUS_REG); + if (event_status & INTERRUPT_PENDING_EVENT_BIT) + retval = IRQ_HANDLED; + // write-clear status bits + if (event_status & (TI_INTERRUPT_EVENT_BIT | POINTERS_EQUAL_EVENT_BIT | + BUFFER_END_EVENT_BIT | TERMINAL_COUNT_EVENT_BIT)) { + outb(event_status & (TI_INTERRUPT_EVENT_BIT | POINTERS_EQUAL_EVENT_BIT | + BUFFER_END_EVENT_BIT | TERMINAL_COUNT_EVENT_BIT), + hp_priv->iobase[0] + EVENT_STATUS_REG); + hp_priv->event_status_bits |= event_status; + } + if (event_status & TI_INTERRUPT_EVENT_BIT) { + status1 = read_byte(tms_priv, ISR0); + status2 = read_byte(tms_priv, ISR1); + tms9914_interrupt_have_status(board, tms_priv, status1, status2); + } + spin_unlock_irqrestore(&board->spinlock, flags); + return retval; +} + +static int read_transfer_counter(struct hp_82341_priv *hp_priv) +{ + int lo, mid, value; + + lo = inb(hp_priv->iobase[1] + TRANSFER_COUNT_LOW_REG); + mid = inb(hp_priv->iobase[1] + TRANSFER_COUNT_MID_REG); + value = (lo & 0xff) | ((mid << 8) & 0x7f00); + value = ~(value - 1) & 0x7fff; + return value; +} + +static void set_transfer_counter(struct hp_82341_priv *hp_priv, int count) +{ + int complement = -count; + + outb(complement & 0xff, hp_priv->iobase[1] + TRANSFER_COUNT_LOW_REG); + outb((complement >> 8) & 0xff, hp_priv->iobase[1] + TRANSFER_COUNT_MID_REG); + // I don't think the hi count reg is even used, but oh well + outb((complement >> 16) & 0xf, hp_priv->iobase[1] + TRANSFER_COUNT_HIGH_REG); +} + diff --git a/drivers/gpib/hp_82341/hp_82341.h b/drivers/gpib/hp_82341/hp_82341.h new file mode 100644 index 000000000000..859ef2899acb --- /dev/null +++ b/drivers/gpib/hp_82341/hp_82341.h @@ -0,0 +1,165 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/*************************************************************************** + * copyright : (C) 2002, 2005 by Frank Mori Hess * + ***************************************************************************/ + +#include "tms9914.h" +#include "gpibP.h" + +enum hp_82341_hardware_version { + HW_VERSION_UNKNOWN, + HW_VERSION_82341C, + HW_VERSION_82341D, +}; + +// struct which defines private_data for board +struct hp_82341_priv { + struct tms9914_priv tms9914_priv; + unsigned int irq; + unsigned short config_control_bits; + unsigned short mode_control_bits; + unsigned short event_status_bits; + struct pnp_dev *pnp_dev; + unsigned long iobase[4]; + unsigned long io_region_offset; + enum hp_82341_hardware_version hw_version; +}; + +static const int hp_82341_region_iosize = 0x8; +static const int hp_82341_num_io_regions = 4; +static const int hp_82341_fifo_size = 0xffe; +static const int hp_82341c_firmware_length = 5764; +static const int hp_82341d_firmware_length = 5302; + +// hp 82341 register offsets +enum hp_82341_region_0_registers { + CONFIG_CONTROL_STATUS_REG = 0x0, + MODE_CONTROL_STATUS_REG = 0x1, + MONITOR_REG = 0x2, // after initialization + XILINX_DATA_REG = 0x2, // before initialization, write only + INTERRUPT_ENABLE_REG = 0x3, + EVENT_STATUS_REG = 0x4, + EVENT_ENABLE_REG = 0x5, + STREAM_STATUS_REG = 0x7, +}; + +enum hp_82341_region_1_registers { + ID0_REG = 0x2, + ID1_REG = 0x3, + TRANSFER_COUNT_LOW_REG = 0x4, + TRANSFER_COUNT_MID_REG = 0x5, + TRANSFER_COUNT_HIGH_REG = 0x6, +}; + +enum hp_82341_region_3_registers { + BUFFER_PORT_LOW_REG = 0x0, + BUFFER_PORT_HIGH_REG = 0x1, + ID2_REG = 0x2, + ID3_REG = 0x3, + BUFFER_FLUSH_REG = 0x4, + BUFFER_CONTROL_REG = 0x7 +}; + +enum config_control_status_bits { + IRQ_SELECT_MASK = 0x7, + DMA_CONFIG_MASK = 0x18, + ENABLE_DMA_CONFIG_BIT = 0x20, + XILINX_READY_BIT = 0x40, // read only + DONE_PGL_BIT = 0x80 +}; + +static inline unsigned int IRQ_SELECT_BITS(int irq) +{ + switch (irq) { + case 3: + return 0x3; + case 5: + return 0x2; + case 7: + return 0x1; + case 9: + return 0x0; + case 10: + return 0x7; + case 11: + return 0x6; + case 12: + return 0x5; + case 15: + return 0x4; + default: + return 0x0; + } +}; + +enum mode_control_status_bits { + SLOT8_BIT = 0x1, // read only + ACTIVE_CONTROLLER_BIT = 0x2, // read only + ENABLE_DMA_BIT = 0x4, + SYSTEM_CONTROLLER_BIT = 0x8, + MONITOR_BIT = 0x10, + ENABLE_IRQ_CONFIG_BIT = 0x20, + ENABLE_TI_STREAM_BIT = 0x40 +}; + +enum monitor_bits { + MONITOR_INTERRUPT_PENDING_BIT = 0x1, // read only + MONITOR_CLEAR_HOLDOFF_BIT = 0x2, // write only + MONITOR_PPOLL_BIT = 0x4, // write clear + MONITOR_SRQ_BIT = 0x8, // write clear + MONITOR_IFC_BIT = 0x10, // write clear + MONITOR_REN_BIT = 0x20, // write clear + MONITOR_END_BIT = 0x40, // write clear + MONITOR_DAV_BIT = 0x80 // write clear +}; + +enum interrupt_enable_bits { + ENABLE_TI_INTERRUPT_BIT = 0x1, + ENABLE_POINTERS_EQUAL_INTERRUPT_BIT = 0x4, + ENABLE_BUFFER_END_INTERRUPT_BIT = 0x10, + ENABLE_TERMINAL_COUNT_INTERRUPT_BIT = 0x20, + ENABLE_DMA_TERMINAL_COUNT_INTERRUPT_BIT = 0x80, +}; + +enum event_status_bits { + TI_INTERRUPT_EVENT_BIT = 0x1, // write clear + INTERRUPT_PENDING_EVENT_BIT = 0x2, // read only + POINTERS_EQUAL_EVENT_BIT = 0x4, // write clear + BUFFER_END_EVENT_BIT = 0x10, // write clear + TERMINAL_COUNT_EVENT_BIT = 0x20, // write clear + DMA_TERMINAL_COUNT_EVENT_BIT = 0x80, // write clear +}; + +enum event_enable_bits { + ENABLE_TI_INTERRUPT_EVENT_BIT = 0x1, // write clear + ENABLE_POINTERS_EQUAL_EVENT_BIT = 0x4, // write clear + ENABLE_BUFFER_END_EVENT_BIT = 0x10, // write clear + ENABLE_TERMINAL_COUNT_EVENT_BIT = 0x20, // write clear + ENABLE_DMA_TERMINAL_COUNT_EVENT_BIT = 0x80, // write clear +}; + +enum stream_status_bits { + HALTED_STATUS_BIT = 0x1, // read + RESTART_STREAM_BIT = 0x1 // write +}; + +enum buffer_control_bits { + DIRECTION_GPIB_TO_HOST_BIT = 0x20, // transfer direction (set for gpib to host) + ENABLE_TI_BUFFER_BIT = 0x40, // enable fifo + FAST_WR_EN_BIT = 0x80, // 350 ns t1 delay? +}; + +// registers accessible through isapnp chip on 82341d +enum hp_82341d_pnp_registers { + PIO_DATA_REG = 0x20, // read/write pio data lines + PIO_DIRECTION_REG = 0x21, // set pio data line directions (set for input) +}; + +enum hp_82341d_pnp_pio_bits { + HP_82341D_XILINX_READY_BIT = 0x1, + HP_82341D_XILINX_DONE_BIT = 0x2, + // use register layout compatible with C and older versions instead of 32 contiguous ioports + HP_82341D_LEGACY_MODE_BIT = 0x4, + HP_82341D_NOT_PROG_BIT = 0x8, // clear to reinitialize xilinx +}; diff --git a/drivers/gpib/include/amcc5920.h b/drivers/gpib/include/amcc5920.h new file mode 100644 index 000000000000..7a88bd282feb --- /dev/null +++ b/drivers/gpib/include/amcc5920.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/*************************************************************************** + * Header for amcc5920 pci chip + * + * copyright : (C) 2002 by Frank Mori Hess + ***************************************************************************/ + +// plx pci chip registers and bits +enum amcc_registers { + AMCC_INTCS_REG = 0x38, + AMCC_PASS_THRU_REG = 0x60, +}; + +enum amcc_incsr_bits { + AMCC_ADDON_INTR_ENABLE_BIT = 0x2000, + AMCC_ADDON_INTR_ACTIVE_BIT = 0x400000, + AMCC_INTR_ACTIVE_BIT = 0x800000, +}; + +static const int bits_per_region = 8; + +static inline uint32_t amcc_wait_state_bits(unsigned int region, unsigned int num_wait_states) +{ + return (num_wait_states & 0x7) << (--region * bits_per_region); +}; + +enum amcc_prefetch_bits { + PREFETCH_DISABLED = 0x0, + PREFETCH_SMALL = 0x8, + PREFETCH_MEDIUM = 0x10, + PREFETCH_LARGE = 0x18, +}; + +static inline uint32_t amcc_prefetch_bits(unsigned int region, enum amcc_prefetch_bits prefetch) +{ + return prefetch << (--region * bits_per_region); +}; + +static inline uint32_t amcc_PTADR_mode_bit(unsigned int region) +{ + return 0x80 << (--region * bits_per_region); +}; + +static inline uint32_t amcc_disable_write_fifo_bit(unsigned int region) +{ + return 0x20 << (--region * bits_per_region); +}; + diff --git a/drivers/gpib/include/amccs5933.h b/drivers/gpib/include/amccs5933.h new file mode 100644 index 000000000000..d7f63c795096 --- /dev/null +++ b/drivers/gpib/include/amccs5933.h @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/*************************************************************************** + * Registers and bits for amccs5933 pci chip + * copyright : (C) 2002 by Frank Mori Hess + ***************************************************************************/ + +// register offsets +enum { + MBEF_REG = 0x34, // mailbux empty/full + INTCSR_REG = 0x38, // interrupt control and status + BMCSR_REG = 0x3c, // bus master control and status +}; + +// incoming mailbox 0-3 register offsets +extern inline int INCOMING_MAILBOX_REG(unsigned int mailbox) +{ + return (0x10 + 4 * mailbox); +}; + +// bit definitions + +// INTCSR bits +enum { + OUTBOX_EMPTY_INTR_BIT = 0x10, // enable outbox empty interrupt + INBOX_FULL_INTR_BIT = 0x1000, // enable inbox full interrupt + INBOX_INTR_CS_BIT = 0x20000, // read, or write clear inbox full interrupt + INTR_ASSERTED_BIT = 0x800000, // read only, interrupt asserted +}; + +// select byte 0 to 3 of incoming mailbox +extern inline int INBOX_BYTE_BITS(unsigned int byte) +{ + return (byte & 0x3) << 8; +}; + +// select incoming mailbox 0 to 3 +extern inline int INBOX_SELECT_BITS(unsigned int mailbox) +{ + return (mailbox & 0x3) << 10; +}; + +// select byte 0 to 3 of outgoing mailbox +extern inline int OUTBOX_BYTE_BITS(unsigned int byte) +{ + return (byte & 0x3); +}; + +// select outgoing mailbox 0 to 3 +extern inline int OUTBOX_SELECT_BITS(unsigned int mailbox) +{ + return (mailbox & 0x3) << 2; +}; + +// BMCSR bits +enum { + MBOX_FLAGS_RESET_BIT = 0x08000000, // resets mailbox empty/full flags +}; + diff --git a/drivers/gpib/include/gpibP.h b/drivers/gpib/include/gpibP.h new file mode 100644 index 000000000000..e3938ada3e0d --- /dev/null +++ b/drivers/gpib/include/gpibP.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/*************************************************************************** + * copyright : (C) 2002,2003 by Frank Mori Hess + ***************************************************************************/ + +#ifndef _GPIB_P_H +#define _GPIB_P_H + +#include + +#include "gpib_types.h" +#include "gpib_proto.h" +#include "gpib_cmd.h" +#include +#include + +#include +#include +#include + +int gpib_register_driver(struct gpib_interface *interface, struct module *mod); +void gpib_unregister_driver(struct gpib_interface *interface); +struct pci_dev *gpib_pci_get_device(const struct gpib_board_config *config, unsigned int vendor_id, + unsigned int device_id, struct pci_dev *from); +struct pci_dev *gpib_pci_get_subsys(const struct gpib_board_config *config, unsigned int vendor_id, + unsigned int device_id, unsigned int ss_vendor, + unsigned int ss_device, struct pci_dev *from); +unsigned int num_gpib_events(const struct gpib_event_queue *queue); +int push_gpib_event(struct gpib_board *board, short event_type); +int pop_gpib_event(struct gpib_board *board, struct gpib_event_queue *queue, short *event_type); +int gpib_request_pseudo_irq(struct gpib_board *board, irqreturn_t (*handler)(int, void *)); +void gpib_free_pseudo_irq(struct gpib_board *board); +int gpib_match_device_path(struct device *dev, const char *device_path_in); + +extern struct gpib_board board_array[GPIB_MAX_NUM_BOARDS]; + +extern struct list_head registered_drivers; + +#endif // _GPIB_P_H + diff --git a/drivers/gpib/include/gpib_cmd.h b/drivers/gpib/include/gpib_cmd.h new file mode 100644 index 000000000000..9e96a3bfa22d --- /dev/null +++ b/drivers/gpib/include/gpib_cmd.h @@ -0,0 +1,112 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef _GPIB_CMD_H +#define _GPIB_CMD_H + +#include + +/* Command byte definitions tests and functions */ + +/* mask of bits that actually matter in a command byte */ +enum { + gpib_command_mask = 0x7f, +}; + +/* Possible GPIB command messages */ + +enum cmd_byte { + GTL = 0x1, /* go to local */ + SDC = 0x4, /* selected device clear */ + PP_CONFIG = 0x5, + GET = 0x8, /* group execute trigger */ + TCT = 0x9, /* take control */ + LLO = 0x11, /* local lockout */ + DCL = 0x14, /* device clear */ + PPU = 0x15, /* parallel poll unconfigure */ + SPE = 0x18, /* serial poll enable */ + SPD = 0x19, /* serial poll disable */ + CFE = 0x1f, /* configure enable */ + LAD = 0x20, /* value to be 'ored' in to obtain listen address */ + UNL = 0x3F, /* unlisten */ + TAD = 0x40, /* value to be 'ored' in to obtain talk address */ + UNT = 0x5F, /* untalk */ + SAD = 0x60, /* my secondary address (base) */ + PPE = 0x60, /* parallel poll enable (base) */ + PPD = 0x70 /* parallel poll disable */ +}; + +/* confine address to range 0 to 30. */ +static inline unsigned int gpib_address_restrict(u32 addr) +{ + addr &= 0x1f; + if (addr == 0x1f) + addr = 0; + return addr; +} + +static inline u8 MLA(u32 addr) +{ + return gpib_address_restrict(addr) | LAD; +} + +static inline u8 MTA(u32 addr) +{ + return gpib_address_restrict(addr) | TAD; +} + +static inline u8 MSA(u32 addr) +{ + return (addr & 0x1f) | SAD; +} + +static inline s32 gpib_address_equal(u32 pad1, s32 sad1, u32 pad2, s32 sad2) +{ + if (pad1 == pad2) { + if (sad1 == sad2) + return 1; + if (sad1 < 0 && sad2 < 0) + return 1; + } + + return 0; +} + +static inline s32 is_PPE(u8 command) +{ + return (command & 0x70) == 0x60; +} + +static inline s32 is_PPD(u8 command) +{ + return (command & 0x70) == 0x70; +} + +static inline s32 in_addressed_command_group(u8 command) +{ + return (command & 0x70) == 0x0; +} + +static inline s32 in_universal_command_group(u8 command) +{ + return (command & 0x70) == 0x10; +} + +static inline s32 in_listen_address_group(u8 command) +{ + return (command & 0x60) == 0x20; +} + +static inline s32 in_talk_address_group(u8 command) +{ + return (command & 0x60) == 0x40; +} + +static inline s32 in_primary_command_group(u8 command) +{ + return in_addressed_command_group(command) || + in_universal_command_group(command) || + in_listen_address_group(command) || + in_talk_address_group(command); +} + +#endif /* _GPIB_CMD_H */ diff --git a/drivers/gpib/include/gpib_pci_ids.h b/drivers/gpib/include/gpib_pci_ids.h new file mode 100644 index 000000000000..52dcab07a7d1 --- /dev/null +++ b/drivers/gpib/include/gpib_pci_ids.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __GPIB_PCI_IDS_H +#define __GPIB_PCI_IDS_H + +#ifndef PCI_VENDOR_ID_AMCC +#define PCI_VENDOR_ID_AMCC 0x10e8 +#endif + +#ifndef PCI_VENDOR_ID_CBOARDS +#define PCI_VENDOR_ID_CBOARDS 0x1307 +#endif + +#ifndef PCI_VENDOR_ID_QUANCOM +#define PCI_VENDOR_ID_QUANCOM 0x8008 +#endif + +#ifndef PCI_DEVICE_ID_QUANCOM_GPIB +#define PCI_DEVICE_ID_QUANCOM_GPIB 0x3302 +#endif + +#endif // __GPIB_PCI_IDS_H + diff --git a/drivers/gpib/include/gpib_proto.h b/drivers/gpib/include/gpib_proto.h new file mode 100644 index 000000000000..42e736e3b7cd --- /dev/null +++ b/drivers/gpib/include/gpib_proto.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef GPIB_PROTO_INCLUDED +#define GPIB_PROTO_INCLUDED + +#include + +int ibopen(struct inode *inode, struct file *filep); +int ibclose(struct inode *inode, struct file *file); +long ibioctl(struct file *filep, unsigned int cmd, unsigned long arg); +void os_start_timer(struct gpib_board *board, unsigned int usec_timeout); +void os_remove_timer(struct gpib_board *board); +void init_gpib_board(struct gpib_board *board); +static inline unsigned long usec_to_jiffies(unsigned int usec) +{ + unsigned long usec_per_jiffy = 1000000 / HZ; + + return 1 + (usec + usec_per_jiffy - 1) / usec_per_jiffy; +}; + +int serial_poll_all(struct gpib_board *board, unsigned int usec_timeout); +void init_gpib_descriptor(struct gpib_descriptor *desc); +int dvrsp(struct gpib_board *board, unsigned int pad, int sad, + unsigned int usec_timeout, u8 *result); +int ibcac(struct gpib_board *board, int sync, int fallback_to_async); +int ibcmd(struct gpib_board *board, u8 *buf, size_t length, size_t *bytes_written); +int ibgts(struct gpib_board *board); +int ibonline(struct gpib_board *board); +int iboffline(struct gpib_board *board); +int iblines(const struct gpib_board *board, short *lines); +int ibrd(struct gpib_board *board, u8 *buf, size_t length, int *end_flag, size_t *bytes_read); +int ibrpp(struct gpib_board *board, u8 *buf); +int ibrsv2(struct gpib_board *board, u8 status_byte, int new_reason_for_service); +int ibrsc(struct gpib_board *board, int request_control); +int ibsic(struct gpib_board *board, unsigned int usec_duration); +int ibsre(struct gpib_board *board, int enable); +int ibpad(struct gpib_board *board, unsigned int addr); +int ibsad(struct gpib_board *board, int addr); +int ibeos(struct gpib_board *board, int eos, int eosflags); +int ibwait(struct gpib_board *board, int wait_mask, int clear_mask, int set_mask, + int *status, unsigned long usec_timeout, struct gpib_descriptor *desc); +int ibwrt(struct gpib_board *board, u8 *buf, size_t cnt, int send_eoi, size_t *bytes_written); +int ibstatus(struct gpib_board *board); +int general_ibstatus(struct gpib_board *board, const struct gpib_status_queue *device, + int clear_mask, int set_mask, struct gpib_descriptor *desc); +int io_timed_out(struct gpib_board *board); +int ibppc(struct gpib_board *board, u8 configuration); + +#endif /* GPIB_PROTO_INCLUDED */ diff --git a/drivers/gpib/include/gpib_state_machines.h b/drivers/gpib/include/gpib_state_machines.h new file mode 100644 index 000000000000..7488c00f191e --- /dev/null +++ b/drivers/gpib/include/gpib_state_machines.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/*************************************************************************** + * copyright : (C) 2006 by Frank Mori Hess + ***************************************************************************/ + +#ifndef _GPIB_STATE_MACHINES_H +#define _GPIB_STATE_MACHINES_H + +enum talker_function_state { + talker_idle, + talker_addressed, + talker_active, + serial_poll_active +}; + +enum listener_function_state { + listener_idle, + listener_addressed, + listener_active +}; + +#endif // _GPIB_STATE_MACHINES_H diff --git a/drivers/gpib/include/gpib_types.h b/drivers/gpib/include/gpib_types.h new file mode 100644 index 000000000000..5a0978ae27e7 --- /dev/null +++ b/drivers/gpib/include/gpib_types.h @@ -0,0 +1,381 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/*************************************************************************** + * copyright : (C) 2002 by Frank Mori Hess + ***************************************************************************/ + +#ifndef _GPIB_TYPES_H +#define _GPIB_TYPES_H + +#ifdef __KERNEL__ +#include +#include +#include +#include +#include +#include +#include +#include + +struct gpib_board; + +/* config parameters that are only used by driver attach functions */ +struct gpib_board_config { + /* firmware blob */ + void *init_data; + int init_data_length; + /* IO base address to use for non-pnp cards (set by core, driver should make local copy) */ + u32 ibbase; + void __iomem *mmibbase; + /* IRQ to use for non-pnp cards (set by core, driver should make local copy) */ + unsigned int ibirq; + /* dma channel to use for non-pnp cards (set by core, driver should make local copy) */ + unsigned int ibdma; + /* + * pci bus of card, useful for distinguishing multiple identical pci cards + * (negative means don't care) + */ + int pci_bus; + /* + * pci slot of card, useful for distinguishing multiple identical pci cards + * (negative means don't care) + */ + int pci_slot; + /* sysfs device path of hardware to attach */ + char *device_path; + /* serial number of hardware to attach */ + char *serial_number; +}; + +/* + * struct gpib_interface defines the interface + * between the board-specific details dealt with in the drivers + * and generic interface provided by gpib-common. + * This really should be in a different header file. + */ +struct gpib_interface { + /* name of board */ + char *name; + /* attach() initializes board and allocates resources */ + int (*attach)(struct gpib_board *board, const struct gpib_board_config *config); + /* detach() shuts down board and frees resources */ + void (*detach)(struct gpib_board *board); + /* + * read() should read at most 'length' bytes from the bus into + * 'buffer'. It should return when it fills the buffer or + * encounters an END (EOI and or EOS if appropriate). It should set 'end' + * to be nonzero if the read was terminated by an END, otherwise 'end' + * should be zero. + * Ultimately, this will be changed into or replaced by an asynchronous + * read. Zero return value for success, negative + * return indicates error. + * nbytes returns number of bytes read + */ + int (*read)(struct gpib_board *board, u8 *buffer, size_t length, int *end, + size_t *bytes_read); + /* + * write() should write 'length' bytes from buffer to the bus. + * If the boolean value send_eoi is nonzero, then EOI should + * be sent along with the last byte. Returns number of bytes + * written or negative value on error. + */ + int (*write)(struct gpib_board *board, u8 *buffer, size_t length, int send_eoi, + size_t *bytes_written); + /* + * command() writes the command bytes in 'buffer' to the bus + * Returns zero on success or negative value on error. + */ + int (*command)(struct gpib_board *board, u8 *buffer, size_t length, + size_t *bytes_written); + /* + * Take control (assert ATN). If 'asyncronous' is nonzero, take + * control asyncronously (assert ATN immediately without waiting + * for other processes to complete first). Should not return + * until board becomes controller in charge. Returns zero no success, + * nonzero on error. + */ + int (*take_control)(struct gpib_board *board, int asyncronous); + /* + * De-assert ATN. Returns zero on success, nonzer on error. + */ + int (*go_to_standby)(struct gpib_board *board); + /* request/release control of the IFC and REN lines (system controller) */ + int (*request_system_control)(struct gpib_board *board, int request_control); + /* + * Asserts or de-asserts 'interface clear' (IFC) depending on + * boolean value of 'assert' + */ + void (*interface_clear)(struct gpib_board *board, int assert); + /* + * Sends remote enable command if 'enable' is nonzero, disables remote mode + * if 'enable' is zero + */ + void (*remote_enable)(struct gpib_board *board, int enable); + /* + * enable END for reads, when byte 'eos' is received. If + * 'compare_8_bits' is nonzero, then all 8 bits are compared + * with the eos bytes. Otherwise only the 7 least significant + * bits are compared. + */ + int (*enable_eos)(struct gpib_board *board, u8 eos, int compare_8_bits); + /* disable END on eos byte (END on EOI only)*/ + void (*disable_eos)(struct gpib_board *board); + /* configure parallel poll */ + void (*parallel_poll_configure)(struct gpib_board *board, u8 configuration); + /* conduct parallel poll */ + int (*parallel_poll)(struct gpib_board *board, u8 *result); + /* set/clear ist (individual status bit) */ + void (*parallel_poll_response)(struct gpib_board *board, int ist); + /* select local parallel poll configuration mode PP2 versus remote PP1 */ + void (*local_parallel_poll_mode)(struct gpib_board *board, int local); + /* + * Returns current status of the bus lines. Should be set to + * NULL if your board does not have the ability to query the + * state of the bus lines. + */ + int (*line_status)(const struct gpib_board *board); + /* + * updates and returns the board's current status. + * The meaning of the bits are specified in gpib_user.h + * in the IBSTA section. The driver does not need to + * worry about setting the CMPL, END, TIMO, or ERR bits. + */ + unsigned int (*update_status)(struct gpib_board *board, unsigned int clear_mask); + /* + * Sets primary address 0-30 for gpib interface card. + */ + int (*primary_address)(struct gpib_board *board, unsigned int address); + /* + * Sets and enables, or disables secondary address 0-30 + * for gpib interface card. + */ + int (*secondary_address)(struct gpib_board *board, unsigned int address, + int enable); + /* + * Sets the byte the board should send in response to a serial poll. + * This function should also start or stop requests for service via + * IEEE 488.2 reqt/reqf, based on MSS (bit 6 of the status_byte). + * If the more flexible serial_poll_response2 is implemented by the + * driver, then this method should be left NULL since it will not + * be used. This method can generate spurious service requests + * which are allowed by IEEE 488.2, but not ideal. + * + * This method should implement the serial poll response method described + * by IEEE 488.2 section 11.3.3.4.3 "Allowed Coupled Control of + * STB, reqt, and reqf". + */ + void (*serial_poll_response)(struct gpib_board *board, u8 status_byte); + /* + * Sets the byte the board should send in response to a serial poll. + * This function should also request service via IEEE 488.2 reqt/reqf + * based on MSS (bit 6 of the status_byte) and new_reason_for_service. + * reqt should be set true if new_reason_for_service is true, + * and reqf should be set true if MSS is false. This function + * will never be called with MSS false and new_reason_for_service + * true simultaneously, so don't worry about that case. + * + * This method implements the serial poll response method described + * by IEEE 488.2 section 11.3.3.4.1 "Preferred Implementation". + * + * If this method is left NULL by the driver, then the user library + * function ibrsv2 will not work. + */ + void (*serial_poll_response2)(struct gpib_board *board, u8 status_byte, + int new_reason_for_service); + /* + * returns the byte the board will send in response to a serial poll. + */ + u8 (*serial_poll_status)(struct gpib_board *board); + /* adjust T1 delay */ + int (*t1_delay)(struct gpib_board *board, unsigned int nano_sec); + /* go to local mode */ + void (*return_to_local)(struct gpib_board *board); + /* board does not support 7 bit eos comparisons */ + unsigned no_7_bit_eos : 1; + /* skip check for listeners before trying to send command bytes */ + unsigned skip_check_for_command_acceptors : 1; +}; + +struct gpib_event_queue { + struct list_head event_head; + spinlock_t lock; // for access to event list + unsigned int num_events; + unsigned dropped_event : 1; +}; + +static inline void init_event_queue(struct gpib_event_queue *queue) +{ + INIT_LIST_HEAD(&queue->event_head); + queue->num_events = 0; + queue->dropped_event = 0; + spin_lock_init(&queue->lock); +} + +/* struct for supporting polling operation when irq is not available */ +struct gpib_pseudo_irq { + struct timer_list timer; + irqreturn_t (*handler)(int irq, void *arg); + struct gpib_board *board; + atomic_t active; +}; + +static inline void init_gpib_pseudo_irq(struct gpib_pseudo_irq *pseudo_irq) +{ + pseudo_irq->handler = NULL; + timer_setup(&pseudo_irq->timer, NULL, 0); + atomic_set(&pseudo_irq->active, 0); +} + +/* list so we can make a linked list of drivers */ +struct gpib_interface_list { + struct list_head list; + struct gpib_interface *interface; + struct module *module; +}; + +/* + * One struct gpib_board is allocated for each physical board in the computer. + * It provides storage for variables local to each board, and interface + * functions for performing operations on the board + */ +struct gpib_board { + /* functions used by this board */ + struct gpib_interface *interface; + /* + * Pointer to module whose use count we should increment when + * interface is in use + */ + struct module *provider_module; + /* buffer used to store read/write data for this board */ + u8 *buffer; + /* length of buffer */ + unsigned int buffer_length; + /* + * Used to hold the board's current status (see update_status() above) + */ + unsigned long status; + /* + * Driver should only sleep on this wait queue. It is special in that the + * core will wake this queue and set the TIMO bit in 'status' when the + * watchdog timer times out. + */ + wait_queue_head_t wait; + /* + * Lock that only allows one process to access this board at a time. + * Has to be first in any locking order, since it can be locked over + * multiple ioctls. + */ + struct mutex user_mutex; + /* + * Mutex which compensates for removal of "big kernel lock" from kernel. + * Should not be held for extended waits. + */ + struct mutex big_gpib_mutex; + /* pid of last process to lock the board mutex */ + pid_t locking_pid; + /* lock for setting locking pid */ + spinlock_t locking_pid_spinlock; + /* Spin lock for dealing with races with the interrupt handler */ + spinlock_t spinlock; + /* Watchdog timer to enable timeouts */ + struct timer_list timer; + /* device of attached driver if any */ + struct device *dev; + /* gpib_common device gpibN */ + struct device *gpib_dev; + /* + * 'private_data' can be used as seen fit by the driver to + * store additional variables for this board + */ + void *private_data; + /* Number of open file descriptors using this board */ + unsigned int use_count; + /* list of open devices connected to this board */ + struct list_head device_list; + /* primary address */ + unsigned int pad; + /* secondary address */ + int sad; + /* timeout for io operations, in microseconds */ + unsigned int usec_timeout; + /* board's parallel poll configuration byte */ + u8 parallel_poll_configuration; + /* t1 delay we are using */ + unsigned int t1_nano_sec; + /* Count that keeps track of whether board is up and running or not */ + unsigned int online; + /* number of processes trying to autopoll */ + int autospollers; + /* autospoll kernel thread */ + struct task_struct *autospoll_task; + /* queue for recording received trigger/clear/ifc events */ + struct gpib_event_queue event_queue; + /* minor number for this board's device file */ + int minor; + /* struct to deal with polling mode*/ + struct gpib_pseudo_irq pseudo_irq; + /* error dong autopoll */ + atomic_t stuck_srq; + struct gpib_board_config config; + /* Flag that indicates whether board is system controller of the bus */ + unsigned master : 1; + /* individual status bit */ + unsigned ist : 1; + /* + * one means local parallel poll mode ieee 488.1 PP2 (or no parallel poll PP0), + * zero means remote parallel poll configuration mode ieee 488.1 PP1 + */ + unsigned local_ppoll_mode : 1; +}; + +/* element of event queue */ +struct gpib_event { + struct list_head list; + short event_type; +}; + +/* + * Each board has a list of gpib_status_queue to keep track of all open devices + * on the bus, so we know what address to poll when we get a service request + */ +struct gpib_status_queue { + /* list_head so we can make a linked list of devices */ + struct list_head list; + unsigned int pad; /* primary gpib address */ + int sad; /* secondary gpib address (negative means disabled) */ + /* stores serial poll bytes for this device */ + struct list_head status_bytes; + unsigned int num_status_bytes; + /* number of times this address is opened */ + unsigned int reference_count; + /* flags loss of status byte error due to limit on size of queue */ + unsigned dropped_byte : 1; +}; + +struct gpib_status_byte { + struct list_head list; + u8 poll_byte; +}; + +void init_gpib_status_queue(struct gpib_status_queue *device); + +/* Used to store device-descriptor-specific information */ +struct gpib_descriptor { + unsigned int pad; /* primary gpib address */ + int sad; /* secondary gpib address (negative means disabled) */ + atomic_t io_in_progress; + unsigned is_board : 1; + unsigned autopoll_enabled : 1; +}; + +struct gpib_file_private { + atomic_t holding_mutex; + struct gpib_descriptor *descriptors[GPIB_MAX_NUM_DESCRIPTORS]; + /* locked while descriptors are being allocated/deallocated */ + struct mutex descriptors_mutex; + unsigned got_module : 1; +}; + +#endif /* __KERNEL__ */ + +#endif /* _GPIB_TYPES_H */ diff --git a/drivers/gpib/include/nec7210.h b/drivers/gpib/include/nec7210.h new file mode 100644 index 000000000000..9835aa5ef4ff --- /dev/null +++ b/drivers/gpib/include/nec7210.h @@ -0,0 +1,141 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/*************************************************************************** + * copyright : (C) 2002 by Frank Mori Hess + ***************************************************************************/ + +#ifndef _NEC7210_H +#define _NEC7210_H + +#include "gpib_state_machines.h" +#include +#include +#include +#include + +#include "gpib_types.h" +#include "nec7210_registers.h" + +/* struct used to provide variables local to a nec7210 chip */ +struct nec7210_priv { +#ifdef CONFIG_HAS_IOPORT + u32 iobase; +#endif + void __iomem *mmiobase; + unsigned int offset; // offset between successive nec7210 io addresses + unsigned int dma_channel; + u8 *dma_buffer; + unsigned int dma_buffer_length; // length of dma buffer + dma_addr_t dma_buffer_addr; // bus address of board->buffer for use with dma + // software copy of bits written to registers + u8 reg_bits[8]; + u8 auxa_bits; // bits written to auxiliary register A + u8 auxb_bits; // bits written to auxiliary register B + // used to keep track of board's state, bit definitions given below + unsigned long state; + // lock for chips that extend the nec7210 registers by paging in alternate regs + spinlock_t register_page_lock; + // wrappers for outb, inb, readb, or writeb + u8 (*read_byte)(struct nec7210_priv *priv, unsigned int register_number); + void (*write_byte)(struct nec7210_priv *priv, u8 byte, unsigned int register_number); + enum nec7210_chipset type; + enum talker_function_state talker_state; + enum listener_function_state listener_state; + void *private; + unsigned srq_pending : 1; +}; + +static inline void init_nec7210_private(struct nec7210_priv *priv) +{ + memset(priv, 0, sizeof(struct nec7210_priv)); + spin_lock_init(&priv->register_page_lock); +} + +// slightly shorter way to access read_byte and write_byte +static inline u8 read_byte(struct nec7210_priv *priv, unsigned int register_number) +{ + return priv->read_byte(priv, register_number); +} + +static inline void write_byte(struct nec7210_priv *priv, u8 byte, unsigned int register_number) +{ + priv->write_byte(priv, byte, register_number); +} + +// struct nec7210_priv.state bit numbers +enum { + PIO_IN_PROGRESS_BN, // pio transfer in progress + DMA_READ_IN_PROGRESS_BN, // dma read transfer in progress + DMA_WRITE_IN_PROGRESS_BN, // dma write transfer in progress + READ_READY_BN, // board has data byte available to read + WRITE_READY_BN, // board is ready to send a data byte + COMMAND_READY_BN, // board is ready to send a command byte + RECEIVED_END_BN, // received END + BUS_ERROR_BN, // output error has occurred + RFD_HOLDOFF_BN, // rfd holdoff in effect + DEV_CLEAR_BN, // device clear received + ADR_CHANGE_BN, // address state change occurred +}; + +// interface functions +int nec7210_read(struct gpib_board *board, struct nec7210_priv *priv, u8 *buffer, + size_t length, int *end, size_t *bytes_read); +int nec7210_write(struct gpib_board *board, struct nec7210_priv *priv, u8 *buffer, + size_t length, int send_eoi, size_t *bytes_written); +int nec7210_command(struct gpib_board *board, struct nec7210_priv *priv, u8 *buffer, + size_t length, size_t *bytes_written); +int nec7210_take_control(struct gpib_board *board, struct nec7210_priv *priv, int syncronous); +int nec7210_go_to_standby(struct gpib_board *board, struct nec7210_priv *priv); +int nec7210_request_system_control(struct gpib_board *board, + struct nec7210_priv *priv, int request_control); +void nec7210_interface_clear(struct gpib_board *board, struct nec7210_priv *priv, int assert); +void nec7210_remote_enable(struct gpib_board *board, struct nec7210_priv *priv, int enable); +int nec7210_enable_eos(struct gpib_board *board, struct nec7210_priv *priv, u8 eos_bytes, + int compare_8_bits); +void nec7210_disable_eos(struct gpib_board *board, struct nec7210_priv *priv); +unsigned int nec7210_update_status(struct gpib_board *board, struct nec7210_priv *priv, + unsigned int clear_mask); +unsigned int nec7210_update_status_nolock(struct gpib_board *board, struct nec7210_priv *priv); +int nec7210_primary_address(const struct gpib_board *board, + struct nec7210_priv *priv, unsigned int address); +int nec7210_secondary_address(const struct gpib_board *board, struct nec7210_priv *priv, + unsigned int address, int enable); +int nec7210_parallel_poll(struct gpib_board *board, struct nec7210_priv *priv, u8 *result); +void nec7210_serial_poll_response(struct gpib_board *board, + struct nec7210_priv *priv, u8 status); +void nec7210_parallel_poll_configure(struct gpib_board *board, + struct nec7210_priv *priv, unsigned int configuration); +void nec7210_parallel_poll_response(struct gpib_board *board, + struct nec7210_priv *priv, int ist); +u8 nec7210_serial_poll_status(struct gpib_board *board, struct nec7210_priv *priv); +int nec7210_t1_delay(struct gpib_board *board, + struct nec7210_priv *priv, unsigned int nano_sec); +void nec7210_return_to_local(const struct gpib_board *board, struct nec7210_priv *priv); + +// utility functions +void nec7210_board_reset(struct nec7210_priv *priv, const struct gpib_board *board); +void nec7210_board_online(struct nec7210_priv *priv, const struct gpib_board *board); +unsigned int nec7210_set_reg_bits(struct nec7210_priv *priv, unsigned int reg, + unsigned int mask, unsigned int bits); +void nec7210_set_handshake_mode(struct gpib_board *board, struct nec7210_priv *priv, int mode); +void nec7210_release_rfd_holdoff(struct gpib_board *board, struct nec7210_priv *priv); +u8 nec7210_read_data_in(struct gpib_board *board, struct nec7210_priv *priv, int *end); + +// wrappers for io functions +u8 nec7210_ioport_read_byte(struct nec7210_priv *priv, unsigned int register_num); +void nec7210_ioport_write_byte(struct nec7210_priv *priv, u8 data, unsigned int register_num); +u8 nec7210_iomem_read_byte(struct nec7210_priv *priv, unsigned int register_num); +void nec7210_iomem_write_byte(struct nec7210_priv *priv, u8 data, unsigned int register_num); +u8 nec7210_locking_ioport_read_byte(struct nec7210_priv *priv, unsigned int register_num); +void nec7210_locking_ioport_write_byte(struct nec7210_priv *priv, u8 data, + unsigned int register_num); +u8 nec7210_locking_iomem_read_byte(struct nec7210_priv *priv, unsigned int register_num); +void nec7210_locking_iomem_write_byte(struct nec7210_priv *priv, u8 data, + unsigned int register_num); + +// interrupt service routine +irqreturn_t nec7210_interrupt(struct gpib_board *board, struct nec7210_priv *priv); +irqreturn_t nec7210_interrupt_have_status(struct gpib_board *board, + struct nec7210_priv *priv, int status1, int status2); + +#endif //_NEC7210_H diff --git a/drivers/gpib/include/nec7210_registers.h b/drivers/gpib/include/nec7210_registers.h new file mode 100644 index 000000000000..067983d7a07f --- /dev/null +++ b/drivers/gpib/include/nec7210_registers.h @@ -0,0 +1,218 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/*************************************************************************** + * copyright : (C) 2002 by Frank Mori Hess + ***************************************************************************/ + +#ifndef _NEC7210_REGISTERS_H +#define _NEC7210_REGISTERS_H + +enum nec7210_chipset { + NEC7210, // The original + TNT4882, // NI + NAT4882, // NI + CB7210, // measurement computing + IOT7210, // iotech + IGPIB7210, // Ines + TNT5004, // NI (minor differences to TNT4882) +}; + +/* + * nec7210 register numbers (might need to be multiplied by + * a board-dependent offset to get actually io address offset) + */ +// write registers +enum nec7210_write_regs { + CDOR, // command/data out + IMR1, // interrupt mask 1 + IMR2, // interrupt mask 2 + SPMR, // serial poll mode + ADMR, // address mode + AUXMR, // auxiliary mode + ADR, // address + EOSR, // end-of-string + + // nec7210 has 8 registers + nec7210_num_registers = 8, +}; + +// read registers +enum nec7210_read_regs { + DIR, // data in + ISR1, // interrupt status 1 + ISR2, // interrupt status 2 + SPSR, // serial poll status + ADSR, // address status + CPTR, // command pass though + ADR0, // address 1 + ADR1, // address 2 +}; + +// bit definitions common to nec-7210 compatible registers + +// ISR1: interrupt status register 1 +enum isr1_bits { + HR_DI = (1 << 0), + HR_DO = (1 << 1), + HR_ERR = (1 << 2), + HR_DEC = (1 << 3), + HR_END = (1 << 4), + HR_DET = (1 << 5), + HR_APT = (1 << 6), + HR_CPT = (1 << 7), +}; + +// IMR1: interrupt mask register 1 +enum imr1_bits { + HR_DIIE = (1 << 0), + HR_DOIE = (1 << 1), + HR_ERRIE = (1 << 2), + HR_DECIE = (1 << 3), + HR_ENDIE = (1 << 4), + HR_DETIE = (1 << 5), + HR_APTIE = (1 << 6), + HR_CPTIE = (1 << 7), +}; + +// ISR2, interrupt status register 2 +enum isr2_bits { + HR_ADSC = (1 << 0), + HR_REMC = (1 << 1), + HR_LOKC = (1 << 2), + HR_CO = (1 << 3), + HR_REM = (1 << 4), + HR_LOK = (1 << 5), + HR_SRQI = (1 << 6), + HR_INT = (1 << 7), +}; + +// IMR2, interrupt mask register 2 +enum imr2_bits { + // all the bits in this register that enable interrupts + IMR2_ENABLE_INTR_MASK = 0x4f, + HR_ACIE = (1 << 0), + HR_REMIE = (1 << 1), + HR_LOKIE = (1 << 2), + HR_COIE = (1 << 3), + HR_DMAI = (1 << 4), + HR_DMAO = (1 << 5), + HR_SRQIE = (1 << 6), +}; + +// SPSR, serial poll status register +enum spsr_bits { + HR_PEND = (1 << 6), +}; + +// SPMR, serial poll mode register +enum spmr_bits { + HR_RSV = (1 << 6), +}; + +// ADSR, address status register +enum adsr_bits { + HR_MJMN = (1 << 0), + HR_TA = (1 << 1), + HR_LA = (1 << 2), + HR_TPAS = (1 << 3), + HR_LPAS = (1 << 4), + HR_SPMS = (1 << 5), + HR_NATN = (1 << 6), + HR_CIC = (1 << 7), +}; + +// ADMR, address mode register +enum admr_bits { + HR_ADM0 = (1 << 0), + HR_ADM1 = (1 << 1), + HR_TRM0 = (1 << 4), + HR_TRM1 = (1 << 5), + HR_TRM_EOIOE_TRIG = 0, + HR_TRM_CIC_TRIG = HR_TRM0, + HR_TRM_CIC_EOIOE = HR_TRM1, + HR_TRM_CIC_PE = HR_TRM0 | HR_TRM1, + HR_LON = (1 << 6), + HR_TON = (1 << 7), +}; + +// ADR, bits used in address0, address1 and address0/1 registers +enum adr_bits { + ADDRESS_MASK = 0x1f, /* mask to specify lower 5 bits */ + HR_DL = (1 << 5), + HR_DT = (1 << 6), + HR_ARS = (1 << 7), +}; + +// ADR1, address1 register +enum adr1_bits { + HR_EOI = (1 << 7), +}; + +// AUXMR, auxiliary mode register +enum auxmr_bits { + ICR = 0x20, + PPR = 0x60, + AUXRA = 0x80, + AUXRB = 0xa0, + AUXRE = 0xc0, +}; + +// auxra, auxiliary register A +enum auxra_bits { + HR_HANDSHAKE_MASK = 0x3, + HR_HLDA = 0x1, + HR_HLDE = 0x2, + HR_LCM = 0x3, /* auxra listen continuous */ + HR_REOS = 0x4, + HR_XEOS = 0x8, + HR_BIN = 0x10, +}; + +// auxrb, auxiliary register B +enum auxrb_bits { + HR_CPTE = (1 << 0), + HR_SPEOI = (1 << 1), + HR_TRI = (1 << 2), + HR_INV = (1 << 3), + HR_ISS = (1 << 4), +}; + +enum auxre_bits { + HR_DAC_HLD_DCAS = 0x1, /* perform DAC holdoff on receiving clear */ + HR_DAC_HLD_DTAS = 0x2, /* perform DAC holdoff on receiving trigger */ +}; + +// parallel poll register +enum ppr_bits { + HR_PPS = (1 << 3), + HR_PPU = (1 << 4), +}; + +/* 7210 Auxiliary Commands */ +enum aux_cmds { + AUX_PON = 0x0, /* Immediate Execute pon */ + AUX_CPPF = 0x1, /* Clear Parallel Poll Flag */ + AUX_CR = 0x2, /* Chip Reset */ + AUX_FH = 0x3, /* Finish Handshake */ + AUX_TRIG = 0x4, /* Trigger */ + AUX_RTL = 0x5, /* Return to local */ + AUX_SEOI = 0x6, /* Send EOI */ + AUX_NVAL = 0x7, /* Non-Valid Secondary Command or Address */ + AUX_SPPF = 0x9, /* Set Parallel Poll Flag */ + AUX_VAL = 0xf, /* Valid Secondary Command or Address */ + AUX_GTS = 0x10, /* Go To Standby */ + AUX_TCA = 0x11, /* Take Control Asynchronously */ + AUX_TCS = 0x12, /* Take Control Synchronously */ + AUX_LTN = 0x13, /* Listen */ + AUX_DSC = 0x14, /* Disable System Control */ + AUX_CIFC = 0x16, /* Clear IFC */ + AUX_CREN = 0x17, /* Clear REN */ + AUX_TCSE = 0x1a, /* Take Control Synchronously on End */ + AUX_LTNC = 0x1b, /* Listen in Continuous Mode */ + AUX_LUN = 0x1c, /* Local Unlisten */ + AUX_EPP = 0x1d, /* Execute Parallel Poll */ + AUX_SIFC = 0x1e, /* Set IFC */ + AUX_SREN = 0x1f, /* Set REN */ +}; + +#endif //_NEC7210_REGISTERS_H diff --git a/drivers/gpib/include/plx9050.h b/drivers/gpib/include/plx9050.h new file mode 100644 index 000000000000..c911b285a0ca --- /dev/null +++ b/drivers/gpib/include/plx9050.h @@ -0,0 +1,72 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/*************************************************************************** + * Header for plx9050 pci chip + * copyright : (C) 2002 by Frank Mori Hess + ***************************************************************************/ + +#ifndef _PLX9050_GPIB_H +#define _PLX9050_GPIB_H + +// plx pci chip registers and bits +enum { + PLX9050_INTCSR_REG = 0x4c, + PLX9050_CNTRL_REG = 0x50 +}; + +enum plx9050_intcsr_bits { + PLX9050_LINTR1_EN_BIT = 0x1, + PLX9050_LINTR1_POLARITY_BIT = 0x2, + PLX9050_LINTR1_STATUS_BIT = 0x4, + PLX9050_LINTR2_EN_BIT = 0x8, + PLX9050_LINTR2_POLARITY_BIT = 0x10, + PLX9050_LINTR2_STATUS_BIT = 0x20, + PLX9050_PCI_INTR_EN_BIT = 0x40, + PLX9050_SOFT_INTR_BIT = 0x80, + PLX9050_LINTR1_SELECT_ENABLE_BIT = 0x100, // 9052 extension + PLX9050_LINTR2_SELECT_ENABLE_BIT = 0x200, // 9052 extension + PLX9050_LINTR1_EDGE_CLEAR_BIT = 0x400, // 9052 extension + PLX9050_LINTR2_EDGE_CLEAR_BIT = 0x800, // 9052 extension +}; + +enum plx9050_cntrl_bits { + PLX9050_WAITO_NOT_USER0_SELECT_BIT = 0x1, + PLX9050_USER0_OUTPUT_BIT = 0x2, + PLX9050_USER0_DATA_BIT = 0x4, + PLX9050_LLOCK_NOT_USER1_SELECT_BIT = 0x8, + PLX9050_USER1_OUTPUT_BIT = 0x10, + PLX9050_USER1_DATA_BIT = 0x20, + PLX9050_CS2_NOT_USER2_SELECT_BIT = 0x40, + PLX9050_USER2_OUTPUT_BIT = 0x80, + PLX9050_USER2_DATA_BIT = 0x100, + PLX9050_CS3_NOT_USER3_SELECT_BIT = 0x200, + PLX9050_USER3_OUTPUT_BIT = 0x400, + PLX9050_USER3_DATA_BIT = 0x800, + PLX9050_PCIBAR_ENABLE_MASK = 0x3000, + PLX9050_PCIBAR_MEMORY_AND_IO_ENABLE_BITS = 0x0, + PLX9050_PCIBAR_MEMORY_NO_IO_ENABLE_BITS = 0x1000, + PLX9050_PCIBAR_IO_NO_MEMORY_ENABLE_BITS = 0x2000, + PLX9050_PCIBAR_MEMORY_AND_IO_TOO_ENABLE_BITS = 0x3000, + PLX9050_PCI_READ_MODE_BIT = 0x4000, + PLX9050_PCI_READ_WITH_WRITE_FLUSH_MODE_BIT = 0x8000, + PLX9050_PCI_READ_NO_FLUSH_MODE_BIT = 0x10000, + PLX9050_PCI_READ_NO_WRITE_MODE_BIT = 0x20000, + PLX9050_PCI_WRITE_MODE_BIT = 0x40000, + PLX9050_PCI_RETRY_DELAY_MASK = 0x780000, + PLX9050_DIRECT_SLAVE_LOCK_ENABLE_BIT = 0x800000, + PLX9050_EEPROM_CLOCK_BIT = 0x1000000, + PLX9050_EEPROM_CHIP_SELECT_BIT = 0x2000000, + PLX9050_WRITE_TO_EEPROM_BIT = 0x4000000, + PLX9050_READ_EEPROM_DATA_BIT = 0x8000000, + PLX9050_EEPROM_VALID_BIT = 0x10000000, + PLX9050_RELOAD_CONFIG_REGISTERS_BIT = 0x20000000, + PLX9050_PCI_SOFTWARE_RESET_BIT = 0x40000000, + PLX9050_MASK_REVISION_BIT = 0x80000000 +}; + +static inline unsigned int PLX9050_PCI_RETRY_DELAY_BITS(unsigned int clocks) +{ + return ((clocks / 8) << 19) & PLX9050_PCI_RETRY_DELAY_MASK; +} + +#endif // _PLX9050_GPIB_H diff --git a/drivers/gpib/include/quancom_pci.h b/drivers/gpib/include/quancom_pci.h new file mode 100644 index 000000000000..cdaf0d056be9 --- /dev/null +++ b/drivers/gpib/include/quancom_pci.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/*************************************************************************** + * Quancom pci stuff + * copyright (C) 2005 by Frank Mori Hess + ***************************************************************************/ + +#ifndef _QUANCOM_PCI_H +#define _QUANCOM_PCI_H + +/* quancom registers */ +enum quancom_regs { + QUANCOM_IRQ_CONTROL_STATUS_REG = 0xfc, +}; + +enum quancom_irq_control_status_bits { + QUANCOM_IRQ_ASSERTED_BIT = 0x1, /* readable */ + /* (any write to the register clears the interrupt)*/ + QUANCOM_IRQ_ENABLE_BIT = 0x4, /* writeable */ +}; + +#endif // _QUANCOM_PCI_H diff --git a/drivers/gpib/include/tms9914.h b/drivers/gpib/include/tms9914.h new file mode 100644 index 000000000000..e66b75e0fda8 --- /dev/null +++ b/drivers/gpib/include/tms9914.h @@ -0,0 +1,280 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/*************************************************************************** + * copyright : (C) 2002 by Frank Mori Hess + ***************************************************************************/ + +#ifndef _TMS9914_H +#define _TMS9914_H + +#include +#include +#include "gpib_state_machines.h" +#include "gpib_types.h" + +enum tms9914_holdoff_mode { + TMS9914_HOLDOFF_NONE, + TMS9914_HOLDOFF_EOI, + TMS9914_HOLDOFF_ALL, +}; + +/* struct used to provide variables local to a tms9914 chip */ +struct tms9914_priv { +#ifdef CONFIG_HAS_IOPORT + u32 iobase; +#endif + void __iomem *mmiobase; + unsigned int offset; // offset between successive tms9914 io addresses + unsigned int dma_channel; + // software copy of bits written to interrupt mask registers + u8 imr0_bits, imr1_bits; + // bits written to address mode register + u8 admr_bits; + u8 auxa_bits; // bits written to auxiliary register A + // used to keep track of board's state, bit definitions given below + unsigned long state; + u8 eos; // eos character + short eos_flags; + u8 spoll_status; + enum tms9914_holdoff_mode holdoff_mode; + unsigned int ppoll_line; + enum talker_function_state talker_state; + enum listener_function_state listener_state; + unsigned ppoll_sense : 1; + unsigned ppoll_enable : 1; + unsigned ppoll_configure_state : 1; + unsigned primary_listen_addressed : 1; + unsigned primary_talk_addressed : 1; + unsigned holdoff_on_end : 1; + unsigned holdoff_on_all : 1; + unsigned holdoff_active : 1; + // wrappers for outb, inb, readb, or writeb + u8 (*read_byte)(struct tms9914_priv *priv, unsigned int register_number); + void (*write_byte)(struct tms9914_priv *priv, u8 byte, unsigned int + register_number); +}; + +// slightly shorter way to access read_byte and write_byte +static inline u8 read_byte(struct tms9914_priv *priv, unsigned int register_number) +{ + return priv->read_byte(priv, register_number); +} + +static inline void write_byte(struct tms9914_priv *priv, u8 byte, unsigned int register_number) +{ + priv->write_byte(priv, byte, register_number); +} + +// struct tms9914_priv.state bit numbers +enum { + PIO_IN_PROGRESS_BN, // pio transfer in progress + DMA_READ_IN_PROGRESS_BN, // dma read transfer in progress + DMA_WRITE_IN_PROGRESS_BN, // dma write transfer in progress + READ_READY_BN, // board has data byte available to read + WRITE_READY_BN, // board is ready to send a data byte + COMMAND_READY_BN, // board is ready to send a command byte + RECEIVED_END_BN, // received END + BUS_ERROR_BN, // bus error + DEV_CLEAR_BN, // device clear received +}; + +// interface functions +int tms9914_read(struct gpib_board *board, struct tms9914_priv *priv, u8 *buffer, + size_t length, int *end, size_t *bytes_read); +int tms9914_write(struct gpib_board *board, struct tms9914_priv *priv, u8 *buffer, + size_t length, int send_eoi, size_t *bytes_written); +int tms9914_command(struct gpib_board *board, struct tms9914_priv *priv, u8 *buffer, + size_t length, size_t *bytes_written); +int tms9914_take_control(struct gpib_board *board, struct tms9914_priv *priv, int syncronous); +/* + * alternate version of tms9914_take_control which works around buggy tcs + * implementation. + */ +int tms9914_take_control_workaround(struct gpib_board *board, struct tms9914_priv *priv, + int syncronous); +int tms9914_go_to_standby(struct gpib_board *board, struct tms9914_priv *priv); +int tms9914_request_system_control(struct gpib_board *board, struct tms9914_priv *priv, + int request_control); +void tms9914_interface_clear(struct gpib_board *board, struct tms9914_priv *priv, int assert); +void tms9914_remote_enable(struct gpib_board *board, struct tms9914_priv *priv, int enable); +int tms9914_enable_eos(struct gpib_board *board, struct tms9914_priv *priv, u8 eos_bytes, + int compare_8_bits); +void tms9914_disable_eos(struct gpib_board *board, struct tms9914_priv *priv); +unsigned int tms9914_update_status(struct gpib_board *board, struct tms9914_priv *priv, + unsigned int clear_mask); +int tms9914_primary_address(struct gpib_board *board, + struct tms9914_priv *priv, unsigned int address); +int tms9914_secondary_address(struct gpib_board *board, struct tms9914_priv *priv, + unsigned int address, int enable); +int tms9914_parallel_poll(struct gpib_board *board, struct tms9914_priv *priv, u8 *result); +void tms9914_parallel_poll_configure(struct gpib_board *board, + struct tms9914_priv *priv, u8 config); +void tms9914_parallel_poll_response(struct gpib_board *board, + struct tms9914_priv *priv, int ist); +void tms9914_serial_poll_response(struct gpib_board *board, + struct tms9914_priv *priv, u8 status); +u8 tms9914_serial_poll_status(struct gpib_board *board, struct tms9914_priv *priv); +int tms9914_line_status(const struct gpib_board *board, struct tms9914_priv *priv); +unsigned int tms9914_t1_delay(struct gpib_board *board, struct tms9914_priv *priv, + unsigned int nano_sec); +void tms9914_return_to_local(const struct gpib_board *board, struct tms9914_priv *priv); + +// utility functions +void tms9914_board_reset(struct tms9914_priv *priv); +void tms9914_online(struct gpib_board *board, struct tms9914_priv *priv); +void tms9914_release_holdoff(struct tms9914_priv *priv); +void tms9914_set_holdoff_mode(struct tms9914_priv *priv, enum tms9914_holdoff_mode mode); + +// wrappers for io functions +u8 tms9914_ioport_read_byte(struct tms9914_priv *priv, unsigned int register_num); +void tms9914_ioport_write_byte(struct tms9914_priv *priv, u8 data, unsigned int register_num); +u8 tms9914_iomem_read_byte(struct tms9914_priv *priv, unsigned int register_num); +void tms9914_iomem_write_byte(struct tms9914_priv *priv, u8 data, unsigned int register_num); + +// interrupt service routine +irqreturn_t tms9914_interrupt(struct gpib_board *board, struct tms9914_priv *priv); +irqreturn_t tms9914_interrupt_have_status(struct gpib_board *board, struct tms9914_priv *priv, + int status1, int status2); + +// tms9914 has 8 registers +enum { + ms9914_num_registers = 8, +}; + +/* + * tms9914 register numbers (might need to be multiplied by + * a board-dependent offset to get actually io address offset) + */ +// write registers +enum { + IMR0 = 0, /* interrupt mask 0 */ + IMR1 = 1, /* interrupt mask 1 */ + AUXCR = 3, /* auxiliary command */ + ADR = 4, /* address register */ + SPMR = 5, /* serial poll mode register */ + PPR = 6, /* parallel poll */ + CDOR = 7, /* data out register */ +}; + +// read registers +enum { + ISR0 = 0, /* interrupt status 0 */ + ISR1 = 1, /* interrupt status 1 */ + ADSR = 2, /* address status */ + BSR = 3, /* bus status */ + CPTR = 6, /* command pass thru */ + DIR = 7, /* data in register */ +}; + +// bit definitions common to tms9914 compatible registers + +/* ISR0 - Register bits */ +enum isr0_bits { + HR_MAC = (1 << 0), /* My Address Change */ + HR_RLC = (1 << 1), /* Remote/Local change */ + HR_SPAS = (1 << 2), /* Serial Poll active State */ + HR_END = (1 << 3), /* END (EOI or EOS) */ + HR_BO = (1 << 4), /* Byte Out */ + HR_BI = (1 << 5), /* Byte In */ +}; + +/* IMR0 - Register bits */ +enum imr0_bits { + HR_MACIE = (1 << 0), /* */ + HR_RLCIE = (1 << 1), /* */ + HR_SPASIE = (1 << 2), /* */ + HR_ENDIE = (1 << 3), /* */ + HR_BOIE = (1 << 4), /* */ + HR_BIIE = (1 << 5), /* */ +}; + +/* ISR1 - Register bits */ +enum isr1_bits { + HR_IFC = (1 << 0), /* IFC asserted */ + HR_SRQ = (1 << 1), /* SRQ asserted */ + HR_MA = (1 << 2), /* My Address */ + HR_DCAS = (1 << 3), /* Device Clear active State */ + HR_APT = (1 << 4), /* Address pass Through */ + HR_UNC = (1 << 5), /* Unrecognized Command */ + HR_ERR = (1 << 6), /* Data Transmission Error */ + HR_GET = (1 << 7), /* Group execute Trigger */ +}; + +/* IMR1 - Register bits */ +enum imr1_bits { + HR_IFCIE = (1 << 0), /* */ + HR_SRQIE = (1 << 1), /* */ + HR_MAIE = (1 << 2), /* */ + HR_DCASIE = (1 << 3), /* */ + HR_APTIE = (1 << 4), /* */ + HR_UNCIE = (1 << 5), /* */ + HR_ERRIE = (1 << 6), /* */ + HR_GETIE = (1 << 7), /* */ +}; + +/* ADSR - Register bits */ +enum adsr_bits { + HR_ULPA = (1 << 0), /* Store last address LSB */ + HR_TA = (1 << 1), /* Talker Adressed */ + HR_LA = (1 << 2), /* Listener adressed */ + HR_TPAS = (1 << 3), /* talker primary address state */ + HR_LPAS = (1 << 4), /* listener " */ + HR_ATN = (1 << 5), /* ATN active */ + HR_LLO = (1 << 6), /* LLO active */ + HR_REM = (1 << 7), /* REM active */ +}; + +/* ADR - Register bits */ +enum adr_bits { + ADDRESS_MASK = 0x1f, /* mask to specify lower 5 bits for ADR */ + HR_DAT = (1 << 5), /* disable talker */ + HR_DAL = (1 << 6), /* disable listener */ + HR_EDPA = (1 << 7), /* enable dual primary addressing */ +}; + +enum bus_status_bits { + BSR_REN_BIT = 0x1, + BSR_IFC_BIT = 0x2, + BSR_SRQ_BIT = 0x4, + BSR_EOI_BIT = 0x8, + BSR_NRFD_BIT = 0x10, + BSR_NDAC_BIT = 0x20, + BSR_DAV_BIT = 0x40, + BSR_ATN_BIT = 0x80, +}; + +/*---------------------------------------------------------*/ +/* TMS 9914 Auxiliary Commands */ +/*---------------------------------------------------------*/ + +enum aux_cmd_bits { + AUX_CS = 0x80, /* set bit instead of clearing it, used with commands marked 'd' below */ + AUX_CHIP_RESET = 0x0, /* d Chip reset */ + AUX_INVAL = 0x1, /* release dac holdoff, invalid command byte */ + AUX_VAL = (AUX_INVAL | AUX_CS), /* release dac holdoff, valid command byte */ + AUX_RHDF = 0x2, /* X Release RFD holdoff */ + AUX_HLDA = 0x3, /* d holdoff on all data */ + AUX_HLDE = 0x4, /* d holdoff on EOI only */ + AUX_NBAF = 0x5, /* X Set new byte available false */ + AUX_FGET = 0x6, /* d force GET */ + AUX_RTL = 0x7, /* d return to local */ + AUX_SEOI = 0x8, /* X send EOI with next byte */ + AUX_LON = 0x9, /* d Listen only */ + AUX_TON = 0xa, /* d Talk only */ + AUX_GTS = 0xb, /* X goto standby */ + AUX_TCA = 0xc, /* X take control asynchronously */ + AUX_TCS = 0xd, /* X take " synchronously */ + AUX_RPP = 0xe, /* d Request parallel poll */ + AUX_SIC = 0xf, /* d send interface clear */ + AUX_SRE = 0x10, /* d send remote enable */ + AUX_RQC = 0x11, /* X request control */ + AUX_RLC = 0x12, /* X release control */ + AUX_DAI = 0x13, /* d disable all interrupts */ + AUX_PTS = 0x14, /* X pass through next secondary */ + AUX_STDL = 0x15, /* d short T1 delay */ + AUX_SHDW = 0x16, /* d shadow handshake */ + AUX_VSTDL = 0x17, /* d very short T1 delay (smj9914 extension) */ + AUX_RSV2 = 0x18, /* d request service bit 2 (smj9914 extension) */ +}; + +#endif //_TMS9914_H diff --git a/drivers/gpib/include/tnt4882_registers.h b/drivers/gpib/include/tnt4882_registers.h new file mode 100644 index 000000000000..d54c4cc61168 --- /dev/null +++ b/drivers/gpib/include/tnt4882_registers.h @@ -0,0 +1,192 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/*************************************************************************** + * copyright : (C) 2002, 2004 by Frank Mori Hess + ***************************************************************************/ + +#ifndef _TNT4882_REGISTERS_H +#define _TNT4882_REGISTERS_H + +// tnt4882 register offsets +enum { + ACCWR = 0x5, + // offset of auxiliary command register in 9914 mode + AUXCR = 0x6, + INTRT = 0x7, + // register number for auxiliary command register when swap bit is set (9914 mode) + SWAPPED_AUXCR = 0xa, + HSSEL = 0xd, // handshake select register + CNT2 = 0x9, + CNT3 = 0xb, + CFG = 0x10, + SASR = 0x1b, + IMR0 = 0x1d, + IMR3 = 0x12, + CNT0 = 0x14, + CNT1 = 0x16, + KEYREG = 0x17, // key control register (7210 mode only) + CSR = KEYREG, + FIFOB = 0x18, + FIFOA = 0x19, + CCR = 0x1a, // carry cycle register + CMDR = 0x1c, // command register + TIMER = 0x1e, // timer register + + STS1 = 0x10, // T488 Status Register 1 + STS2 = 0x1c, // T488 Status Register 2 + ISR0 = IMR0, + ISR3 = 0x1a, // T488 Interrupt Status Register 3 + BCR = 0x1f, // bus control/status register + BSR = BCR, +}; + +enum { + tnt_pagein_offset = 0x11, +}; + +/*============================================================*/ + +/* TURBO-488 registers bit definitions */ + +enum bus_control_status_bits { + BCSR_REN_BIT = 0x1, + BCSR_IFC_BIT = 0x2, + BCSR_SRQ_BIT = 0x4, + BCSR_EOI_BIT = 0x8, + BCSR_NRFD_BIT = 0x10, + BCSR_NDAC_BIT = 0x20, + BCSR_DAV_BIT = 0x40, + BCSR_ATN_BIT = 0x80, +}; + +/* CFG -- Configuration Register (write only) */ +enum cfg_bits { + TNT_COMMAND = 0x80, /* bytes are command bytes instead of data bytes + * (tnt4882 one-chip and newer only?) + */ + TNT_TLCHE = (1 << 6), /* halt transfer on imr0, imr1, or imr2 interrupt */ + TNT_IN = (1 << 5), /* transfer is GPIB read */ + TNT_A_B = (1 << 4), /* order to use fifos 1=fifo A first(big endian), + * 0=fifo b first(little endian) + */ + TNT_CCEN = (1 << 3), /* enable carry cycle */ + TNT_TMOE = (1 << 2), /* enable CPU bus time limit */ + TNT_TIM_BYTN = (1 << 1), /* tmot reg is: 1=125ns clocks, 0=num bytes */ + TNT_B_16BIT = (1 << 0), /* 1=FIFO is 16-bit register, 0=8-bit */ +}; + +/* CMDR -- Command Register */ +enum cmdr_bits { + CLRSC = 0x2, /* clear the system controller bit */ + SETSC = 0x3, /* set the system controller bit */ + GO = 0x4, /* start fifos */ + STOP = 0x8, /* stop fifos */ + RESET_FIFO = 0x10, /* reset the FIFOs */ + SOFT_RESET = 0x22, /* issue a software reset */ + HARD_RESET = 0x40 /* 500x only? */ +}; + +/* HSSEL -- handshake select register (write only) */ +enum hssel_bits { + TNT_ONE_CHIP_BIT = 0x1, + NODMA = 0x10, + TNT_GO2SIDS_BIT = 0x20, +}; + +/* IMR0 -- Interrupt Mode Register 0 */ +enum imr0_bits { + TNT_SYNCIE_BIT = 0x1, /* handshake sync */ + TNT_TOIE_BIT = 0x2, /* timeout */ + TNT_ATNIE_BIT = 0x4, /* ATN interrupt */ + TNT_IFCIE_BIT = 0x8, /* interface clear interrupt */ + TNT_BTO_BIT = 0x10, /* byte timeout */ + TNT_NLEN_BIT = 0x20, /* treat new line as EOS char */ + TNT_STBOIE_BIT = 0x40, /* status byte out */ + TNT_IMR0_ALWAYS_BITS = 0x80, /* always set this bit on write */ +}; + +/* ISR0 -- Interrupt Status Register 0 */ +enum isr0_bits { + TNT_SYNC_BIT = 0x1, /* handshake sync */ + TNT_TO_BIT = 0x2, /* timeout */ + TNT_ATNI_BIT = 0x4, /* ATN interrupt */ + TNT_IFCI_BIT = 0x8, /* interface clear interrupt */ + TNT_EOS_BIT = 0x10, /* end of string */ + TNT_NL_BIT = 0x20, /* new line receive */ + TNT_STBO_BIT = 0x40, /* status byte out */ + TNT_NBA_BIT = 0x80, /* new byte available */ +}; + +/* ISR3 -- Interrupt Status Register 3 (read only) */ +enum isr3_bits { + HR_DONE = (1 << 0), /* transfer done */ + HR_TLCI = (1 << 1), /* isr0, isr1, or isr2 interrupt asserted */ + HR_NEF = (1 << 2), /* NOT empty fifo */ + HR_NFF = (1 << 3), /* NOT full fifo */ + HR_STOP = (1 << 4), /* fifo empty or STOP command issued */ + HR_SRQI_CIC = (1 << 5), /* SRQ asserted and we are CIC (500x only?)*/ + HR_INTR = (1 << 7), /* isr3 interrupt active */ +}; + +enum keyreg_bits { + MSTD = 0x20, /* enable 350ns T1 delay */ +}; + +/* STS1 -- Status Register 1 (read only) */ +enum sts1_bits { + S_DONE = 0x80, /* DMA done */ + S_SC = 0x40, /* is system controller */ + S_IN = 0x20, /* DMA in (to memory) */ + S_DRQ = 0x10, /* DRQ line (for diagnostics) */ + S_STOP = 0x08, /* DMA stopped */ + S_NDAV = 0x04, /* inverse of DAV */ + S_HALT = 0x02, /* status of transfer machine */ + S_GSYNC = 0x01, /* indicates if GPIB is in sync w I/O */ +}; + +/* STS2 -- Status Register 2 */ +enum sts2_bits { + AFFN = (1 << 3), /* "A full FIFO NOT" (0=FIFO full) */ + AEFN = (1 << 2), /* "A empty FIFO NOT" (0=FIFO empty) */ + BFFN = (1 << 1), /* "B full FIFO NOT" (0=FIFO full) */ + BEFN = (1 << 0), /* "B empty FIFO NOT" (0=FIFO empty) */ +}; + +// Auxiliary commands +enum tnt4882_aux_cmds { + AUX_9914 = 0x15, // switch to 9914 mode + AUX_REQT = 0x18, + AUX_REQF = 0x19, + AUX_PAGEIN = 0x50, // page in alternate registers + AUX_HLDI = 0x51, // rfd holdoff immediately + AUX_CLEAR_END = 0x55, + AUX_7210 = 0x99, // switch to 7210 mode +}; + +enum tnt4882_aux_regs { + AUXRG = 0x40, + AUXRI = 0xe0, +}; + +enum auxg_bits { + /* no talking when no listeners bit (prevents bus errors when data written at wrong time) */ + NTNL_BIT = 0x8, + RPP2_BIT = 0x4, /* set/clear local rpp message */ + CHES_BIT = 0x1, /*clear holdoff on end select bit*/ +}; + +enum auxi_bits { + SISB = 0x1, // static interrupt bits (don't clear isr1, isr2 on read) + PP2 = 0x4, // ignore remote parallel poll configuration + USTD = 0x8, // ultra short (1100 nanosec) T1 delay +}; + +enum sasr_bits { + ACRDY_BIT = 0x4, /* acceptor ready state */ + ADHS_BIT = 0x8, /* acceptor data holdoff state */ + ANHS2_BIT = 0x10, /* acceptor not ready holdoff immediately state */ + ANHS1_BIT = 0x20, /* acceptor not ready holdoff state */ + AEHS_BIT = 0x40, /* acceptor end holdoff state */ +}; + +#endif // _TNT4882_REGISTERS_H diff --git a/drivers/gpib/ines/Makefile b/drivers/gpib/ines/Makefile new file mode 100644 index 000000000000..88241f15ecea --- /dev/null +++ b/drivers/gpib/ines/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_GPIB_INES) += ines_gpib.o + + diff --git a/drivers/gpib/ines/ines.h b/drivers/gpib/ines/ines.h new file mode 100644 index 000000000000..6ad57e9a1216 --- /dev/null +++ b/drivers/gpib/ines/ines.h @@ -0,0 +1,165 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/*************************************************************************** + * Header for ines GPIB boards + * copyright : (C) 2002 by Frank Mori Hess + ***************************************************************************/ + +#ifndef _INES_GPIB_H +#define _INES_GPIB_H + +#include "nec7210.h" +#include "gpibP.h" +#include "plx9050.h" +#include "amcc5920.h" +#include "quancom_pci.h" +#include + +enum ines_pci_chip { + PCI_CHIP_NONE, + PCI_CHIP_PLX9050, + PCI_CHIP_AMCC5920, + PCI_CHIP_QUANCOM, + PCI_CHIP_QUICKLOGIC5030, +}; + +struct ines_priv { + struct nec7210_priv nec7210_priv; + struct pci_dev *pci_device; + // base address for plx9052 pci chip + unsigned long plx_iobase; + // base address for amcc5920 pci chip + unsigned long amcc_iobase; + unsigned int irq; + enum ines_pci_chip pci_chip_type; + u8 extend_mode_bits; +}; + +/* inb/outb wrappers */ +static inline unsigned int ines_inb(struct ines_priv *priv, unsigned int register_number) +{ + return inb(priv->nec7210_priv.iobase + + register_number * priv->nec7210_priv.offset); +} + +static inline void ines_outb(struct ines_priv *priv, unsigned int value, + unsigned int register_number) +{ + outb(value, priv->nec7210_priv.iobase + + register_number * priv->nec7210_priv.offset); +} + +enum ines_regs { + // read + FIFO_STATUS = 0x8, + ISR3 = 0x9, + ISR4 = 0xa, + IN_FIFO_COUNT = 0x10, + OUT_FIFO_COUNT = 0x11, + EXTEND_STATUS = 0xf, + + // write + XDMA_CONTROL = 0x8, + IMR3 = ISR3, + IMR4 = ISR4, + IN_FIFO_WATERMARK = IN_FIFO_COUNT, + OUT_FIFO_WATERMARK = OUT_FIFO_COUNT, + EXTEND_MODE = 0xf, + + // read-write + XFER_COUNT_LOWER = 0xb, + XFER_COUNT_UPPER = 0xc, + BUS_CONTROL_MONITOR = 0x13, +}; + +enum isr3_imr3_bits { + HW_TIMEOUT_BIT = 0x1, + XFER_COUNT_BIT = 0x2, + CMD_RECEIVED_BIT = 0x4, + TCT_RECEIVED_BIT = 0x8, + IFC_ACTIVE_BIT = 0x10, + ATN_ACTIVE_BIT = 0x20, + FIFO_ERROR_BIT = 0x40, +}; + +enum isr4_imr4_bits { + IN_FIFO_WATERMARK_BIT = 0x1, + OUT_FIFO_WATERMARK_BIT = 0x2, + IN_FIFO_FULL_BIT = 0x4, + OUT_FIFO_EMPTY_BIT = 0x8, + IN_FIFO_READY_BIT = 0x10, + OUT_FIFO_READY_BIT = 0x20, + IN_FIFO_EXIT_WATERMARK_BIT = 0x40, + OUT_FIFO_EXIT_WATERMARK_BIT = 0x80, +}; + +enum extend_mode_bits { + TR3_TRIG_ENABLE_BIT = 0x1, // enable generation of trigger pulse T/R3 pin + // clear message available status bit when chip writes byte with EOI true + MAV_ENABLE_BIT = 0x2, + EOS1_ENABLE_BIT = 0x4, // enable eos register 1 + EOS2_ENABLE_BIT = 0x8, // enable eos register 2 + EOIDIS_BIT = 0x10, // disable EOI interrupt when doing rfd holdoff on end? + XFER_COUNTER_ENABLE_BIT = 0x20, + XFER_COUNTER_OUTPUT_BIT = 0x40, // use counter for output, clear for input + // when xfer counter hits 0, assert EOI on write or RFD holdoff on read + LAST_BYTE_HANDLING_BIT = 0x80, +}; + +enum extend_status_bits { + OUTPUT_MESSAGE_IN_PROGRESS_BIT = 0x1, + SCSEL_BIT = 0x2, // statue of SCSEL pin + LISTEN_DISABLED = 0x4, + IN_FIFO_EMPTY_BIT = 0x8, + OUT_FIFO_FULL_BIT = 0x10, +}; + +// ines adds fifo enable bits to address mode register +enum ines_admr_bits { + IN_FIFO_ENABLE_BIT = 0x8, + OUT_FIFO_ENABLE_BIT = 0x4, +}; + +enum xdma_control_bits { + DMA_OUTPUT_BIT = 0x1, // use dma for output, clear for input + ENABLE_SYNC_DMA_BIT = 0x2, + DMA_ACCESS_EVERY_CYCLE = 0x4, // dma accesses fifo every cycle, clear for every other cycle + DMA_16BIT = 0x8, // clear for 8 bit transfers +}; + +enum bus_control_monitor_bits { + BCM_DAV_BIT = 0x1, + BCM_NRFD_BIT = 0x2, + BCM_NDAC_BIT = 0x4, + BCM_IFC_BIT = 0x8, + BCM_ATN_BIT = 0x10, + BCM_SRQ_BIT = 0x20, + BCM_REN_BIT = 0x40, + BCM_EOI_BIT = 0x80, +}; + +enum ines_aux_reg_bits { + INES_AUXD = 0x40, +}; + +enum ines_aux_cmds { + INES_RFD_HLD_IMMEDIATE = 0x4, + INES_AUX_CLR_OUT_FIFO = 0x5, + INES_AUX_CLR_IN_FIFO = 0x6, + INES_AUX_XMODE = 0xa, +}; + +enum ines_auxd_bits { + INES_FOLLOWING_T1_MASK = 0x3, + INES_FOLLOWING_T1_500ns = 0x0, + INES_FOLLOWING_T1_350ns = 0x1, + INES_FOLLOWING_T1_250ns = 0x2, + INES_INITIAL_TI_MASK = 0xc, + INES_INITIAL_T1_2000ns = 0x0, + INES_INITIAL_T1_1100ns = 0x4, + INES_INITIAL_T1_700ns = 0x8, + INES_T6_2us = 0x0, + INES_T6_50us = 0x10, +}; + +#endif // _INES_GPIB_H diff --git a/drivers/gpib/ines/ines_gpib.c b/drivers/gpib/ines/ines_gpib.c new file mode 100644 index 000000000000..a3cf846fd0f9 --- /dev/null +++ b/drivers/gpib/ines/ines_gpib.c @@ -0,0 +1,1500 @@ +// SPDX-License-Identifier: GPL-2.0 + +/*************************************************************************** + * copyright : (C) 1999 Axel Dziemba (axel.dziemba@ines.de) + * (C) 2002 by Frank Mori Hess + ***************************************************************************/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#define dev_fmt pr_fmt +#define DRV_NAME KBUILD_MODNAME + +#include "ines.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "gpib_pci_ids.h" + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("GPIB driver for Ines iGPIB 72010"); + +static irqreturn_t ines_interrupt(struct gpib_board *board); + +static int ines_line_status(const struct gpib_board *board) +{ + int status = VALID_ALL; + int bcm_bits; + struct ines_priv *ines_priv; + + ines_priv = board->private_data; + + bcm_bits = ines_inb(ines_priv, BUS_CONTROL_MONITOR); + + if (bcm_bits & BCM_REN_BIT) + status |= BUS_REN; + if (bcm_bits & BCM_IFC_BIT) + status |= BUS_IFC; + if (bcm_bits & BCM_SRQ_BIT) + status |= BUS_SRQ; + if (bcm_bits & BCM_EOI_BIT) + status |= BUS_EOI; + if (bcm_bits & BCM_NRFD_BIT) + status |= BUS_NRFD; + if (bcm_bits & BCM_NDAC_BIT) + status |= BUS_NDAC; + if (bcm_bits & BCM_DAV_BIT) + status |= BUS_DAV; + if (bcm_bits & BCM_ATN_BIT) + status |= BUS_ATN; + + return status; +} + +static void ines_set_xfer_counter(struct ines_priv *priv, unsigned int count) +{ + if (count > 0xffff) { + pr_err("bug! tried to set xfer counter > 0xffff\n"); + return; + } + ines_outb(priv, (count >> 8) & 0xff, XFER_COUNT_UPPER); + ines_outb(priv, count & 0xff, XFER_COUNT_LOWER); +} + +static int ines_t1_delay(struct gpib_board *board, unsigned int nano_sec) +{ + struct ines_priv *ines_priv = board->private_data; + struct nec7210_priv *nec_priv = &ines_priv->nec7210_priv; + unsigned int retval; + + retval = nec7210_t1_delay(board, nec_priv, nano_sec); + + if (nano_sec <= 250) { + write_byte(nec_priv, INES_AUXD | INES_FOLLOWING_T1_250ns | + INES_INITIAL_T1_2000ns, AUXMR); + retval = 250; + } else if (nano_sec <= 350) { + write_byte(nec_priv, INES_AUXD | INES_FOLLOWING_T1_350ns | + INES_INITIAL_T1_2000ns, AUXMR); + retval = 350; + } else { + write_byte(nec_priv, INES_AUXD | INES_FOLLOWING_T1_500ns | + INES_INITIAL_T1_2000ns, AUXMR); + retval = 500; + } + + return retval; +} + +static inline unsigned short num_in_fifo_bytes(struct ines_priv *ines_priv) +{ + return ines_inb(ines_priv, IN_FIFO_COUNT); +} + +static ssize_t pio_read(struct gpib_board *board, struct ines_priv *ines_priv, u8 *buffer, + size_t length, size_t *nbytes) +{ + ssize_t retval = 0; + unsigned int num_fifo_bytes, i; + struct nec7210_priv *nec_priv = &ines_priv->nec7210_priv; + + *nbytes = 0; + while (*nbytes < length) { + if (wait_event_interruptible(board->wait, + num_in_fifo_bytes(ines_priv) || + test_bit(RECEIVED_END_BN, &nec_priv->state) || + test_bit(DEV_CLEAR_BN, &nec_priv->state) || + test_bit(TIMO_NUM, &board->status))) + return -ERESTARTSYS; + + if (test_bit(TIMO_NUM, &board->status)) + return -ETIMEDOUT; + if (test_bit(DEV_CLEAR_BN, &nec_priv->state)) + return -EINTR; + + num_fifo_bytes = num_in_fifo_bytes(ines_priv); + if (num_fifo_bytes + *nbytes > length) + num_fifo_bytes = length - *nbytes; + + for (i = 0; i < num_fifo_bytes; i++) + buffer[(*nbytes)++] = read_byte(nec_priv, DIR); + if (test_bit(RECEIVED_END_BN, &nec_priv->state) && + num_in_fifo_bytes(ines_priv) == 0) + break; + if (need_resched()) + schedule(); + } + /* make sure RECEIVED_END is in sync */ + ines_interrupt(board); + return retval; +} + +static int ines_accel_read(struct gpib_board *board, u8 *buffer, + size_t length, int *end, size_t *bytes_read) +{ + ssize_t retval = 0; + struct ines_priv *ines_priv = board->private_data; + struct nec7210_priv *nec_priv = &ines_priv->nec7210_priv; + int counter_setting; + + *end = 0; + *bytes_read = 0; + if (length == 0) + return 0; + + clear_bit(DEV_CLEAR_BN, &nec_priv->state); + + write_byte(nec_priv, INES_RFD_HLD_IMMEDIATE, AUXMR); + + // clear in fifo + nec7210_set_reg_bits(nec_priv, ADMR, IN_FIFO_ENABLE_BIT, 0); + nec7210_set_reg_bits(nec_priv, ADMR, IN_FIFO_ENABLE_BIT, IN_FIFO_ENABLE_BIT); + + ines_priv->extend_mode_bits |= LAST_BYTE_HANDLING_BIT; + ines_priv->extend_mode_bits &= ~XFER_COUNTER_OUTPUT_BIT & ~XFER_COUNTER_ENABLE_BIT; + ines_outb(ines_priv, ines_priv->extend_mode_bits, EXTEND_MODE); + + counter_setting = length - num_in_fifo_bytes(ines_priv); + if (counter_setting > 0) { + ines_set_xfer_counter(ines_priv, length); + ines_priv->extend_mode_bits |= XFER_COUNTER_ENABLE_BIT; + ines_outb(ines_priv, ines_priv->extend_mode_bits, EXTEND_MODE); + + // holdoff on END + nec7210_set_handshake_mode(board, nec_priv, HR_HLDE); + /* release rfd holdoff */ + write_byte(nec_priv, AUX_FH, AUXMR); + } + + retval = pio_read(board, ines_priv, buffer, length, bytes_read); + ines_priv->extend_mode_bits &= ~XFER_COUNTER_ENABLE_BIT; + ines_outb(ines_priv, ines_priv->extend_mode_bits, EXTEND_MODE); + if (retval < 0) { + write_byte(nec_priv, INES_RFD_HLD_IMMEDIATE, AUXMR); + return retval; + } + if (test_and_clear_bit(RECEIVED_END_BN, &nec_priv->state)) + *end = 1; + + return retval; +} + +static const int out_fifo_size = 0xff; + +static inline unsigned short num_out_fifo_bytes(struct ines_priv *ines_priv) +{ + return ines_inb(ines_priv, OUT_FIFO_COUNT); +} + +static int ines_write_wait(struct gpib_board *board, struct ines_priv *ines_priv, + unsigned int fifo_threshold) +{ + struct nec7210_priv *nec_priv = &ines_priv->nec7210_priv; + + // wait until byte is ready to be sent + if (wait_event_interruptible(board->wait, + num_out_fifo_bytes(ines_priv) < fifo_threshold || + test_bit(BUS_ERROR_BN, &nec_priv->state) || + test_bit(DEV_CLEAR_BN, &nec_priv->state) || + test_bit(TIMO_NUM, &board->status))) + return -ERESTARTSYS; + + if (test_bit(BUS_ERROR_BN, &nec_priv->state)) + return -EIO; + if (test_bit(DEV_CLEAR_BN, &nec_priv->state)) + return -EINTR; + if (test_bit(TIMO_NUM, &board->status)) + return -ETIMEDOUT; + + return 0; +} + +static int ines_accel_write(struct gpib_board *board, u8 *buffer, size_t length, + int send_eoi, size_t *bytes_written) +{ + size_t count = 0; + ssize_t retval = 0; + struct ines_priv *ines_priv = board->private_data; + struct nec7210_priv *nec_priv = &ines_priv->nec7210_priv; + unsigned int num_bytes, i; + + *bytes_written = 0; + // clear out fifo + nec7210_set_reg_bits(nec_priv, ADMR, OUT_FIFO_ENABLE_BIT, 0); + nec7210_set_reg_bits(nec_priv, ADMR, OUT_FIFO_ENABLE_BIT, OUT_FIFO_ENABLE_BIT); + + ines_priv->extend_mode_bits |= XFER_COUNTER_OUTPUT_BIT; + ines_priv->extend_mode_bits &= ~XFER_COUNTER_ENABLE_BIT; + ines_priv->extend_mode_bits &= ~LAST_BYTE_HANDLING_BIT; + ines_outb(ines_priv, ines_priv->extend_mode_bits, EXTEND_MODE); + + ines_set_xfer_counter(ines_priv, length); + if (send_eoi) + ines_priv->extend_mode_bits |= LAST_BYTE_HANDLING_BIT; + ines_priv->extend_mode_bits |= XFER_COUNTER_ENABLE_BIT; + ines_outb(ines_priv, ines_priv->extend_mode_bits, EXTEND_MODE); + + while (count < length) { + retval = ines_write_wait(board, ines_priv, out_fifo_size); + if (retval < 0) + break; + + num_bytes = out_fifo_size - num_out_fifo_bytes(ines_priv); + if (num_bytes + count > length) + num_bytes = length - count; + for (i = 0; i < num_bytes; i++) + write_byte(nec_priv, buffer[count++], CDOR); + } + if (retval < 0) { + ines_priv->extend_mode_bits &= ~XFER_COUNTER_ENABLE_BIT; + ines_outb(ines_priv, ines_priv->extend_mode_bits, EXTEND_MODE); + *bytes_written = length - num_out_fifo_bytes(ines_priv); + return retval; + } + // wait last byte has been sent + retval = ines_write_wait(board, ines_priv, 1); + ines_priv->extend_mode_bits &= ~XFER_COUNTER_ENABLE_BIT; + ines_outb(ines_priv, ines_priv->extend_mode_bits, EXTEND_MODE); + *bytes_written = length - num_out_fifo_bytes(ines_priv); + + return retval; +} + +static irqreturn_t ines_pci_interrupt(int irq, void *arg) +{ + struct gpib_board *board = arg; + struct ines_priv *priv = board->private_data; + struct nec7210_priv *nec_priv = &priv->nec7210_priv; + + if (priv->pci_chip_type == PCI_CHIP_QUANCOM) { + if ((inb(nec_priv->iobase + + QUANCOM_IRQ_CONTROL_STATUS_REG) & + QUANCOM_IRQ_ASSERTED_BIT)) + outb(QUANCOM_IRQ_ENABLE_BIT, nec_priv->iobase + + QUANCOM_IRQ_CONTROL_STATUS_REG); + } + + return ines_interrupt(board); +} + +static irqreturn_t ines_interrupt(struct gpib_board *board) +{ + struct ines_priv *priv = board->private_data; + struct nec7210_priv *nec_priv = &priv->nec7210_priv; + unsigned int isr3_bits, isr4_bits; + unsigned long flags; + int wake = 0; + + spin_lock_irqsave(&board->spinlock, flags); + + nec7210_interrupt(board, nec_priv); + isr3_bits = ines_inb(priv, ISR3); + isr4_bits = ines_inb(priv, ISR4); + if (isr3_bits & IFC_ACTIVE_BIT) { + push_gpib_event(board, EVENT_IFC); + wake++; + } + if (isr3_bits & FIFO_ERROR_BIT) + dev_err(board->gpib_dev, "fifo error\n"); + if (isr3_bits & XFER_COUNT_BIT) + wake++; + + if (isr4_bits & (IN_FIFO_WATERMARK_BIT | IN_FIFO_FULL_BIT | OUT_FIFO_WATERMARK_BIT | + OUT_FIFO_EMPTY_BIT)) + wake++; + + if (wake) + wake_up_interruptible(&board->wait); + spin_unlock_irqrestore(&board->spinlock, flags); + return IRQ_HANDLED; +} + +static int ines_pci_attach(struct gpib_board *board, const struct gpib_board_config *config); +static int ines_pci_accel_attach(struct gpib_board *board, const struct gpib_board_config *config); +static int ines_isa_attach(struct gpib_board *board, const struct gpib_board_config *config); + +static void ines_pci_detach(struct gpib_board *board); +static void ines_isa_detach(struct gpib_board *board); + +enum ines_pci_vendor_ids { + PCI_VENDOR_ID_INES_QUICKLOGIC = 0x16da +}; + +enum ines_pci_device_ids { + PCI_DEVICE_ID_INES_GPIB_AMCC = 0x8507, + PCI_DEVICE_ID_INES_GPIB_QL5030 = 0x11, +}; + +enum ines_pci_subdevice_ids { + PCI_SUBDEVICE_ID_INES_GPIB = 0x1072 +}; + +static struct pci_device_id ines_pci_table[] = { + {PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_9050, PCI_VENDOR_ID_PLX, + PCI_SUBDEVICE_ID_INES_GPIB, 0, 0, 0}, + {PCI_VENDOR_ID_AMCC, PCI_DEVICE_ID_INES_GPIB_AMCC, PCI_VENDOR_ID_AMCC, + PCI_SUBDEVICE_ID_INES_GPIB, 0, 0, 0}, + {PCI_VENDOR_ID_INES_QUICKLOGIC, PCI_DEVICE_ID_INES_GPIB_QL5030, + PCI_VENDOR_ID_INES_QUICKLOGIC, PCI_DEVICE_ID_INES_GPIB_QL5030, 0, 0, 0}, + {PCI_DEVICE(PCI_VENDOR_ID_QUANCOM, PCI_DEVICE_ID_QUANCOM_GPIB)}, + {0} +}; +MODULE_DEVICE_TABLE(pci, ines_pci_table); + +struct ines_pci_id { + unsigned int vendor_id; + unsigned int device_id; + int subsystem_vendor_id; + int subsystem_device_id; + unsigned int gpib_region; + unsigned int io_offset; + enum ines_pci_chip pci_chip_type; +}; + +static struct ines_pci_id pci_ids[] = { + {.vendor_id = PCI_VENDOR_ID_PLX, + .device_id = PCI_DEVICE_ID_PLX_9050, + .subsystem_vendor_id = PCI_VENDOR_ID_PLX, + .subsystem_device_id = PCI_SUBDEVICE_ID_INES_GPIB, + .gpib_region = 2, + .io_offset = 1, + .pci_chip_type = PCI_CHIP_PLX9050, + }, + {.vendor_id = PCI_VENDOR_ID_AMCC, + .device_id = PCI_DEVICE_ID_INES_GPIB_AMCC, + .subsystem_vendor_id = PCI_VENDOR_ID_AMCC, + .subsystem_device_id = PCI_SUBDEVICE_ID_INES_GPIB, + .gpib_region = 1, + .io_offset = 1, + .pci_chip_type = PCI_CHIP_AMCC5920, + }, + {.vendor_id = PCI_VENDOR_ID_INES_QUICKLOGIC, + .device_id = PCI_DEVICE_ID_INES_GPIB_QL5030, + .subsystem_vendor_id = PCI_VENDOR_ID_INES_QUICKLOGIC, + .subsystem_device_id = PCI_DEVICE_ID_INES_GPIB_QL5030, + .gpib_region = 1, + .io_offset = 1, + .pci_chip_type = PCI_CHIP_QUICKLOGIC5030, + }, + {.vendor_id = PCI_VENDOR_ID_QUANCOM, + .device_id = PCI_DEVICE_ID_QUANCOM_GPIB, + .subsystem_vendor_id = -1, + .subsystem_device_id = -1, + .gpib_region = 0, + .io_offset = 4, + .pci_chip_type = PCI_CHIP_QUANCOM, + }, +}; + +static const int num_pci_chips = ARRAY_SIZE(pci_ids); + +// wrappers for interface functions +static int ines_read(struct gpib_board *board, u8 *buffer, size_t length, + int *end, size_t *bytes_read) +{ + struct ines_priv *priv = board->private_data; + struct nec7210_priv *nec_priv = &priv->nec7210_priv; + ssize_t retval; + int dummy; + + retval = nec7210_read(board, &priv->nec7210_priv, buffer, length, end, bytes_read); + if (retval < 0) { + write_byte(nec_priv, INES_RFD_HLD_IMMEDIATE, AUXMR); + + set_bit(RFD_HOLDOFF_BN, &nec_priv->state); + + nec7210_read_data_in(board, nec_priv, &dummy); + } + return retval; +} + +static int ines_write(struct gpib_board *board, u8 *buffer, size_t length, int send_eoi, + size_t *bytes_written) +{ + struct ines_priv *priv = board->private_data; + + return nec7210_write(board, &priv->nec7210_priv, buffer, length, send_eoi, bytes_written); +} + +static int ines_command(struct gpib_board *board, u8 *buffer, size_t length, size_t *bytes_written) +{ + struct ines_priv *priv = board->private_data; + + return nec7210_command(board, &priv->nec7210_priv, buffer, length, bytes_written); +} + +static int ines_take_control(struct gpib_board *board, int synchronous) +{ + struct ines_priv *priv = board->private_data; + + return nec7210_take_control(board, &priv->nec7210_priv, synchronous); +} + +static int ines_go_to_standby(struct gpib_board *board) +{ + struct ines_priv *priv = board->private_data; + + return nec7210_go_to_standby(board, &priv->nec7210_priv); +} + +static int ines_request_system_control(struct gpib_board *board, int request_control) +{ + struct ines_priv *priv = board->private_data; + + return nec7210_request_system_control(board, &priv->nec7210_priv, request_control); +} + +static void ines_interface_clear(struct gpib_board *board, int assert) +{ + struct ines_priv *priv = board->private_data; + + nec7210_interface_clear(board, &priv->nec7210_priv, assert); +} + +static void ines_remote_enable(struct gpib_board *board, int enable) +{ + struct ines_priv *priv = board->private_data; + + nec7210_remote_enable(board, &priv->nec7210_priv, enable); +} + +static int ines_enable_eos(struct gpib_board *board, u8 eos_byte, int compare_8_bits) +{ + struct ines_priv *priv = board->private_data; + + return nec7210_enable_eos(board, &priv->nec7210_priv, eos_byte, compare_8_bits); +} + +static void ines_disable_eos(struct gpib_board *board) +{ + struct ines_priv *priv = board->private_data; + + nec7210_disable_eos(board, &priv->nec7210_priv); +} + +static unsigned int ines_update_status(struct gpib_board *board, unsigned int clear_mask) +{ + struct ines_priv *priv = board->private_data; + + return nec7210_update_status(board, &priv->nec7210_priv, clear_mask); +} + +static int ines_primary_address(struct gpib_board *board, unsigned int address) +{ + struct ines_priv *priv = board->private_data; + + return nec7210_primary_address(board, &priv->nec7210_priv, address); +} + +static int ines_secondary_address(struct gpib_board *board, unsigned int address, int enable) +{ + struct ines_priv *priv = board->private_data; + + return nec7210_secondary_address(board, &priv->nec7210_priv, address, enable); +} + +static int ines_parallel_poll(struct gpib_board *board, u8 *result) +{ + struct ines_priv *priv = board->private_data; + + return nec7210_parallel_poll(board, &priv->nec7210_priv, result); +} + +static void ines_parallel_poll_configure(struct gpib_board *board, u8 config) +{ + struct ines_priv *priv = board->private_data; + + nec7210_parallel_poll_configure(board, &priv->nec7210_priv, config); +} + +static void ines_parallel_poll_response(struct gpib_board *board, int ist) +{ + struct ines_priv *priv = board->private_data; + + nec7210_parallel_poll_response(board, &priv->nec7210_priv, ist); +} + +static void ines_serial_poll_response(struct gpib_board *board, u8 status) +{ + struct ines_priv *priv = board->private_data; + + nec7210_serial_poll_response(board, &priv->nec7210_priv, status); +} + +static u8 ines_serial_poll_status(struct gpib_board *board) +{ + struct ines_priv *priv = board->private_data; + + return nec7210_serial_poll_status(board, &priv->nec7210_priv); +} + +static void ines_return_to_local(struct gpib_board *board) +{ + struct ines_priv *priv = board->private_data; + + nec7210_return_to_local(board, &priv->nec7210_priv); +} + +static struct gpib_interface ines_pci_unaccel_interface = { + .name = "ines_pci_unaccel", + .attach = ines_pci_attach, + .detach = ines_pci_detach, + .read = ines_read, + .write = ines_write, + .command = ines_command, + .take_control = ines_take_control, + .go_to_standby = ines_go_to_standby, + .request_system_control = ines_request_system_control, + .interface_clear = ines_interface_clear, + .remote_enable = ines_remote_enable, + .enable_eos = ines_enable_eos, + .disable_eos = ines_disable_eos, + .parallel_poll = ines_parallel_poll, + .parallel_poll_configure = ines_parallel_poll_configure, + .parallel_poll_response = ines_parallel_poll_response, + .local_parallel_poll_mode = NULL, // XXX + .line_status = ines_line_status, + .update_status = ines_update_status, + .primary_address = ines_primary_address, + .secondary_address = ines_secondary_address, + .serial_poll_response = ines_serial_poll_response, + .serial_poll_status = ines_serial_poll_status, + .t1_delay = ines_t1_delay, + .return_to_local = ines_return_to_local, +}; + +static struct gpib_interface ines_pci_interface = { + .name = "ines_pci", + .attach = ines_pci_accel_attach, + .detach = ines_pci_detach, + .read = ines_accel_read, + .write = ines_accel_write, + .command = ines_command, + .take_control = ines_take_control, + .go_to_standby = ines_go_to_standby, + .request_system_control = ines_request_system_control, + .interface_clear = ines_interface_clear, + .remote_enable = ines_remote_enable, + .enable_eos = ines_enable_eos, + .disable_eos = ines_disable_eos, + .parallel_poll = ines_parallel_poll, + .parallel_poll_configure = ines_parallel_poll_configure, + .parallel_poll_response = ines_parallel_poll_response, + .local_parallel_poll_mode = NULL, // XXX + .line_status = ines_line_status, + .update_status = ines_update_status, + .primary_address = ines_primary_address, + .secondary_address = ines_secondary_address, + .serial_poll_response = ines_serial_poll_response, + .serial_poll_status = ines_serial_poll_status, + .t1_delay = ines_t1_delay, + .return_to_local = ines_return_to_local, +}; + +static struct gpib_interface ines_pci_accel_interface = { + .name = "ines_pci_accel", + .attach = ines_pci_accel_attach, + .detach = ines_pci_detach, + .read = ines_accel_read, + .write = ines_accel_write, + .command = ines_command, + .take_control = ines_take_control, + .go_to_standby = ines_go_to_standby, + .request_system_control = ines_request_system_control, + .interface_clear = ines_interface_clear, + .remote_enable = ines_remote_enable, + .enable_eos = ines_enable_eos, + .disable_eos = ines_disable_eos, + .parallel_poll = ines_parallel_poll, + .parallel_poll_configure = ines_parallel_poll_configure, + .parallel_poll_response = ines_parallel_poll_response, + .local_parallel_poll_mode = NULL, // XXX + .line_status = ines_line_status, + .update_status = ines_update_status, + .primary_address = ines_primary_address, + .secondary_address = ines_secondary_address, + .serial_poll_response = ines_serial_poll_response, + .serial_poll_status = ines_serial_poll_status, + .t1_delay = ines_t1_delay, + .return_to_local = ines_return_to_local, +}; + +static struct gpib_interface ines_isa_interface = { + .name = "ines_isa", + .attach = ines_isa_attach, + .detach = ines_isa_detach, + .read = ines_accel_read, + .write = ines_accel_write, + .command = ines_command, + .take_control = ines_take_control, + .go_to_standby = ines_go_to_standby, + .request_system_control = ines_request_system_control, + .interface_clear = ines_interface_clear, + .remote_enable = ines_remote_enable, + .enable_eos = ines_enable_eos, + .disable_eos = ines_disable_eos, + .parallel_poll = ines_parallel_poll, + .parallel_poll_configure = ines_parallel_poll_configure, + .parallel_poll_response = ines_parallel_poll_response, + .local_parallel_poll_mode = NULL, // XXX + .line_status = ines_line_status, + .update_status = ines_update_status, + .primary_address = ines_primary_address, + .secondary_address = ines_secondary_address, + .serial_poll_response = ines_serial_poll_response, + .serial_poll_status = ines_serial_poll_status, + .t1_delay = ines_t1_delay, + .return_to_local = ines_return_to_local, +}; + +static int ines_allocate_private(struct gpib_board *board) +{ + struct ines_priv *priv; + + board->private_data = kmalloc(sizeof(struct ines_priv), GFP_KERNEL); + if (!board->private_data) + return -1; + priv = board->private_data; + memset(priv, 0, sizeof(struct ines_priv)); + init_nec7210_private(&priv->nec7210_priv); + return 0; +} + +static void ines_free_private(struct gpib_board *board) +{ + kfree(board->private_data); + board->private_data = NULL; +} + +static int ines_generic_attach(struct gpib_board *board) +{ + struct ines_priv *ines_priv; + struct nec7210_priv *nec_priv; + + board->status = 0; + + if (ines_allocate_private(board)) + return -ENOMEM; + ines_priv = board->private_data; + nec_priv = &ines_priv->nec7210_priv; + nec_priv->read_byte = nec7210_ioport_read_byte; + nec_priv->write_byte = nec7210_ioport_write_byte; + nec_priv->offset = 1; + nec_priv->type = IGPIB7210; + ines_priv->pci_chip_type = PCI_CHIP_NONE; + + return 0; +} + +static void ines_online(struct ines_priv *ines_priv, const struct gpib_board *board, int use_accel) +{ + struct nec7210_priv *nec_priv = &ines_priv->nec7210_priv; + + /* ines doesn't seem to use internal count register */ + write_byte(nec_priv, ICR | 0, AUXMR); + + write_byte(nec_priv, INES_AUX_XMODE, AUXMR); + write_byte(nec_priv, INES_RFD_HLD_IMMEDIATE, AUXMR); + + set_bit(RFD_HOLDOFF_BN, &nec_priv->state); + + write_byte(nec_priv, INES_AUXD | 0, AUXMR); + ines_outb(ines_priv, 0, XDMA_CONTROL); + ines_priv->extend_mode_bits = 0; + ines_outb(ines_priv, ines_priv->extend_mode_bits, EXTEND_MODE); + if (use_accel) { + ines_outb(ines_priv, 0x80, OUT_FIFO_WATERMARK); + ines_outb(ines_priv, 0x80, IN_FIFO_WATERMARK); + ines_outb(ines_priv, IFC_ACTIVE_BIT | ATN_ACTIVE_BIT | + FIFO_ERROR_BIT | XFER_COUNT_BIT, IMR3); + ines_outb(ines_priv, IN_FIFO_WATERMARK_BIT | IN_FIFO_FULL_BIT | + OUT_FIFO_WATERMARK_BIT | OUT_FIFO_EMPTY_BIT, IMR4); + } else { + nec7210_set_reg_bits(nec_priv, ADMR, IN_FIFO_ENABLE_BIT | OUT_FIFO_ENABLE_BIT, 0); + ines_outb(ines_priv, IFC_ACTIVE_BIT | FIFO_ERROR_BIT, IMR3); + ines_outb(ines_priv, 0, IMR4); + } + + nec7210_board_online(nec_priv, board); + if (use_accel) + nec7210_set_reg_bits(nec_priv, IMR1, HR_DOIE | HR_DIIE, 0); +} + +static int ines_common_pci_attach(struct gpib_board *board, const struct gpib_board_config *config) +{ + struct ines_priv *ines_priv; + struct nec7210_priv *nec_priv; + int isr_flags = 0; + int retval; + struct ines_pci_id found_id; + unsigned int i; + struct pci_dev *pdev; + + memset(&found_id, 0, sizeof(found_id)); + + retval = ines_generic_attach(board); + if (retval) + return retval; + + ines_priv = board->private_data; + nec_priv = &ines_priv->nec7210_priv; + + // find board + ines_priv->pci_device = NULL; + for (i = 0; i < num_pci_chips && !ines_priv->pci_device; i++) { + pdev = NULL; + do { + if (pci_ids[i].subsystem_vendor_id >= 0 && + pci_ids[i].subsystem_device_id >= 0) + pdev = pci_get_subsys(pci_ids[i].vendor_id, pci_ids[i].device_id, + pci_ids[i].subsystem_vendor_id, + pci_ids[i].subsystem_device_id, pdev); + else + pdev = pci_get_device(pci_ids[i].vendor_id, pci_ids[i].device_id, + pdev); + if (!pdev) + break; + if (config->pci_bus >= 0 && config->pci_bus != pdev->bus->number) + continue; + if (config->pci_slot >= 0 && config->pci_slot != PCI_SLOT(pdev->devfn)) + continue; + found_id = pci_ids[i]; + ines_priv->pci_device = pdev; + break; + } while (1); + } + if (!ines_priv->pci_device) { + dev_err(board->gpib_dev, "could not find ines PCI board\n"); + return -1; + } + + if (pci_enable_device(ines_priv->pci_device)) { + dev_err(board->gpib_dev, "error enabling pci device\n"); + return -1; + } + + if (pci_request_regions(ines_priv->pci_device, DRV_NAME)) + return -1; + nec_priv->iobase = pci_resource_start(ines_priv->pci_device, + found_id.gpib_region); + + ines_priv->pci_chip_type = found_id.pci_chip_type; + nec_priv->offset = found_id.io_offset; + switch (ines_priv->pci_chip_type) { + case PCI_CHIP_PLX9050: + ines_priv->plx_iobase = pci_resource_start(ines_priv->pci_device, 1); + break; + case PCI_CHIP_AMCC5920: + ines_priv->amcc_iobase = pci_resource_start(ines_priv->pci_device, 0); + break; + case PCI_CHIP_QUANCOM: + break; + case PCI_CHIP_QUICKLOGIC5030: + break; + default: + dev_err(board->gpib_dev, "unspecified chip type? (bug)\n"); + nec_priv->iobase = 0; + pci_release_regions(ines_priv->pci_device); + return -1; + } + + nec7210_board_reset(nec_priv, board); +#ifdef QUANCOM_PCI + if (ines_priv->pci_chip_type == PCI_CHIP_QUANCOM) { + /* change interrupt polarity */ + nec_priv->auxb_bits |= HR_INV; + ines_outb(ines_priv, nec_priv->auxb_bits, AUXMR); + } +#endif + isr_flags |= IRQF_SHARED; + if (request_irq(ines_priv->pci_device->irq, ines_pci_interrupt, isr_flags, + DRV_NAME, board)) { + dev_err(board->gpib_dev, "can't request IRQ %d\n", ines_priv->pci_device->irq); + return -1; + } + ines_priv->irq = ines_priv->pci_device->irq; + + // enable interrupts on pci chip + switch (ines_priv->pci_chip_type) { + case PCI_CHIP_PLX9050: + outl(PLX9050_LINTR1_EN_BIT | PLX9050_LINTR1_POLARITY_BIT | PLX9050_PCI_INTR_EN_BIT, + ines_priv->plx_iobase + PLX9050_INTCSR_REG); + break; + case PCI_CHIP_AMCC5920: + { + static const int region = 1; + static const int num_wait_states = 7; + u32 bits; + + bits = amcc_prefetch_bits(region, PREFETCH_DISABLED); + bits |= amcc_PTADR_mode_bit(region); + bits |= amcc_disable_write_fifo_bit(region); + bits |= amcc_wait_state_bits(region, num_wait_states); + outl(bits, ines_priv->amcc_iobase + AMCC_PASS_THRU_REG); + outl(AMCC_ADDON_INTR_ENABLE_BIT, ines_priv->amcc_iobase + AMCC_INTCS_REG); + } + break; + case PCI_CHIP_QUANCOM: + outb(QUANCOM_IRQ_ENABLE_BIT, nec_priv->iobase + + QUANCOM_IRQ_CONTROL_STATUS_REG); + break; + case PCI_CHIP_QUICKLOGIC5030: + break; + default: + dev_err(board->gpib_dev, "unspecified chip type? (bug)\n"); + return -1; + } + + return 0; +} + +static int ines_pci_attach(struct gpib_board *board, const struct gpib_board_config *config) +{ + struct ines_priv *ines_priv; + int retval; + + retval = ines_common_pci_attach(board, config); + if (retval < 0) + return retval; + + ines_priv = board->private_data; + ines_online(ines_priv, board, 0); + + return 0; +} + +static int ines_pci_accel_attach(struct gpib_board *board, const struct gpib_board_config *config) +{ + struct ines_priv *ines_priv; + int retval; + + retval = ines_common_pci_attach(board, config); + if (retval < 0) + return retval; + + ines_priv = board->private_data; + ines_online(ines_priv, board, 1); + + return 0; +} + +static const int ines_isa_iosize = 0x20; + +static int ines_isa_attach(struct gpib_board *board, const struct gpib_board_config *config) +{ + struct ines_priv *ines_priv; + struct nec7210_priv *nec_priv; + int isr_flags = 0; + int retval; + + retval = ines_generic_attach(board); + if (retval) + return retval; + + ines_priv = board->private_data; + nec_priv = &ines_priv->nec7210_priv; + + if (!request_region(config->ibbase, ines_isa_iosize, DRV_NAME)) { + dev_err(board->gpib_dev, "ioports at 0x%x already in use\n", + config->ibbase); + return -EBUSY; + } + nec_priv->iobase = config->ibbase; + nec_priv->offset = 1; + nec7210_board_reset(nec_priv, board); + if (request_irq(config->ibirq, ines_pci_interrupt, isr_flags, DRV_NAME, board)) { + dev_err(board->gpib_dev, "failed to allocate IRQ %d\n", config->ibirq); + return -1; + } + ines_priv->irq = config->ibirq; + ines_online(ines_priv, board, 1); + return 0; +} + +static void ines_pci_detach(struct gpib_board *board) +{ + struct ines_priv *ines_priv = board->private_data; + struct nec7210_priv *nec_priv; + + if (ines_priv) { + nec_priv = &ines_priv->nec7210_priv; + if (ines_priv->irq) { + // disable interrupts + switch (ines_priv->pci_chip_type) { + case PCI_CHIP_AMCC5920: + if (ines_priv->plx_iobase) + outl(0, ines_priv->plx_iobase + PLX9050_INTCSR_REG); + break; + case PCI_CHIP_QUANCOM: + if (nec_priv->iobase) + outb(0, nec_priv->iobase + + QUANCOM_IRQ_CONTROL_STATUS_REG); + break; + default: + break; + } + free_irq(ines_priv->irq, board); + } + if (nec_priv->iobase) { + nec7210_board_reset(nec_priv, board); + pci_release_regions(ines_priv->pci_device); + } + if (ines_priv->pci_device) + pci_dev_put(ines_priv->pci_device); + } + ines_free_private(board); +} + +static void ines_isa_detach(struct gpib_board *board) +{ + struct ines_priv *ines_priv = board->private_data; + struct nec7210_priv *nec_priv; + + if (ines_priv) { + nec_priv = &ines_priv->nec7210_priv; + if (ines_priv->irq) + free_irq(ines_priv->irq, board); + if (nec_priv->iobase) { + nec7210_board_reset(nec_priv, board); + release_region(nec_priv->iobase, ines_isa_iosize); + } + } + ines_free_private(board); +} + +static int ines_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + return 0; +} + +static struct pci_driver ines_pci_driver = { + .name = "ines_gpib", + .id_table = ines_pci_table, + .probe = &ines_pci_probe +}; + +#ifdef CONFIG_GPIB_PCMCIA + +#include +#include +#include +#include + +#include +#include +#include + +static const int ines_pcmcia_iosize = 0x20; + +/* + * The event() function is this driver's Card Services event handler. + * It will be called by Card Services when an appropriate card status + * event is received. The config() and release() entry points are + * used to configure or release a socket, in response to card insertion + * and ejection events. They are invoked from the gpib event + * handler. + */ + +static int ines_gpib_config(struct pcmcia_device *link); +static void ines_gpib_release(struct pcmcia_device *link); +static int ines_pcmcia_attach(struct gpib_board *board, const struct gpib_board_config *config); +static int ines_pcmcia_accel_attach(struct gpib_board *board, + const struct gpib_board_config *config); +static void ines_pcmcia_detach(struct gpib_board *board); +static int ines_common_pcmcia_attach(struct gpib_board *board); +/* + * A linked list of "instances" of the gpib device. Each actual + * PCMCIA card corresponds to one device instance, and is described + * by one dev_link_t structure (defined in ds.h). + * + * You may not want to use a linked list for this -- for example, the + * memory card driver uses an array of dev_link_t pointers, where minor + * device numbers are used to derive the corresponding array index. + */ + +static struct pcmcia_device *curr_dev; + +/* + * A dev_link_t structure has fields for most things that are needed + * to keep track of a socket, but there will usually be some device + * specific information that also needs to be kept track of. The + * 'priv' pointer in a dev_link_t structure can be used to point to + * a device-specific private data structure, like this. + * + * A driver needs to provide a dev_node_t structure for each device + * on a card. In some cases, there is only one device per card (for + * example, ethernet cards, modems). In other cases, there may be + * many actual or logical devices (SCSI adapters, memory cards with + * multiple partitions). The dev_node_t structures need to be kept + * in a linked list starting at the 'dev' field of a dev_link_t + * structure. We allocate them in the card's private data structure, + * because they generally can't be allocated dynamically. + */ + +struct local_info { + struct pcmcia_device *p_dev; + struct gpib_board *dev; + u_short manfid; + u_short cardid; +}; + +/* + * gpib_attach() creates an "instance" of the driver, allocating + * local data structures for one device. The device is registered + * with Card Services. + * + * The dev_link structure is initialized, but we don't actually + * configure the card at this point -- we wait until we receive a + * card insertion event. + */ +static int ines_gpib_probe(struct pcmcia_device *link) +{ + struct local_info *info; + +// int ret, i; + + /* Allocate space for private device-specific data */ + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->p_dev = link; + link->priv = info; + + /* The io structure describes IO port mapping */ + link->resource[0]->end = 32; + link->resource[0]->flags &= ~IO_DATA_PATH_WIDTH; + link->resource[0]->flags |= IO_DATA_PATH_WIDTH_8; + link->io_lines = 5; + + /* General socket configuration */ + link->config_flags = CONF_ENABLE_IRQ | CONF_AUTO_SET_IO; + + /* Register with Card Services */ + curr_dev = link; + return ines_gpib_config(link); +} + +/* + * This deletes a driver "instance". The device is de-registered + * with Card Services. If it has been released, all local data + * structures are freed. Otherwise, the structures will be freed + * when the device is released. + */ +static void ines_gpib_remove(struct pcmcia_device *link) +{ + struct local_info *info = link->priv; + //struct struct gpib_board *dev = info->dev; + + if (info->dev) + ines_pcmcia_detach(info->dev); + ines_gpib_release(link); + + //free_netdev(dev); + kfree(info); +} + +static int ines_gpib_config_iteration(struct pcmcia_device *link, void *priv_data) +{ + return pcmcia_request_io(link); +} + +/* + * gpib_config() is scheduled to run after a CARD_INSERTION event + * is received, to configure the PCMCIA socket, and to make the + * device available to the system. + */ +static int ines_gpib_config(struct pcmcia_device *link) +{ + int retval; + void __iomem *virt; + + retval = pcmcia_loop_config(link, &ines_gpib_config_iteration, NULL); + if (retval) { + dev_warn(&link->dev, "no configuration found\n"); + ines_gpib_release(link); + return -ENODEV; + } + + dev_dbg(&link->dev, "ines_cs: manufacturer: 0x%x card: 0x%x\n", + link->manf_id, link->card_id); + + /* + * for the ines card we have to setup the configuration registers in + * attribute memory here + */ + link->resource[2]->flags |= WIN_MEMORY_TYPE_AM | WIN_DATA_WIDTH_8 | WIN_ENABLE; + link->resource[2]->end = 0x1000; + retval = pcmcia_request_window(link, link->resource[2], 250); + if (retval) { + dev_warn(&link->dev, "pcmcia_request_window failed\n"); + ines_gpib_release(link); + return -ENODEV; + } + retval = pcmcia_map_mem_page(link, link->resource[2], 0); + if (retval) { + dev_warn(&link->dev, "pcmcia_map_mem_page failed\n"); + ines_gpib_release(link); + return -ENODEV; + } + virt = ioremap(link->resource[2]->start, resource_size(link->resource[2])); + writeb((link->resource[2]->start >> 2) & 0xff, virt + 0xf0); // IOWindow base + iounmap(virt); + + /* + * This actually configures the PCMCIA socket -- setting up + * the I/O windows and the interrupt mapping. + */ + retval = pcmcia_enable_device(link); + if (retval) { + ines_gpib_release(link); + return -ENODEV; + } + return 0; +} /* gpib_config */ + +/* + * After a card is removed, gpib_release() will unregister the net + * device, and release the PCMCIA configuration. If the device is + * still open, this will be postponed until it is closed. + */ + +static void ines_gpib_release(struct pcmcia_device *link) +{ + pcmcia_disable_device(link); +} /* gpib_release */ + +static int ines_gpib_suspend(struct pcmcia_device *link) +{ + //struct local_info *info = link->priv; + //struct struct gpib_board *dev = info->dev; + + if (link->open) + dev_err(&link->dev, "Device still open\n"); + //netif_device_detach(dev); + + return 0; +} + +static int ines_gpib_resume(struct pcmcia_device *link) +{ + //struct local_info_t *info = link->priv; + //struct struct gpib_board *dev = info->dev; + + /*if (link->open) { + * ni_gpib_probe(dev); / really? + * //netif_device_attach(dev); + *} + */ + return ines_gpib_config(link); +} + +static struct pcmcia_device_id ines_pcmcia_ids[] = { + PCMCIA_DEVICE_MANF_CARD(0x01b4, 0x4730), + PCMCIA_DEVICE_NULL +}; +MODULE_DEVICE_TABLE(pcmcia, ines_pcmcia_ids); + +static struct pcmcia_driver ines_gpib_cs_driver = { + .owner = THIS_MODULE, + .name = "ines_gpib_cs", + .id_table = ines_pcmcia_ids, + .probe = ines_gpib_probe, + .remove = ines_gpib_remove, + .suspend = ines_gpib_suspend, + .resume = ines_gpib_resume, +}; + +static void ines_pcmcia_cleanup_module(void) +{ + pcmcia_unregister_driver(&ines_gpib_cs_driver); +} + +static struct gpib_interface ines_pcmcia_unaccel_interface = { + .name = "ines_pcmcia_unaccel", + .attach = ines_pcmcia_attach, + .detach = ines_pcmcia_detach, + .read = ines_read, + .write = ines_write, + .command = ines_command, + .take_control = ines_take_control, + .go_to_standby = ines_go_to_standby, + .request_system_control = ines_request_system_control, + .interface_clear = ines_interface_clear, + .remote_enable = ines_remote_enable, + .enable_eos = ines_enable_eos, + .disable_eos = ines_disable_eos, + .parallel_poll = ines_parallel_poll, + .parallel_poll_configure = ines_parallel_poll_configure, + .parallel_poll_response = ines_parallel_poll_response, + .local_parallel_poll_mode = NULL, // XXX + .line_status = ines_line_status, + .update_status = ines_update_status, + .primary_address = ines_primary_address, + .secondary_address = ines_secondary_address, + .serial_poll_response = ines_serial_poll_response, + .serial_poll_status = ines_serial_poll_status, + .t1_delay = ines_t1_delay, + .return_to_local = ines_return_to_local, +}; + +static struct gpib_interface ines_pcmcia_accel_interface = { + .name = "ines_pcmcia_accel", + .attach = ines_pcmcia_accel_attach, + .detach = ines_pcmcia_detach, + .read = ines_accel_read, + .write = ines_accel_write, + .command = ines_command, + .take_control = ines_take_control, + .go_to_standby = ines_go_to_standby, + .request_system_control = ines_request_system_control, + .interface_clear = ines_interface_clear, + .remote_enable = ines_remote_enable, + .enable_eos = ines_enable_eos, + .disable_eos = ines_disable_eos, + .parallel_poll = ines_parallel_poll, + .parallel_poll_configure = ines_parallel_poll_configure, + .parallel_poll_response = ines_parallel_poll_response, + .local_parallel_poll_mode = NULL, // XXX + .line_status = ines_line_status, + .update_status = ines_update_status, + .primary_address = ines_primary_address, + .secondary_address = ines_secondary_address, + .serial_poll_response = ines_serial_poll_response, + .serial_poll_status = ines_serial_poll_status, + .t1_delay = ines_t1_delay, + .return_to_local = ines_return_to_local, +}; + +static struct gpib_interface ines_pcmcia_interface = { + .name = "ines_pcmcia", + .attach = ines_pcmcia_accel_attach, + .detach = ines_pcmcia_detach, + .read = ines_accel_read, + .write = ines_accel_write, + .command = ines_command, + .take_control = ines_take_control, + .go_to_standby = ines_go_to_standby, + .request_system_control = ines_request_system_control, + .interface_clear = ines_interface_clear, + .remote_enable = ines_remote_enable, + .enable_eos = ines_enable_eos, + .disable_eos = ines_disable_eos, + .parallel_poll = ines_parallel_poll, + .parallel_poll_configure = ines_parallel_poll_configure, + .parallel_poll_response = ines_parallel_poll_response, + .local_parallel_poll_mode = NULL, // XXX + .line_status = ines_line_status, + .update_status = ines_update_status, + .primary_address = ines_primary_address, + .secondary_address = ines_secondary_address, + .serial_poll_response = ines_serial_poll_response, + .serial_poll_status = ines_serial_poll_status, + .t1_delay = ines_t1_delay, + .return_to_local = ines_return_to_local, +}; + +static irqreturn_t ines_pcmcia_interrupt(int irq, void *arg) +{ + struct gpib_board *board = arg; + + return ines_interrupt(board); +} + +static int ines_common_pcmcia_attach(struct gpib_board *board) +{ + struct ines_priv *ines_priv; + struct nec7210_priv *nec_priv; + int retval; + + if (!curr_dev) { + dev_err(board->gpib_dev, "no ines pcmcia cards found\n"); + return -1; + } + + retval = ines_generic_attach(board); + if (retval) + return retval; + + ines_priv = board->private_data; + nec_priv = &ines_priv->nec7210_priv; + + if (!request_region(curr_dev->resource[0]->start, + resource_size(curr_dev->resource[0]), DRV_NAME)) { + dev_err(board->gpib_dev, "ioports at 0x%lx already in use\n", + (unsigned long)(curr_dev->resource[0]->start)); + return -1; + } + + nec_priv->iobase = curr_dev->resource[0]->start; + + nec7210_board_reset(nec_priv, board); + + if (request_irq(curr_dev->irq, ines_pcmcia_interrupt, IRQF_SHARED, + "pcmcia-gpib", board)) { + dev_err(board->gpib_dev, "can't request IRQ %d\n", curr_dev->irq); + return -1; + } + ines_priv->irq = curr_dev->irq; + + return 0; +} + +static int ines_pcmcia_attach(struct gpib_board *board, const struct gpib_board_config *config) +{ + struct ines_priv *ines_priv; + int retval; + + retval = ines_common_pcmcia_attach(board); + if (retval < 0) + return retval; + + ines_priv = board->private_data; + ines_online(ines_priv, board, 0); + + return 0; +} + +static int ines_pcmcia_accel_attach(struct gpib_board *board, + const struct gpib_board_config *config) +{ + struct ines_priv *ines_priv; + int retval; + + retval = ines_common_pcmcia_attach(board); + if (retval < 0) + return retval; + + ines_priv = board->private_data; + ines_online(ines_priv, board, 1); + + return 0; +} + +static void ines_pcmcia_detach(struct gpib_board *board) +{ + struct ines_priv *ines_priv = board->private_data; + struct nec7210_priv *nec_priv; + + if (ines_priv) { + nec_priv = &ines_priv->nec7210_priv; + if (ines_priv->irq) + free_irq(ines_priv->irq, board); + if (nec_priv->iobase) { + nec7210_board_reset(nec_priv, board); + release_region(nec_priv->iobase, ines_pcmcia_iosize); + } + } + ines_free_private(board); +} + +#endif /* CONFIG_GPIB_PCMCIA */ + +static int __init ines_init_module(void) +{ + int ret; + + ret = pci_register_driver(&ines_pci_driver); + if (ret) { + pr_err("pci_register_driver failed: error = %d\n", ret); + return ret; + } + + ret = gpib_register_driver(&ines_pci_interface, THIS_MODULE); + if (ret) { + pr_err("gpib_register_driver failed: error = %d\n", ret); + goto err_pci; + } + + ret = gpib_register_driver(&ines_pci_unaccel_interface, THIS_MODULE); + if (ret) { + pr_err("gpib_register_driver failed: error = %d\n", ret); + goto err_pci_unaccel; + } + + ret = gpib_register_driver(&ines_pci_accel_interface, THIS_MODULE); + if (ret) { + pr_err("gpib_register_driver failed: error = %d\n", ret); + goto err_pci_accel; + } + + ret = gpib_register_driver(&ines_isa_interface, THIS_MODULE); + if (ret) { + pr_err("gpib_register_driver failed: error = %d\n", ret); + goto err_isa; + } + +#ifdef CONFIG_GPIB_PCMCIA + ret = gpib_register_driver(&ines_pcmcia_interface, THIS_MODULE); + if (ret) { + pr_err("gpib_register_driver failed: error = %d\n", ret); + goto err_pcmcia; + } + + ret = gpib_register_driver(&ines_pcmcia_unaccel_interface, THIS_MODULE); + if (ret) { + pr_err("gpib_register_driver failed: error = %d\n", ret); + goto err_pcmcia_unaccel; + } + + ret = gpib_register_driver(&ines_pcmcia_accel_interface, THIS_MODULE); + if (ret) { + pr_err("gpib_register_driver failed: error = %d\n", ret); + goto err_pcmcia_accel; + } + + ret = pcmcia_register_driver(&ines_gpib_cs_driver); + if (ret) { + pr_err("pcmcia_register_driver failed: error = %d\n", ret); + goto err_pcmcia_driver; + } +#endif + + return 0; + +#ifdef CONFIG_GPIB_PCMCIA +err_pcmcia_driver: + gpib_unregister_driver(&ines_pcmcia_accel_interface); +err_pcmcia_accel: + gpib_unregister_driver(&ines_pcmcia_unaccel_interface); +err_pcmcia_unaccel: + gpib_unregister_driver(&ines_pcmcia_interface); +err_pcmcia: +#endif + gpib_unregister_driver(&ines_isa_interface); +err_isa: + gpib_unregister_driver(&ines_pci_accel_interface); +err_pci_accel: + gpib_unregister_driver(&ines_pci_unaccel_interface); +err_pci_unaccel: + gpib_unregister_driver(&ines_pci_interface); +err_pci: + pci_unregister_driver(&ines_pci_driver); + + return ret; +} + +static void __exit ines_exit_module(void) +{ + gpib_unregister_driver(&ines_pci_interface); + gpib_unregister_driver(&ines_pci_unaccel_interface); + gpib_unregister_driver(&ines_pci_accel_interface); + gpib_unregister_driver(&ines_isa_interface); +#ifdef CONFIG_GPIB_PCMCIA + gpib_unregister_driver(&ines_pcmcia_interface); + gpib_unregister_driver(&ines_pcmcia_unaccel_interface); + gpib_unregister_driver(&ines_pcmcia_accel_interface); + ines_pcmcia_cleanup_module(); +#endif + + pci_unregister_driver(&ines_pci_driver); +} + +module_init(ines_init_module); +module_exit(ines_exit_module); diff --git a/drivers/gpib/lpvo_usb_gpib/Makefile b/drivers/gpib/lpvo_usb_gpib/Makefile new file mode 100644 index 000000000000..360553488e6d --- /dev/null +++ b/drivers/gpib/lpvo_usb_gpib/Makefile @@ -0,0 +1,3 @@ + +obj-$(CONFIG_GPIB_LPVO) += lpvo_usb_gpib.o + diff --git a/drivers/gpib/lpvo_usb_gpib/lpvo_usb_gpib.c b/drivers/gpib/lpvo_usb_gpib/lpvo_usb_gpib.c new file mode 100644 index 000000000000..dd68c4843490 --- /dev/null +++ b/drivers/gpib/lpvo_usb_gpib/lpvo_usb_gpib.c @@ -0,0 +1,2025 @@ +// SPDX-License-Identifier: GPL-2.0 + +/*************************************************************************** + * This code has been developed at the Department of Physics (University * + * of Florence, Italy) to support in linux-gpib the open usb-gpib adapter * + * implemented at the University of Ljubljana (lpvo.fe.uni-lj.si/gpib) * + * * + * copyright : (C) 2011 Marcello Carla' * + ***************************************************************************/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#define dev_fmt pr_fmt +#define NAME KBUILD_MODNAME + +/* base module includes */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gpibP.h" + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("GPIB driver for LPVO usb devices"); + +/* + * Table of devices that work with this driver. + * + * Currently, only one device is known to be used in the + * lpvo_usb_gpib adapter (FTDI 0403:6001). + * If your adapter uses a different chip, insert a line + * in the following table with proper , . + * + * To have your chip automatically handled by the driver, + * update files "/usr/local/etc/modprobe.d/lpvo_usb_gpib.conf" + * and /usr/local/etc/udev/rules.d/99-lpvo_usb_gpib.rules. + * + */ + +static const struct usb_device_id skel_table[] = { + { USB_DEVICE(0x0403, 0x6001) }, + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, skel_table); + +/* + * *** Diagnostics and Debug *** + * To enable the diagnostic and debug messages either compile with DEBUG set + * or control via the dynamic debug mechanisms. + * The module parameter "debug" controls the sending of debug messages to + * syslog. By default it is set to 0 + * debug = 0: only attach/detach messages are sent + * 1: every action is logged + * 2: extended logging; each single exchanged byte is documented + * (about twice the log volume of [1]) + * To switch debug level: + * At module loading: modprobe lpvo_usb_gpib debug={0,1,2} + * On the fly: echo {0,1,2} > /sys/modules/lpvo_usb_gpib/parameters/debug + */ + +static int debug; +module_param(debug, int, 0644); + +#define DIA_LOG(level, format, ...) \ + do { if (debug >= (level)) \ + dev_dbg(board->gpib_dev, format, ## __VA_ARGS__); } \ + while (0) + +#define WQT wait_queue_entry_t +#define WQH head +#define WQE entry + +/* standard and extended command sets of the usb-gpib adapter */ + +#define USB_GPIB_ON "\nIB\n" +#define USB_GPIB_OFF "\nIBO\n" +#define USB_GPIB_IBm0 "\nIBm0\n" /* do not assert REN with IFC */ +#define USB_GPIB_IBm1 "\nIBm1\n" /* assert REN with IFC */ +#define USB_GPIB_IBCL "\nIBZ\n" +#define USB_GPIB_STATUS "\nIBS\n" +#define USB_GPIB_READ "\nIB?\n" +#define USB_GPIB_READ_1 "\nIBB\n" +#define USB_GPIB_EOI "\nIBe0\n" +#define USB_GPIB_FTMO "\nIBf0\n" /* disable first byte timeout */ +#define USB_GPIB_TTMOZ "\nIBt0\n" /* disable byte timeout */ + +/* incomplete commands */ + +#define USB_GPIB_BTMO "\nIBt" /* set byte timeout */ +#define USB_GPIB_TTMO "\nIBT" /* set total timeout */ + +#define USB_GPIB_DEBUG_ON "\nIBDE\xAA\n" +#define USB_GPIB_SET_LISTEN "\nIBDT0\n" +#define USB_GPIB_SET_TALK "\nIBDT1\n" +#define USB_GPIB_SET_LINES "\nIBDC.\n" +#define USB_GPIB_SET_DATA "\nIBDM.\n" +#define USB_GPIB_READ_LINES "\nIBD?C\n" +#define USB_GPIB_READ_DATA "\nIBD?M\n" +#define USB_GPIB_READ_BUS "\nIBD??\n" + +/* command sequences */ + +#define USB_GPIB_UNTALK "\nIBC_\n" +#define USB_GPIB_UNLISTEN "\nIBC?\n" + +/* special characters used by the adapter */ + +#define DLE ('\020') +#define STX ('\02') +#define ETX ('\03') +#define ACK ('\06') +#define NODATA ('\03') +#define NODAV ('\011') + +#define IB_BUS_REN 0x01 +#define IB_BUS_IFC 0x02 +#define IB_BUS_NDAC 0x04 +#define IB_BUS_NRFD 0x08 +#define IB_BUS_DAV 0x10 +#define IB_BUS_EOI 0x20 +#define IB_BUS_ATN 0x40 +#define IB_BUS_SRQ 0x80 + +#define INBUF_SIZE 128 + +struct char_buf { /* used by one_char() routine */ + char *inbuf; + int last; + int nchar; +}; + +struct usb_gpib_priv { /* private data to the device */ + u8 eos; /* eos character */ + short eos_flags; /* eos mode */ + int timeout; /* current value for timeout */ + void *dev; /* the usb device private data structure */ +}; + +#define GPIB_DEV (((struct usb_gpib_priv *)board->private_data)->dev) + +static void show_status(struct gpib_board *board) +{ + DIA_LOG(2, "# - buffer_length %d\n", board->buffer_length); + DIA_LOG(2, "# - status %lx\n", board->status); + DIA_LOG(2, "# - use_count %d\n", board->use_count); + DIA_LOG(2, "# - pad %x\n", board->pad); + DIA_LOG(2, "# - sad %x\n", board->sad); + DIA_LOG(2, "# - timeout %d\n", board->usec_timeout); + DIA_LOG(2, "# - ppc %d\n", board->parallel_poll_configuration); + DIA_LOG(2, "# - t1delay %d\n", board->t1_nano_sec); + DIA_LOG(2, "# - online %d\n", board->online); + DIA_LOG(2, "# - autopoll %d\n", board->autospollers); + DIA_LOG(2, "# - autopoll task %p\n", board->autospoll_task); + DIA_LOG(2, "# - minor %d\n", board->minor); + DIA_LOG(2, "# - master %d\n", board->master); + DIA_LOG(2, "# - list %d\n", board->ist); +} + +/* + * GLOBAL VARIABLES: required for + * pairing among gpib minor and usb minor. + * MAX_DEV is the max number of usb-gpib adapters; free + * to change as you like, but no more than 32 + */ + +#define MAX_DEV 8 +static struct usb_interface *lpvo_usb_interfaces[MAX_DEV]; /* registered interfaces */ +static int usb_minors[MAX_DEV]; /* usb minors */ +static int assigned_usb_minors; /* mask of filled slots */ +static struct mutex minors_lock; /* operations on usb_minors are to be protected */ + +/* + * usb-skeleton prototypes + */ + +struct usb_skel; +static ssize_t skel_do_write(struct usb_skel *, const char *, size_t); +static ssize_t skel_do_read(struct usb_skel *, char *, size_t); +static int skel_do_open(struct gpib_board *, int); +static int skel_do_release(struct gpib_board *); + +/* + * usec_diff : take difference in MICROsec between two 'timespec' + * (unix time in sec and NANOsec) + */ + +static inline int usec_diff(struct timespec64 *a, struct timespec64 *b) +{ + return ((a->tv_sec - b->tv_sec) * 1000000 + + (a->tv_nsec - b->tv_nsec) / 1000); +} + +/* + * *** these routines are specific to the usb-gpib adapter *** + */ + +/** + * write_loop() - Send a byte sequence to the adapter + * + * @dev: the private device structure + * @msg: the byte sequence. + * @leng: the byte sequence length. + * + */ + +static int write_loop(void *dev, char *msg, int leng) +{ + return skel_do_write(dev, msg, leng); +} + +/** + * send_command() - Send a byte sequence and return a single byte reply. + * + * @board: the gpib_board_struct data area for this gpib interface + * @msg: the byte sequence. + * @leng: the byte sequence length; can be given as zero and is + * computed automatically, but if 'msg' contains a zero byte, + * it has to be given explicitly. + */ + +static int send_command(struct gpib_board *board, char *msg, int leng) +{ + char buffer[64]; + int nchar; + int retval; + struct timespec64 before, after; + + ktime_get_real_ts64 (&before); + + if (!leng) + leng = strlen(msg); + retval = write_loop(GPIB_DEV, msg, leng); + if (retval < 0) + return retval; + + nchar = skel_do_read(GPIB_DEV, buffer, 64); + + if (nchar < 0) { + dev_err(board->gpib_dev, " return from read: %d\n", nchar); + return nchar; + } else if (nchar != 1) { + dev_err(board->gpib_dev, " Irregular reply to command: %s\n", msg); + return -EIO; + } + ktime_get_real_ts64 (&after); + + DIA_LOG(1, "Sent %d - done %d us.\n", leng, usec_diff(&after, &before)); + + return buffer[0] & 0xff; +} + +/* + * set_control_line() - Set the value of a single gpib control line + * + * @board: the gpib_board_struct data area for this gpib interface + * @line: line mask + * @value: line new value (0/1) + */ + +static int set_control_line(struct gpib_board *board, int line, int value) +{ + char msg[] = USB_GPIB_SET_LINES; + int retval; + int leng = strlen(msg); + + DIA_LOG(1, "setting line %x to %x\n", line, value); + + retval = send_command(board, USB_GPIB_READ_LINES, 0); + + DIA_LOG(1, "old line values: %x\n", retval); + + if (retval == -EIO) + return retval; + + msg[leng - 2] = value ? (retval & ~line) : retval | line; + + retval = send_command(board, msg, 0); + + DIA_LOG(1, "operation result: %x\n", retval); + + return retval; +} + +/* + * one_char() - read one single byte from input buffer + * + * @board: the gpib_board_struct data area for this gpib interface + * @char_buf: the routine private data structure + */ + +static int one_char(struct gpib_board *board, struct char_buf *b) +{ + struct timespec64 before, after; + + if (b->nchar) { + DIA_LOG(2, "-> %x\n", b->inbuf[b->last - b->nchar]); + return b->inbuf[b->last - b->nchar--]; + } + ktime_get_real_ts64 (&before); + b->nchar = skel_do_read(GPIB_DEV, b->inbuf, INBUF_SIZE); + b->last = b->nchar; + ktime_get_real_ts64 (&after); + + DIA_LOG(2, "read %d bytes in %d usec\n", + b->nchar, usec_diff(&after, &before)); + + if (b->nchar > 0) { + DIA_LOG(2, "--> %x\n", b->inbuf[b->last - b->nchar]); + return b->inbuf[b->last - b->nchar--]; + } + return -EIO; +} + +/** + * set_timeout() - set single byte / total timeouts on the adapter + * + * @board: the gpib_board_struct data area for this gpib interface + * + * For sake of speed, the operation is performed only if it + * modifies the current (saved) value. Minimum allowed timeout + * is 30 ms (T30ms -> 8); timeout disable (TNONE -> 0) currently + * not supported. + */ + +static void set_timeout(struct gpib_board *board) +{ + int n, val; + char command[sizeof(USB_GPIB_TTMO) + 6]; + struct usb_gpib_priv *data = board->private_data; + + if (data->timeout == board->usec_timeout) + return; + + n = (board->usec_timeout + 32767) / 32768; + if (n < 2) + n = 2; + + DIA_LOG(1, "Set timeout to %d us -> %d\n", board->usec_timeout, n); + + sprintf(command, "%s%d\n", USB_GPIB_BTMO, n > 255 ? 255 : n); + val = send_command(board, command, 0); + + if (val == ACK) { + if (n > 65535) + n = 65535; + sprintf(command, "%s%d\n", USB_GPIB_TTMO, n); + val = send_command(board, command, 0); + } + + if (val != ACK) + dev_err(board->gpib_dev, "error in timeout set: <%s>\n", command); + else + data->timeout = board->usec_timeout; +} + +/* + * now the standard interface functions - attach and detach + */ + +/** + * usb_gpib_attach() - activate the usb-gpib converter board + * + * @board: the gpib_board_struct data area for this gpib interface + * @config: firmware data, if any (from gpib_config -I ) + * + * The channel name is ttyUSBn, with n=0 by default. Other values for n + * passed with gpib_config -b . + * + * In this routine I trust that when an error code is returned + * detach() will be called. Always. + */ + +static int usb_gpib_attach(struct gpib_board *board, const struct gpib_board_config *config) +{ + int retval, j; + u32 base = config->ibbase; + char *device_path; + int match; + struct usb_device *udev; + + DIA_LOG(0, "Board %p -t %s -m %d -a %p -u %d -l %d -b %d\n", + board, board->interface->name, board->minor, config->device_path, + config->pci_bus, config->pci_slot, base); + + board->private_data = NULL; /* to be sure - we can detach before setting */ + + /* identify device to be attached */ + + mutex_lock(&minors_lock); + + if (config->device_path) { + /* if config->device_path given, try that first */ + for (j = 0 ; j < MAX_DEV ; j++) { + if ((assigned_usb_minors & 1 << j) == 0) + continue; + udev = usb_get_dev(interface_to_usbdev(lpvo_usb_interfaces[j])); + device_path = kobject_get_path(&udev->dev.kobj, GFP_KERNEL); + match = gpib_match_device_path(&lpvo_usb_interfaces[j]->dev, + config->device_path); + DIA_LOG(1, "dev. %d: minor %d path: %s --> %d\n", j, + lpvo_usb_interfaces[j]->minor, device_path, match); + kfree(device_path); + if (match) + break; + } + } else if (config->pci_bus != -1 && config->pci_slot != -1) { + /* second: look for bus and slot */ + for (j = 0 ; j < MAX_DEV ; j++) { + if ((assigned_usb_minors & 1 << j) == 0) + continue; + udev = usb_get_dev(interface_to_usbdev(lpvo_usb_interfaces[j])); + DIA_LOG(1, "dev. %d: bus %d -> %d dev: %d -> %d\n", j, + udev->bus->busnum, config->pci_bus, udev->devnum, config->pci_slot); + if (config->pci_bus == udev->bus->busnum && + config->pci_slot == udev->devnum) + break; + } + } else { /* last chance: usb_minor, given as ibbase */ + for (j = 0 ; j < MAX_DEV ; j++) { + if (usb_minors[j] == base && assigned_usb_minors & 1 << j) + break; + } + } + mutex_unlock(&minors_lock); + + if (j == MAX_DEV) { + dev_err(board->gpib_dev, "Requested device is not registered.\n"); + return -EIO; + } + + board->private_data = kzalloc(sizeof(struct usb_gpib_priv), GFP_KERNEL); + if (!board->private_data) + return -ENOMEM; + + retval = skel_do_open(board, usb_minors[j]); + + DIA_LOG(1, "Skel open: %d\n", retval); + + if (retval) { + dev_err(board->gpib_dev, "skel open failed.\n"); + kfree(board->private_data); + board->private_data = NULL; + return -ENODEV; + } + + show_status(board); + + retval = send_command(board, USB_GPIB_ON, 0); + DIA_LOG(1, "USB_GPIB_ON returns %x\n", retval); + if (retval != ACK) + return -EIO; + + /* + * We must setup debug mode because we need the extended instruction + * set to cope with the Core (gpib_common) point of view + */ + + retval = send_command(board, USB_GPIB_DEBUG_ON, 0); + DIA_LOG(1, "USB_GPIB_DEBUG_ON returns %x\n", retval); + if (retval != ACK) + return -EIO; + + /* + * We must keep REN off after an IFC because so it is + * assumed by the Core + */ + + retval = send_command(board, USB_GPIB_IBm0, 0); + DIA_LOG(1, "USB_GPIB_IBm0 returns %x\n", retval); + if (retval != ACK) + return -EIO; + + retval = set_control_line(board, IB_BUS_REN, 0); + if (retval != ACK) + return -EIO; + + retval = send_command(board, USB_GPIB_FTMO, 0); + DIA_LOG(1, "USB_GPIB_FTMO returns %x\n", retval); + if (retval != ACK) + return -EIO; + + show_status(board); + DIA_LOG(0, "attached\n"); + return 0; +} + +/** + * usb_gpib_detach() - deactivate the usb-gpib converter board + * + * @board: the gpib_board data area for this gpib interface + * + */ + +static void usb_gpib_detach(struct gpib_board *board) +{ + int retval; + + show_status(board); + + DIA_LOG(0, "detaching\n"); + + if (board->private_data) { + if (GPIB_DEV) { + write_loop(GPIB_DEV, USB_GPIB_OFF, strlen(USB_GPIB_OFF)); + msleep(100); + DIA_LOG(1, "%s", "GPIB off\n"); + retval = skel_do_release(board); + DIA_LOG(1, "skel release -> %d\n", retval); + } + kfree(board->private_data); + board->private_data = NULL; + } + + DIA_LOG(0, "detached\n"); +} + +/* + * Other functions follow in alphabetical order + */ +/* command */ +static int usb_gpib_command(struct gpib_board *board, + u8 *buffer, + size_t length, + size_t *bytes_written) +{ + int i, retval; + char command[6] = "IBc.\n"; + + DIA_LOG(1, "enter %p\n", board); + + set_timeout(board); + + *bytes_written = 0; + for (i = 0 ; i < length ; i++) { + command[3] = buffer[i]; + retval = send_command(board, command, 5); + DIA_LOG(2, "%d ==> %x %x\n", i, buffer[i], retval); + if (retval != 0x06) + return retval; + ++(*bytes_written); + } + return 0; +} + +/** + * usb_gpib_disable_eos() - Disable END on eos byte (END on EOI only) + * + * @board: the gpib_board data area for this gpib interface + * + * With the lpvo adapter eos can only be handled via software. + * Cannot do nothing here, but remember for future use. + */ + +static void usb_gpib_disable_eos(struct gpib_board *board) +{ + ((struct usb_gpib_priv *)board->private_data)->eos_flags &= ~REOS; + DIA_LOG(1, "done: %x\n", + ((struct usb_gpib_priv *)board->private_data)->eos_flags); +} + +/** + * usb_gpib_enable_eos() - Enable END for reads when eos byte is received. + * + * @board: the gpib_board data area for this gpib interface + * @eos_byte: the 'eos' byte + * @compare_8_bits: if zero ignore eigthth bit when comparing + * + */ + +static int usb_gpib_enable_eos(struct gpib_board *board, + u8 eos_byte, + int compare_8_bits) +{ + struct usb_gpib_priv *pd = (struct usb_gpib_priv *)board->private_data; + + DIA_LOG(1, "enter with %x\n", eos_byte); + pd->eos = eos_byte; + pd->eos_flags = REOS; + if (compare_8_bits) + pd->eos_flags |= BIN; + return 0; +} + +/** + * usb_gpib_go_to_standby() - De-assert ATN + * + * @board: the gpib_board data area for this gpib interface + */ + +static int usb_gpib_go_to_standby(struct gpib_board *board) +{ + int retval = set_control_line(board, IB_BUS_ATN, 0); + + DIA_LOG(1, "done with %x\n", retval); + + if (retval == ACK) + return 0; + return -EIO; +} + +/** + * usb_gpib_interface_clear() - Assert or de-assert IFC + * + * @board: the gpib_board data area for this gpib interface + * @assert: 1: assert IFC; 0: de-assert IFC + * + * Currently on the assert request we issue the lpvo IBZ + * command that cycles IFC low for 100 usec, then we ignore + * the de-assert request. + */ + +static void usb_gpib_interface_clear(struct gpib_board *board, int assert) +{ + int retval = 0; + + DIA_LOG(1, "enter with %d\n", assert); + + if (assert) { + retval = send_command(board, USB_GPIB_IBCL, 0); + + set_bit(CIC_NUM, &board->status); + } + + DIA_LOG(1, "done with %d %d\n", assert, retval); +} + +/** + * usb_gpib_line_status() - Read the status of the bus lines. + * + * @board: the gpib_board data area for this gpib interface + * + * We can read all lines. + */ +static int usb_gpib_line_status(const struct gpib_board *board) +{ + int buffer; + int line_status = VALID_ALL; /* all lines will be read */ + struct list_head *p, *q; + WQT *item; + unsigned long flags; + int sleep = 0; + + DIA_LOG(1, "%s\n", "request"); + + /* + * if we are on the wait queue (board->wait), do not hurry + * reading status line; instead, pause a little + */ + + spin_lock_irqsave((spinlock_t *)&board->wait.lock, flags); + q = (struct list_head *)&board->wait.WQH; + list_for_each(p, q) { + item = container_of(p, WQT, WQE); + if (item->private == current) { + sleep = 20; + break; + } + /* pid is: ((struct task_struct *) item->private)->pid); */ + } + spin_unlock_irqrestore((spinlock_t *)&board->wait.lock, flags); + if (sleep) { + DIA_LOG(1, "we are on the wait queue - sleep %d ms\n", sleep); + msleep(sleep); + } + + buffer = send_command((struct gpib_board *)board, USB_GPIB_STATUS, 0); + + if (buffer < 0) { + dev_err(board->gpib_dev, "line status read failed with %d\n", buffer); + return -1; + } + + if ((buffer & 0x01) == 0) + line_status |= BUS_REN; + if ((buffer & 0x02) == 0) + line_status |= BUS_IFC; + if ((buffer & 0x04) == 0) + line_status |= BUS_NDAC; + if ((buffer & 0x08) == 0) + line_status |= BUS_NRFD; + if ((buffer & 0x10) == 0) + line_status |= BUS_DAV; + if ((buffer & 0x20) == 0) + line_status |= BUS_EOI; + if ((buffer & 0x40) == 0) + line_status |= BUS_ATN; + if ((buffer & 0x80) == 0) + line_status |= BUS_SRQ; + + DIA_LOG(1, "done with %x %x\n", buffer, line_status); + + return line_status; +} + +/* parallel_poll */ + +static int usb_gpib_parallel_poll(struct gpib_board *board, u8 *result) +{ + /* + * request parallel poll asserting ATN | EOI; + * we suppose ATN already asserted + */ + + int retval; + + DIA_LOG(1, "enter %p\n", board); + + retval = set_control_line(board, IB_BUS_EOI, 1); + if (retval != ACK) + return -EIO; + + *result = send_command(board, USB_GPIB_READ_DATA, 0); + + DIA_LOG(1, "done with %x\n", *result); + + retval = set_control_line(board, IB_BUS_EOI, 0); + if (retval != 0x06) + return -EIO; + + return 0; +} + +/* read */ + +static int usb_gpib_read(struct gpib_board *board, + u8 *buffer, + size_t length, + int *end, + size_t *bytes_read) +{ +#define MAX_READ_EXCESS 16384 + + struct char_buf b = {NULL, 0}; + + int retval; + char c, nc; + int ic; + struct timespec64 before, after; + int read_count = MAX_READ_EXCESS; + struct usb_gpib_priv *pd = (struct usb_gpib_priv *)board->private_data; + + DIA_LOG(1, "enter %p -> %zu\n", board, length); + + *bytes_read = 0; /* by default, things go wrong */ + *end = 0; + + set_timeout(board); + + /* single byte read has a special handling */ + + if (length == 1) { + char inbuf[2] = {0, 0}; + + /* read a single character */ + + ktime_get_real_ts64 (&before); + + retval = write_loop(GPIB_DEV, USB_GPIB_READ_1, strlen(USB_GPIB_READ_1)); + if (retval < 0) + return retval; + + retval = skel_do_read(GPIB_DEV, inbuf, 1); + retval += skel_do_read(GPIB_DEV, inbuf + 1, 1); + + ktime_get_real_ts64 (&after); + + DIA_LOG(1, "single read: %x %x %x in %d\n", retval, + inbuf[0], inbuf[1], + usec_diff(&after, &before)); + + /* good char / last char? */ + + if (retval == 2 && inbuf[1] == ACK) { + buffer[0] = inbuf[0]; + *bytes_read = 1; + return 0; + } + if (retval < 2) + return -EIO; + else + return -ETIME; + } + + /* allocate buffer for multibyte read */ + + b.inbuf = kmalloc(INBUF_SIZE, GFP_KERNEL); + if (!b.inbuf) + return -ENOMEM; + + /* send read command and check sequence */ + + retval = write_loop(GPIB_DEV, USB_GPIB_READ, strlen(USB_GPIB_READ)); + if (retval < 0) + goto read_return; + + if (one_char(board, &b) != DLE || one_char(board, &b) != STX) { + dev_err(board->gpib_dev, "wrong sequence\n"); + retval = -EIO; + goto read_return; + } + + /* get data flow */ + + while (1) { + ic = one_char(board, &b); + if (ic == -EIO) { + retval = -EIO; + goto read_return; + } + c = ic; + + if (c == DLE) + nc = one_char(board, &b); + if (c != DLE || nc == DLE) { + /* data byte - store into buffer */ + + if (*bytes_read == length) + break; /* data overflow */ + if (c == DLE) + c = nc; + buffer[(*bytes_read)++] = c; + if (c == pd->eos) { + *end = 1; + break; + } + + } else { + /* we are in the closing sequence */ + c = nc; + if (c == ETX) { + c = one_char(board, &b); + if (c == ACK) { + *end = 1; + retval = 0; + goto read_return; + } else { + dev_err(board->gpib_dev, "wrong end of message %x", c); + retval = -ETIME; + goto read_return; + } + } else { + dev_err(board->gpib_dev, "lone in stream"); + retval = -EIO; + goto read_return; + } + } + } + + /* we had a data overflow - flush excess data */ + + while (read_count--) { + if (one_char(board, &b) != DLE) + continue; + c = one_char(board, &b); + if (c == DLE) + continue; + if (c == ETX) { + c = one_char(board, &b); + if (c == ACK) { + if (MAX_READ_EXCESS - read_count > 1) + dev_dbg(board->gpib_dev, "small buffer - maybe some data lost"); + retval = 0; + goto read_return; + } + break; + } + } + + dev_err(board->gpib_dev, "no input end - board in odd state\n"); + retval = -EIO; + +read_return: + kfree(b.inbuf); + + DIA_LOG(1, "done with byte/status: %d %x %d\n", (int)*bytes_read, retval, *end); + + if (retval == 0 || retval == -ETIME) { + if (send_command(board, USB_GPIB_UNTALK, sizeof(USB_GPIB_UNTALK)) == 0x06) + return retval; + return -EIO; + } + + return retval; +} + +/* remote_enable */ + +static void usb_gpib_remote_enable(struct gpib_board *board, int enable) +{ + int retval; + + retval = set_control_line(board, IB_BUS_REN, enable ? 1 : 0); + if (retval != ACK) + dev_err(board->gpib_dev, "could not set REN line: %x\n", retval); + + DIA_LOG(1, "done with %x\n", retval); +} + +/* request_system_control */ + +static int usb_gpib_request_system_control(struct gpib_board *board, int request_control) +{ + if (!request_control) + return -EINVAL; + + DIA_LOG(1, "done with %d -> %lx\n", request_control, board->status); + return 0; +} + +/* take_control */ +/* beware: the sync flag is ignored; what is its real meaning? */ + +static int usb_gpib_take_control(struct gpib_board *board, int sync) +{ + int retval; + + retval = set_control_line(board, IB_BUS_ATN, 1); + + DIA_LOG(1, "done with %d %x\n", sync, retval); + + if (retval == ACK) + return 0; + return -EIO; +} + +/* update_status */ + +static unsigned int usb_gpib_update_status(struct gpib_board *board, + unsigned int clear_mask) +{ + /* There is nothing we can do here, I guess */ + + board->status &= ~clear_mask; + + DIA_LOG(1, "done with %x %lx\n", clear_mask, board->status); + + return board->status; +} + +/* write */ +/* beware: DLE characters are not escaped - can only send ASCII data */ + +static int usb_gpib_write(struct gpib_board *board, + u8 *buffer, + size_t length, + int send_eoi, + size_t *bytes_written) +{ + int retval; + char *msg; + + DIA_LOG(1, "enter %p -> %zu\n", board, length); + + set_timeout(board); + + msg = kmalloc(length + 8, GFP_KERNEL); + if (!msg) + return -ENOMEM; + + memcpy(msg, "\nIB\020\002", 5); + memcpy(msg + 5, buffer, length); + memcpy(msg + 5 + length, "\020\003\n", 3); + + retval = send_command(board, msg, length + 8); + kfree(msg); + + DIA_LOG(1, "<%.*s> -> %x\n", (int)length, buffer, retval); + + if (retval != ACK) + return -EPIPE; + + *bytes_written = length; + + if (send_command(board, USB_GPIB_UNLISTEN, sizeof(USB_GPIB_UNLISTEN)) != 0x06) + return -EPIPE; + + return length; +} + +/* + * *** following functions not implemented yet *** + */ + +/* parallel_poll configure */ + +static void usb_gpib_parallel_poll_configure(struct gpib_board *board, + u8 configuration) +{ +} + +/* parallel_poll_response */ + +static void usb_gpib_parallel_poll_response(struct gpib_board *board, int ist) +{ +} + +/* primary_address */ + +static int usb_gpib_primary_address(struct gpib_board *board, unsigned int address) +{ + return 0; +} + +/* return_to_local */ + +static void usb_gpib_return_to_local(struct gpib_board *board) +{ +} + +/* secondary_address */ + +static int usb_gpib_secondary_address(struct gpib_board *board, + unsigned int address, + int enable) +{ + return 0; +} + +/* serial_poll_response */ + +static void usb_gpib_serial_poll_response(struct gpib_board *board, u8 status) +{ +} + +/* serial_poll_status */ + +static u8 usb_gpib_serial_poll_status(struct gpib_board *board) +{ + return 0; +} + +/* t1_delay */ + +static int usb_gpib_t1_delay(struct gpib_board *board, unsigned int nano_sec) +{ + return 0; +} + +/* + * *** module dispatch table and init/exit functions *** + */ + +static struct gpib_interface usb_gpib_interface = { + .name = NAME, + .attach = usb_gpib_attach, + .detach = usb_gpib_detach, + .read = usb_gpib_read, + .write = usb_gpib_write, + .command = usb_gpib_command, + .take_control = usb_gpib_take_control, + .go_to_standby = usb_gpib_go_to_standby, + .request_system_control = usb_gpib_request_system_control, + .interface_clear = usb_gpib_interface_clear, + .remote_enable = usb_gpib_remote_enable, + .enable_eos = usb_gpib_enable_eos, + .disable_eos = usb_gpib_disable_eos, + .parallel_poll = usb_gpib_parallel_poll, + .parallel_poll_configure = usb_gpib_parallel_poll_configure, + .parallel_poll_response = usb_gpib_parallel_poll_response, + .local_parallel_poll_mode = NULL, // XXX + .line_status = usb_gpib_line_status, + .update_status = usb_gpib_update_status, + .primary_address = usb_gpib_primary_address, + .secondary_address = usb_gpib_secondary_address, + .serial_poll_response = usb_gpib_serial_poll_response, + .serial_poll_status = usb_gpib_serial_poll_status, + .t1_delay = usb_gpib_t1_delay, + .return_to_local = usb_gpib_return_to_local, + .skip_check_for_command_acceptors = 1 +}; + +/* + * usb_gpib_init_module(), usb_gpib_exit_module() + * + * This functions are called every time a new device is detected + * and registered or is removed and unregistered. + * We must take note of created and destroyed usb minors to be used + * when usb_gpib_attach() and usb_gpib_detach() will be called on + * request by gpib_config. + */ + +static int usb_gpib_init_module(struct usb_interface *interface) +{ + int j, mask, rv; + + rv = mutex_lock_interruptible(&minors_lock); + if (rv < 0) + return rv; + + if (!assigned_usb_minors) { + rv = gpib_register_driver(&usb_gpib_interface, THIS_MODULE); + if (rv) { + pr_err("gpib_register_driver failed: error = %d\n", rv); + goto exit; + } + } else { + /* + * check if minor is already registered - maybe useless, but if + * it happens the code is inconsistent somewhere + */ + + for (j = 0 ; j < MAX_DEV ; j++) { + if (usb_minors[j] == interface->minor && assigned_usb_minors & 1 << j) { + pr_err("CODE BUG: USB minor %d registered at %d.\n", + interface->minor, j); + rv = -1; + goto exit; + } + } + } + + /* find a free slot */ + + for (j = 0 ; j < MAX_DEV ; j++) { + mask = 1 << j; + if ((assigned_usb_minors & mask) == 0) { + usb_minors[j] = interface->minor; + lpvo_usb_interfaces[j] = interface; + assigned_usb_minors |= mask; + rv = 0; + goto exit; + } + } + pr_err("No slot available for interface %p minor %d\n", interface, interface->minor); + rv = -1; + +exit: + mutex_unlock(&minors_lock); + return rv; +} + +static void usb_gpib_exit_module(int minor) +{ + int j; + + mutex_lock(&minors_lock); + for (j = 0 ; j < MAX_DEV ; j++) { + if (usb_minors[j] == minor && assigned_usb_minors & 1 << j) { + assigned_usb_minors &= ~(1 << j); + usb_minors[j] = -1; + if (assigned_usb_minors == 0) + gpib_unregister_driver(&usb_gpib_interface); + goto exit; + } + } + pr_err("CODE BUG: USB minor %d not found.\n", minor); + +exit: + mutex_unlock(&minors_lock); +} + +/* + * Default latency time (16 msec) is too long. + * We must use 1 msec (best); anyhow, no more than 5 msec. + * + * Defines and function taken and modified from the kernel tree + * (see ftdi_sio.h and ftdi_sio.c). + */ + +#define FTDI_SIO_SET_LATENCY_TIMER 9 /* Set the latency timer */ +#define FTDI_SIO_SET_LATENCY_TIMER_REQUEST FTDI_SIO_SET_LATENCY_TIMER +#define FTDI_SIO_SET_LATENCY_TIMER_REQUEST_TYPE 0x40 +#define WDR_TIMEOUT 5000 /* default urb timeout */ +#define WDR_SHORT_TIMEOUT 1000 /* shorter urb timeout */ + +#define LATENCY_TIMER 1 /* use a small latency timer: 1 ... 5 msec */ +#define LATENCY_CHANNEL 0 /* channel selection in multichannel devices */ +static int write_latency_timer(struct usb_device *udev) +{ + int rv = usb_control_msg(udev, + usb_sndctrlpipe(udev, 0), + FTDI_SIO_SET_LATENCY_TIMER_REQUEST, + FTDI_SIO_SET_LATENCY_TIMER_REQUEST_TYPE, + LATENCY_TIMER, LATENCY_CHANNEL, + NULL, 0, WDR_TIMEOUT); + if (rv < 0) + dev_err(&udev->dev, "Unable to write latency timer: %i\n", rv); + return rv; +} + +/***************************************************************************** + * * + * The following code is a modified version of the USB Skeleton driver * + * written by Greg Kroah-Hartman and available in the kernel tree. * + * * + * Functions skel_open() and skel_release() have been rewritten and named * + * skel_do_open() and skel_do_release() to process the attach and detach * + * requests coming from gpib_config. * + * * + * Functions skel_read() and skel_write() have been split into a * + * skel_do_read() and skel_do_write(), that cover the kernel stuff of read * + * and write operations, and the original skel_read() and skel_write(), * + * that handle communication with user space and call their _do_ companion. * + * * + * Only the _do_ versions are used by the lpvo_usb_gpib driver; other ones * + * can be (optionally) maintained in the compilation to have direct access * + * to a gpib controller for debug and diagnostics. * + * * + * To avoid collisions in names, devices in user space have been renamed * + * lpvo_raw1, lpvo_raw2 .... and the usb driver has been renamed with the * + * gpib module name. * + * * + *****************************************************************************/ + +/* + * USB Skeleton driver - 2.2 + * + * Copyright (C) 2001-2004 Greg Kroah-Hartman (greg@kroah.com) + * + * This driver is based on the 2.6.3 version of drivers/usb/usb-skeleton.c + * but has been rewritten to be easier to read and use. + */ + +#include +#include +#include +#include + +/* Get a minor range for your devices from the usb maintainer */ +#define USB_SKEL_MINOR_BASE 192 + +/* private defines */ + +#define MAX_TRANSFER (PAGE_SIZE - 512) +/* + * MAX_TRANSFER is chosen so that the VM is not stressed by + * allocations > PAGE_SIZE and the number of packets in a page + * is an integer 512 is the largest possible packet on EHCI + */ + +#define WRITES_IN_FLIGHT 1 /* we do not want more than one pending write */ +#define USER_DEVICE 1 /* compile for device(s) in user space */ + +/* Structure to hold all of our device specific stuff */ +struct usb_skel { + struct usb_device *udev; /* the usb device for this device */ + struct usb_interface *interface; /* the interface for this device */ + struct semaphore limit_sem; /* limiting the number of writes in progress */ + struct usb_anchor submitted; /* in case need to retract our submissions */ + struct urb *bulk_in_urb; /* the urb to read data with */ + unsigned char *bulk_in_buffer; /* the buffer to receive data */ + size_t bulk_in_size; /* the size of the receive buffer */ + size_t bulk_in_filled; /* number of bytes in the buffer */ + size_t bulk_in_copied; /* already copied to user space */ + __u8 bulk_in_endpoint_addr; /* the address of the bulk in endpoint */ + __u8 bulk_out_endpoint_addr; /* the address of the bulk out endpoint */ + int errors; /* the last request tanked */ + bool ongoing_read; /* a read is going on */ + spinlock_t err_lock; /* lock for errors */ + struct kref kref; + struct mutex io_mutex; /* synchronize I/O with disconnect */ + wait_queue_head_t bulk_in_wait; /* to wait for an ongoing read */ +}; + +#define to_skel_dev(d) container_of(d, struct usb_skel, kref) + +static struct usb_driver skel_driver; +static void skel_draw_down(struct usb_skel *dev); + +static void skel_delete(struct kref *kref) +{ + struct usb_skel *dev = to_skel_dev(kref); + + usb_free_urb(dev->bulk_in_urb); + usb_put_dev(dev->udev); + kfree(dev->bulk_in_buffer); + kfree(dev); +} + +/* + * skel_do_open() - to be called by usb_gpib_attach + */ + +static int skel_do_open(struct gpib_board *board, int subminor) +{ + struct usb_skel *dev; + struct usb_interface *interface; + int retval = 0; + + interface = usb_find_interface(&skel_driver, subminor); + if (!interface) { + dev_err(board->gpib_dev, "can't find device for minor %d\n", subminor); + retval = -ENODEV; + goto exit; + } + + dev = usb_get_intfdata(interface); + if (!dev) { + retval = -ENODEV; + goto exit; + } + + retval = usb_autopm_get_interface(interface); + if (retval) + goto exit; + + /* increment our usage count for the device */ + kref_get(&dev->kref); + + /* save our object in the file's private structure */ + GPIB_DEV = dev; + +exit: + return retval; +} + +/* + * skel_do_release() - to be called by usb_gpib_detach + */ + +static int skel_do_release(struct gpib_board *board) +{ + struct usb_skel *dev; + + dev = GPIB_DEV; + if (!dev) + return -ENODEV; + + /* allow the device to be autosuspended */ + mutex_lock(&dev->io_mutex); + if (dev->interface) + usb_autopm_put_interface(dev->interface); + mutex_unlock(&dev->io_mutex); + + /* decrement the count on our device */ + kref_put(&dev->kref, skel_delete); + return 0; +} + +/* + * read functions + */ + +static void skel_read_bulk_callback(struct urb *urb) +{ + struct usb_skel *dev; + unsigned long flags; + + dev = urb->context; + + spin_lock_irqsave(&dev->err_lock, flags); + /* sync/async unlink faults aren't errors */ + if (urb->status) { + if (!(urb->status == -ENOENT || + urb->status == -ECONNRESET || + urb->status == -ESHUTDOWN)) + dev_err(&dev->interface->dev, "nonzero read bulk status received: %d\n", + urb->status); + + dev->errors = urb->status; + } else { + dev->bulk_in_filled = urb->actual_length; + } + dev->ongoing_read = 0; + spin_unlock_irqrestore(&dev->err_lock, flags); + + wake_up_interruptible(&dev->bulk_in_wait); +} + +static int skel_do_read_io(struct usb_skel *dev, size_t count) +{ + int rv; + + /* prepare a read */ + usb_fill_bulk_urb(dev->bulk_in_urb, + dev->udev, + usb_rcvbulkpipe(dev->udev, + dev->bulk_in_endpoint_addr), + dev->bulk_in_buffer, + min(dev->bulk_in_size, count), + skel_read_bulk_callback, + dev); + /* tell everybody to leave the URB alone */ + spin_lock_irq(&dev->err_lock); + dev->ongoing_read = 1; + spin_unlock_irq(&dev->err_lock); + + /* submit bulk in urb, which means no data to deliver */ + dev->bulk_in_filled = 0; + dev->bulk_in_copied = 0; + + /* do it */ + rv = usb_submit_urb(dev->bulk_in_urb, GFP_KERNEL); + if (rv < 0) { + dev_err(&dev->interface->dev, "failed submitting read urb, error %d\n", rv); + rv = (rv == -ENOMEM) ? rv : -EIO; + spin_lock_irq(&dev->err_lock); + dev->ongoing_read = 0; + spin_unlock_irq(&dev->err_lock); + } + + return rv; +} + +/* + * skel_do_read() - read operations from lpvo_usb_gpib + */ + +static ssize_t skel_do_read(struct usb_skel *dev, char *buffer, size_t count) +{ + int rv; + bool ongoing_io; + + /* if we cannot read at all, return EOF */ + + if (!dev->bulk_in_urb || !count) + return 0; + +restart: /* added to comply with ftdi timeout technique */ + + /* no concurrent readers */ + + rv = mutex_lock_interruptible(&dev->io_mutex); + if (rv < 0) + return rv; + + if (!dev->interface) { /* disconnect() was called */ + rv = -ENODEV; + goto exit; + } + +retry: + /* if IO is under way, we must not touch things */ + spin_lock_irq(&dev->err_lock); + ongoing_io = dev->ongoing_read; + spin_unlock_irq(&dev->err_lock); + + if (ongoing_io) { +// /* nonblocking IO shall not wait */ +// /* no file, no O_NONBLOCK; maybe provide when from user space */ +// if (file->f_flags & O_NONBLOCK) { +// rv = -EAGAIN; +// goto exit; +// } + + /* + * IO may take forever + * hence wait in an interruptible state + */ + rv = wait_event_interruptible(dev->bulk_in_wait, (!dev->ongoing_read)); + if (rv < 0) + goto exit; + } + + /* errors must be reported */ + rv = dev->errors; + if (rv < 0) { + /* any error is reported once */ + dev->errors = 0; + /* to preserve notifications about reset */ + rv = (rv == -EPIPE) ? rv : -EIO; + /* report it */ + goto exit; + } + + /* + * if the buffer is filled we may satisfy the read + * else we need to start IO + */ + + if (dev->bulk_in_filled) { + /* we had read data */ + + size_t available = dev->bulk_in_filled - dev->bulk_in_copied; +// size_t chunk = min(available, count); /* compute chunk later */ + size_t chunk; + + if (!available) { + /* + * all data has been used + * actual IO needs to be done + */ + /* + * it seems that requests for less than dev->bulk_in_size + * are not accepted + */ + rv = skel_do_read_io(dev, dev->bulk_in_size); + if (rv < 0) + goto exit; + else + goto retry; + } + + /* + * data is available - chunk tells us how much shall be copied + */ + + /* + * Condition dev->bulk_in_copied > 0 maybe will never happen. In case, + * signal the event and copy using the original procedure, i.e., copy + * first two bytes also + */ + + if (dev->bulk_in_copied) { + chunk = min(available, count); + memcpy(buffer, dev->bulk_in_buffer + dev->bulk_in_copied, chunk); + rv = chunk; + dev->bulk_in_copied += chunk; + + /* copy discarding first two bytes that contain ftdi chip status */ + + } else { + /* account for two bytes to be discarded */ + chunk = min(available, count + 2); + if (chunk < 2) { + dev_err(&dev->udev->dev, "BAD READ - chunk: %zu\n", chunk); + rv = -EIO; + goto exit; + } + + memcpy(buffer, dev->bulk_in_buffer + 2, chunk - 2); + rv = chunk; + dev->bulk_in_copied += chunk; + } + + /* + * if we are asked for more than we have, + * we start IO but don't wait + * + * No, no read ahead allowed; if the case, more data will be + * asked for by the lpvo_usb_gpib layer. + */ +// if (available < count) +// skel_do_read_io(dev, dev->bulk_in_size); + } else { + /* no data in the buffer */ + rv = skel_do_read_io(dev, dev->bulk_in_size); + if (rv < 0) + goto exit; + else + goto retry; + } +exit: + mutex_unlock(&dev->io_mutex); + if (rv == 2) + goto restart; /* ftdi chip returns two status bytes after a latency anyhow */ + + if (rv > 0) + return rv - 2; /* account for 2 discarded bytes in a valid buffer */ + return rv; +} + +/* + * write functions + */ + +static void skel_write_bulk_callback(struct urb *urb) +{ + struct usb_skel *dev; + unsigned long flags; + + dev = urb->context; + + /* sync/async unlink faults aren't errors */ + if (urb->status) { + if (!(urb->status == -ENOENT || + urb->status == -ECONNRESET || + urb->status == -ESHUTDOWN)) + dev_err(&dev->interface->dev, + "nonzero write bulk status received: %d\n", urb->status); + + spin_lock_irqsave(&dev->err_lock, flags); + dev->errors = urb->status; + spin_unlock_irqrestore(&dev->err_lock, flags); + } + + /* free up our allocated buffer */ + usb_free_coherent(urb->dev, urb->transfer_buffer_length, + urb->transfer_buffer, urb->transfer_dma); + up(&dev->limit_sem); +} + +/* + * skel_do_write() - write operations from lpvo_usb_gpib + */ + +static ssize_t skel_do_write(struct usb_skel *dev, const char *buffer, size_t count) +{ + int retval = 0; + struct urb *urb = NULL; + char *buf = NULL; + size_t writesize = min_t(size_t, count, (size_t)MAX_TRANSFER); + + /* verify that we actually have some data to write */ + if (count == 0) + goto exit; + + /* + * limit the number of URBs in flight to stop a user from using up all + * RAM + */ + /* Only one URB is used, because we can't have a pending write() and go on */ + +// if (!(file->f_flags & O_NONBLOCK)) { /* no NONBLOCK provided */ + if (down_interruptible(&dev->limit_sem)) { + retval = -ERESTARTSYS; + goto exit; + } +// } else { +// if (down_trylock(&dev->limit_sem)) { +// retval = -EAGAIN; +// goto exit; +// } +// } + + spin_lock_irq(&dev->err_lock); + retval = dev->errors; + if (retval < 0) { + /* any error is reported once */ + dev->errors = 0; + /* to preserve notifications about reset */ + retval = (retval == -EPIPE) ? retval : -EIO; + } + spin_unlock_irq(&dev->err_lock); + if (retval < 0) + goto error; + + /* create a urb, and a buffer for it, and copy the data to the urb */ + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + retval = -ENOMEM; + goto error; + } + + buf = usb_alloc_coherent(dev->udev, writesize, GFP_KERNEL, + &urb->transfer_dma); + if (!buf) { + retval = -ENOMEM; + goto error; + } + + memcpy(buf, buffer, count); + + /* this lock makes sure we don't submit URBs to gone devices */ + mutex_lock(&dev->io_mutex); + if (!dev->interface) { /* disconnect() was called */ + mutex_unlock(&dev->io_mutex); + retval = -ENODEV; + goto error; + } + + /* initialize the urb properly */ + usb_fill_bulk_urb(urb, dev->udev, + usb_sndbulkpipe(dev->udev, dev->bulk_out_endpoint_addr), + buf, writesize, skel_write_bulk_callback, dev); + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + usb_anchor_urb(urb, &dev->submitted); + + /* send the data out the bulk port */ + retval = usb_submit_urb(urb, GFP_KERNEL); + mutex_unlock(&dev->io_mutex); + if (retval) { + dev_err(&dev->interface->dev, "failed submitting write urb, error %d\n", retval); + goto error_unanchor; + } + + /* + * release our reference to this urb, the USB core will eventually free + * it entirely + */ + usb_free_urb(urb); + + return writesize; + +error_unanchor: + usb_unanchor_urb(urb); +error: + if (urb) { + usb_free_coherent(dev->udev, writesize, buf, urb->transfer_dma); + usb_free_urb(urb); + } + up(&dev->limit_sem); + +exit: + return retval; +} + +/* + * services for the user space devices + */ + +#if USER_DEVICE /* conditional compilation of user space device */ + +static int skel_flush(struct file *file, fl_owner_t id) +{ + struct usb_skel *dev; + int res; + + dev = file->private_data; + if (!dev) + return -ENODEV; + + /* wait for io to stop */ + mutex_lock(&dev->io_mutex); + skel_draw_down(dev); + + /* read out errors, leave subsequent opens a clean slate */ + spin_lock_irq(&dev->err_lock); + res = dev->errors ? (dev->errors == -EPIPE ? -EPIPE : -EIO) : 0; + dev->errors = 0; + spin_unlock_irq(&dev->err_lock); + + mutex_unlock(&dev->io_mutex); + + return res; +} + +static int skel_open(struct inode *inode, struct file *file) +{ + struct usb_skel *dev; + struct usb_interface *interface; + int subminor; + int retval = 0; + + subminor = iminor(inode); + + interface = usb_find_interface(&skel_driver, subminor); + if (!interface) { + pr_err("can't find device for minor %d\n", subminor); + retval = -ENODEV; + goto exit; + } + + dev = usb_get_intfdata(interface); + if (!dev) { + retval = -ENODEV; + goto exit; + } + + retval = usb_autopm_get_interface(interface); + if (retval) + goto exit; + + /* increment our usage count for the device */ + kref_get(&dev->kref); + + /* save our object in the file's private structure */ + file->private_data = dev; + +exit: + return retval; +} + +static int skel_release(struct inode *inode, struct file *file) +{ + struct usb_skel *dev; + + dev = file->private_data; + if (!dev) + return -ENODEV; + + /* allow the device to be autosuspended */ + mutex_lock(&dev->io_mutex); + if (dev->interface) + usb_autopm_put_interface(dev->interface); + mutex_unlock(&dev->io_mutex); + + /* decrement the count on our device */ + kref_put(&dev->kref, skel_delete); + return 0; +} + +/* + * user space access to read function + */ + +static ssize_t skel_read(struct file *file, char __user *buffer, size_t count, + loff_t *ppos) +{ + struct usb_skel *dev; + char *buf; + ssize_t rv; + + dev = file->private_data; + + buf = kmalloc(count, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + rv = skel_do_read(dev, buf, count); + + if (rv > 0) { + if (copy_to_user(buffer, buf, rv)) { + kfree(buf); + return -EFAULT; + } + } + kfree(buf); + return rv; +} + +/* + * user space access to write function + */ + +static ssize_t skel_write(struct file *file, const char __user *user_buffer, + size_t count, loff_t *ppos) +{ + struct usb_skel *dev; + char *buf; + ssize_t rv; + + dev = file->private_data; + + buf = kmalloc(count, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + if (copy_from_user(buf, user_buffer, count)) { + kfree(buf); + return -EFAULT; + } + + rv = skel_do_write(dev, buf, count); + kfree(buf); + return rv; +} +#endif + +static const struct file_operations skel_fops = { + .owner = THIS_MODULE, +#if USER_DEVICE + .read = skel_read, + .write = skel_write, + .open = skel_open, + .release = skel_release, + .flush = skel_flush, + .llseek = noop_llseek, +#endif +}; + +/* + * usb class driver info in order to get a minor number from the usb core, + * and to have the device registered with the driver core + */ +#if USER_DEVICE +static struct usb_class_driver skel_class = { + .name = "lpvo_raw%d", + .fops = &skel_fops, + .minor_base = USB_SKEL_MINOR_BASE, +}; +#endif + +static int skel_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_skel *dev; + struct usb_endpoint_descriptor *bulk_in, *bulk_out; + int retval; + char *device_path; + + mutex_init(&minors_lock); /* required for handling minor numbers table */ + + /* allocate memory for our device state and initialize it */ + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + kref_init(&dev->kref); + sema_init(&dev->limit_sem, WRITES_IN_FLIGHT); + mutex_init(&dev->io_mutex); + spin_lock_init(&dev->err_lock); + init_usb_anchor(&dev->submitted); + init_waitqueue_head(&dev->bulk_in_wait); + + dev->udev = usb_get_dev(interface_to_usbdev(interface)); + dev->interface = interface; + + /* set up the endpoint information */ + /* use only the first bulk-in and bulk-out endpoints */ + retval = usb_find_common_endpoints(interface->cur_altsetting, + &bulk_in, &bulk_out, NULL, NULL); + if (retval) { + dev_err(&interface->dev, + "Could not find both bulk-in and bulk-out endpoints\n"); + goto error; + } + + dev->bulk_in_size = usb_endpoint_maxp(bulk_in); + dev->bulk_in_endpoint_addr = bulk_in->bEndpointAddress; + dev->bulk_in_buffer = kmalloc(dev->bulk_in_size, GFP_KERNEL); + if (!dev->bulk_in_buffer) { + retval = -ENOMEM; + goto error; + } + dev->bulk_in_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->bulk_in_urb) { + retval = -ENOMEM; + goto error; + } + + dev->bulk_out_endpoint_addr = bulk_out->bEndpointAddress; + + /* save our data pointer in this interface device */ + usb_set_intfdata(interface, dev); + + /* let the world know */ + + device_path = kobject_get_path(&dev->udev->dev.kobj, GFP_KERNEL); + dev_dbg(&interface->dev, "New lpvo_usb_device -> bus: %d dev: %d path: %s\n", + dev->udev->bus->busnum, dev->udev->devnum, device_path); + kfree(device_path); + +#if USER_DEVICE + /* we can register the device now, as it is ready */ + retval = usb_register_dev(interface, &skel_class); + if (retval) { + /* something prevented us from registering this driver */ + dev_err(&interface->dev, + "Not able to get a minor for this device.\n"); + usb_set_intfdata(interface, NULL); + goto error; + } +#endif + + write_latency_timer(dev->udev); /* adjust the latency timer */ + + usb_gpib_init_module(interface); /* last, init the lpvo for this minor */ + + return 0; + +error: + /* this frees allocated memory */ + kref_put(&dev->kref, skel_delete); + + return retval; +} + +static void skel_disconnect(struct usb_interface *interface) +{ + struct usb_skel *dev; + int minor = interface->minor; + + usb_gpib_exit_module(minor); /* first, disactivate the lpvo */ + + dev = usb_get_intfdata(interface); + usb_set_intfdata(interface, NULL); + +#if USER_DEVICE + /* give back our minor */ + usb_deregister_dev(interface, &skel_class); +#endif + + /* prevent more I/O from starting */ + mutex_lock(&dev->io_mutex); + dev->interface = NULL; + mutex_unlock(&dev->io_mutex); + + usb_kill_anchored_urbs(&dev->submitted); + + /* decrement our usage count */ + kref_put(&dev->kref, skel_delete); +} + +static void skel_draw_down(struct usb_skel *dev) +{ + int time; + + time = usb_wait_anchor_empty_timeout(&dev->submitted, 1000); + if (!time) + usb_kill_anchored_urbs(&dev->submitted); + usb_kill_urb(dev->bulk_in_urb); +} + +static int skel_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct usb_skel *dev = usb_get_intfdata(intf); + + if (!dev) + return 0; + skel_draw_down(dev); + return 0; +} + +static int skel_resume(struct usb_interface *intf) +{ + return 0; +} + +static int skel_pre_reset(struct usb_interface *intf) +{ + struct usb_skel *dev = usb_get_intfdata(intf); + + mutex_lock(&dev->io_mutex); + skel_draw_down(dev); + + return 0; +} + +static int skel_post_reset(struct usb_interface *intf) +{ + struct usb_skel *dev = usb_get_intfdata(intf); + + /* we are sure no URBs are active - no locking needed */ + dev->errors = -EPIPE; + mutex_unlock(&dev->io_mutex); + + return 0; +} + +static struct usb_driver skel_driver = { + .name = NAME, + .probe = skel_probe, + .disconnect = skel_disconnect, + .suspend = skel_suspend, + .resume = skel_resume, + .pre_reset = skel_pre_reset, + .post_reset = skel_post_reset, + .id_table = skel_table, + .supports_autosuspend = 1, +}; + +module_usb_driver(skel_driver); diff --git a/drivers/gpib/nec7210/Makefile b/drivers/gpib/nec7210/Makefile new file mode 100644 index 000000000000..64330f2e89d1 --- /dev/null +++ b/drivers/gpib/nec7210/Makefile @@ -0,0 +1,4 @@ + +obj-$(CONFIG_GPIB_NEC7210) += nec7210.o + + diff --git a/drivers/gpib/nec7210/board.h b/drivers/gpib/nec7210/board.h new file mode 100644 index 000000000000..ac3fe38ade57 --- /dev/null +++ b/drivers/gpib/nec7210/board.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/*************************************************************************** + * copyright : (C) 2001, 2002 by Frank Mori Hess + ***************************************************************************/ + +#ifndef _GPIB_PCIIA_BOARD_H +#define _GPIB_PCIIA_BOARD_H + +#include "gpibP.h" +#include +#include +#include +#include + +#include "nec7210.h" + +#endif //_GPIB_PCIIA_BOARD_H + diff --git a/drivers/gpib/nec7210/nec7210.c b/drivers/gpib/nec7210/nec7210.c new file mode 100644 index 000000000000..bbf39367f5e4 --- /dev/null +++ b/drivers/gpib/nec7210/nec7210.c @@ -0,0 +1,1121 @@ +// SPDX-License-Identifier: GPL-2.0 + +/*************************************************************************** + * copyright : (C) 2001, 2002 by Frank Mori Hess + ***************************************************************************/ + +#define dev_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include "board.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("GPIB library code for NEC uPD7210"); + +int nec7210_enable_eos(struct gpib_board *board, struct nec7210_priv *priv, u8 eos_byte, + int compare_8_bits) +{ + write_byte(priv, eos_byte, EOSR); + priv->auxa_bits |= HR_REOS; + if (compare_8_bits) + priv->auxa_bits |= HR_BIN; + else + priv->auxa_bits &= ~HR_BIN; + write_byte(priv, priv->auxa_bits, AUXMR); + return 0; +} +EXPORT_SYMBOL(nec7210_enable_eos); + +void nec7210_disable_eos(struct gpib_board *board, struct nec7210_priv *priv) +{ + priv->auxa_bits &= ~HR_REOS; + write_byte(priv, priv->auxa_bits, AUXMR); +} +EXPORT_SYMBOL(nec7210_disable_eos); + +int nec7210_parallel_poll(struct gpib_board *board, struct nec7210_priv *priv, u8 *result) +{ + int ret; + + clear_bit(COMMAND_READY_BN, &priv->state); + + // execute parallel poll + write_byte(priv, AUX_EPP, AUXMR); + // wait for result FIXME: support timeouts + ret = wait_event_interruptible(board->wait, test_bit(COMMAND_READY_BN, &priv->state)); + if (ret) { + dev_dbg(board->gpib_dev, "gpib: parallel poll interrupted\n"); + return -ERESTARTSYS; + } + *result = read_byte(priv, CPTR); + + return 0; +} +EXPORT_SYMBOL(nec7210_parallel_poll); + +void nec7210_parallel_poll_configure(struct gpib_board *board, + struct nec7210_priv *priv, unsigned int configuration) +{ + write_byte(priv, PPR | configuration, AUXMR); +} +EXPORT_SYMBOL(nec7210_parallel_poll_configure); + +void nec7210_parallel_poll_response(struct gpib_board *board, struct nec7210_priv *priv, int ist) +{ + if (ist) + write_byte(priv, AUX_SPPF, AUXMR); + else + write_byte(priv, AUX_CPPF, AUXMR); +} +EXPORT_SYMBOL(nec7210_parallel_poll_response); +/* + * This is really only adequate for chips that do a 488.2 style reqt/reqf + * based on bit 6 of the SPMR (see chapter 11.3.3 of 488.2). For simpler chips that simply + * set rsv directly based on bit 6, we either need to do more hardware setup to expose + * the 488.2 capability (for example with NI chips), or we need to implement the + * 488.2 set srv state machine in the driver (if that is even viable). + */ +void nec7210_serial_poll_response(struct gpib_board *board, + struct nec7210_priv *priv, u8 status) +{ + unsigned long flags; + + spin_lock_irqsave(&board->spinlock, flags); + if (status & request_service_bit) { + priv->srq_pending = 1; + clear_bit(SPOLL_NUM, &board->status); + + } else { + priv->srq_pending = 0; + } + write_byte(priv, status, SPMR); + spin_unlock_irqrestore(&board->spinlock, flags); +} +EXPORT_SYMBOL(nec7210_serial_poll_response); + +u8 nec7210_serial_poll_status(struct gpib_board *board, struct nec7210_priv *priv) +{ + return read_byte(priv, SPSR); +} +EXPORT_SYMBOL(nec7210_serial_poll_status); + +int nec7210_primary_address(const struct gpib_board *board, struct nec7210_priv *priv, + unsigned int address) +{ + // put primary address in address0 + write_byte(priv, address & ADDRESS_MASK, ADR); + return 0; +} +EXPORT_SYMBOL(nec7210_primary_address); + +int nec7210_secondary_address(const struct gpib_board *board, struct nec7210_priv *priv, + unsigned int address, int enable) +{ + if (enable) { + // put secondary address in address1 + write_byte(priv, HR_ARS | (address & ADDRESS_MASK), ADR); + // go to address mode 2 + priv->reg_bits[ADMR] &= ~HR_ADM0; + priv->reg_bits[ADMR] |= HR_ADM1; + } else { + // disable address1 register + write_byte(priv, HR_ARS | HR_DT | HR_DL, ADR); + // go to address mode 1 + priv->reg_bits[ADMR] |= HR_ADM0; + priv->reg_bits[ADMR] &= ~HR_ADM1; + } + write_byte(priv, priv->reg_bits[ADMR], ADMR); + return 0; +} +EXPORT_SYMBOL(nec7210_secondary_address); + +static void update_talker_state(struct nec7210_priv *priv, unsigned int address_status_bits) +{ + if ((address_status_bits & HR_TA)) { + if ((address_status_bits & HR_NATN)) { + if (address_status_bits & HR_SPMS) + priv->talker_state = serial_poll_active; + else + priv->talker_state = talker_active; + } else { + priv->talker_state = talker_addressed; + } + } else { + priv->talker_state = talker_idle; + } +} + +static void update_listener_state(struct nec7210_priv *priv, unsigned int address_status_bits) +{ + if (address_status_bits & HR_LA) { + if ((address_status_bits & HR_NATN)) + priv->listener_state = listener_active; + else + priv->listener_state = listener_addressed; + } else { + priv->listener_state = listener_idle; + } +} + +unsigned int nec7210_update_status_nolock(struct gpib_board *board, struct nec7210_priv *priv) +{ + int address_status_bits; + u8 spoll_status; + + if (!priv) + return 0; + + address_status_bits = read_byte(priv, ADSR); + if (address_status_bits & HR_CIC) + set_bit(CIC_NUM, &board->status); + else + clear_bit(CIC_NUM, &board->status); + // check for talker/listener addressed + update_talker_state(priv, address_status_bits); + if (priv->talker_state == talker_active || priv->talker_state == talker_addressed) + set_bit(TACS_NUM, &board->status); + else + clear_bit(TACS_NUM, &board->status); + update_listener_state(priv, address_status_bits); + if (priv->listener_state == listener_active || + priv->listener_state == listener_addressed) + set_bit(LACS_NUM, &board->status); + else + clear_bit(LACS_NUM, &board->status); + if (address_status_bits & HR_NATN) + clear_bit(ATN_NUM, &board->status); + else + set_bit(ATN_NUM, &board->status); + spoll_status = nec7210_serial_poll_status(board, priv); + if (priv->srq_pending && (spoll_status & request_service_bit) == 0) { + priv->srq_pending = 0; + set_bit(SPOLL_NUM, &board->status); + } + + /* + * we rely on the interrupt handler to set the + * rest of the status bits + */ + + return board->status; +} +EXPORT_SYMBOL(nec7210_update_status_nolock); + +unsigned int nec7210_update_status(struct gpib_board *board, struct nec7210_priv *priv, + unsigned int clear_mask) +{ + unsigned long flags; + unsigned int retval; + + spin_lock_irqsave(&board->spinlock, flags); + board->status &= ~clear_mask; + retval = nec7210_update_status_nolock(board, priv); + spin_unlock_irqrestore(&board->spinlock, flags); + + return retval; +} +EXPORT_SYMBOL(nec7210_update_status); + +unsigned int nec7210_set_reg_bits(struct nec7210_priv *priv, unsigned int reg, + unsigned int mask, unsigned int bits) +{ + priv->reg_bits[reg] &= ~mask; + priv->reg_bits[reg] |= mask & bits; + write_byte(priv, priv->reg_bits[reg], reg); + return priv->reg_bits[reg]; +} +EXPORT_SYMBOL(nec7210_set_reg_bits); + +void nec7210_set_handshake_mode(struct gpib_board *board, struct nec7210_priv *priv, int mode) +{ + unsigned long flags; + + mode &= HR_HANDSHAKE_MASK; + + spin_lock_irqsave(&board->spinlock, flags); + if ((priv->auxa_bits & HR_HANDSHAKE_MASK) != mode) { + priv->auxa_bits &= ~HR_HANDSHAKE_MASK; + priv->auxa_bits |= mode; + write_byte(priv, priv->auxa_bits, AUXMR); + } + spin_unlock_irqrestore(&board->spinlock, flags); +} +EXPORT_SYMBOL(nec7210_set_handshake_mode); + +u8 nec7210_read_data_in(struct gpib_board *board, struct nec7210_priv *priv, int *end) +{ + unsigned long flags; + u8 data; + + spin_lock_irqsave(&board->spinlock, flags); + data = read_byte(priv, DIR); + clear_bit(READ_READY_BN, &priv->state); + if (test_and_clear_bit(RECEIVED_END_BN, &priv->state)) + *end = 1; + else + *end = 0; + spin_unlock_irqrestore(&board->spinlock, flags); + + return data; +} +EXPORT_SYMBOL(nec7210_read_data_in); + +int nec7210_take_control(struct gpib_board *board, struct nec7210_priv *priv, int syncronous) +{ + int i; + const int timeout = 100; + int retval = 0; + unsigned int adsr_bits = 0; + + if (syncronous) + write_byte(priv, AUX_TCS, AUXMR); + else + write_byte(priv, AUX_TCA, AUXMR); + // busy wait until ATN is asserted + for (i = 0; i < timeout; i++) { + adsr_bits = read_byte(priv, ADSR); + if ((adsr_bits & HR_NATN) == 0) + break; + udelay(1); + } + if (i == timeout) + return -ETIMEDOUT; + + clear_bit(WRITE_READY_BN, &priv->state); + + return retval; +} +EXPORT_SYMBOL(nec7210_take_control); + +int nec7210_go_to_standby(struct gpib_board *board, struct nec7210_priv *priv) +{ + int i; + const int timeout = 1000; + unsigned int adsr_bits = 0; + int retval = 0; + + write_byte(priv, AUX_GTS, AUXMR); + // busy wait until ATN is released + for (i = 0; i < timeout; i++) { + adsr_bits = read_byte(priv, ADSR); + if (adsr_bits & HR_NATN) + break; + udelay(1); + } + // if busy wait has failed, try sleeping + if (i == timeout) { + for (i = 0; i < HZ; i++) { + set_current_state(TASK_INTERRUPTIBLE); + if (schedule_timeout(1)) + return -ERESTARTSYS; + adsr_bits = read_byte(priv, ADSR); + if (adsr_bits & HR_NATN) + break; + } + if (i == HZ) + return -ETIMEDOUT; + } + + clear_bit(COMMAND_READY_BN, &priv->state); + return retval; +} +EXPORT_SYMBOL(nec7210_go_to_standby); + +int nec7210_request_system_control(struct gpib_board *board, struct nec7210_priv *priv, + int request_control) +{ + if (request_control == 0) { + write_byte(priv, AUX_CREN, AUXMR); + write_byte(priv, AUX_CIFC, AUXMR); + write_byte(priv, AUX_DSC, AUXMR); + } + return 0; +} +EXPORT_SYMBOL(nec7210_request_system_control); + +void nec7210_interface_clear(struct gpib_board *board, struct nec7210_priv *priv, int assert) +{ + if (assert) + write_byte(priv, AUX_SIFC, AUXMR); + else + write_byte(priv, AUX_CIFC, AUXMR); +} +EXPORT_SYMBOL(nec7210_interface_clear); + +void nec7210_remote_enable(struct gpib_board *board, struct nec7210_priv *priv, int enable) +{ + if (enable) + write_byte(priv, AUX_SREN, AUXMR); + else + write_byte(priv, AUX_CREN, AUXMR); +} +EXPORT_SYMBOL(nec7210_remote_enable); + +void nec7210_release_rfd_holdoff(struct gpib_board *board, struct nec7210_priv *priv) +{ + unsigned long flags; + + spin_lock_irqsave(&board->spinlock, flags); + if (test_bit(RFD_HOLDOFF_BN, &priv->state) && + test_bit(READ_READY_BN, &priv->state) == 0) { + write_byte(priv, AUX_FH, AUXMR); + clear_bit(RFD_HOLDOFF_BN, &priv->state); + } + spin_unlock_irqrestore(&board->spinlock, flags); +} +EXPORT_SYMBOL(nec7210_release_rfd_holdoff); + +int nec7210_t1_delay(struct gpib_board *board, struct nec7210_priv *priv, + unsigned int nano_sec) +{ + unsigned int retval; + + if (nano_sec <= 500) { + priv->auxb_bits |= HR_TRI; + retval = 500; + } else { + priv->auxb_bits &= ~HR_TRI; + retval = 2000; + } + write_byte(priv, priv->auxb_bits, AUXMR); + + return retval; +} +EXPORT_SYMBOL(nec7210_t1_delay); + +void nec7210_return_to_local(const struct gpib_board *board, struct nec7210_priv *priv) +{ + write_byte(priv, AUX_RTL, AUXMR); +} +EXPORT_SYMBOL(nec7210_return_to_local); + +static inline short nec7210_atn_has_changed(struct gpib_board *board, struct nec7210_priv *priv) +{ + short address_status_bits = read_byte(priv, ADSR); + + if (address_status_bits & HR_NATN) { + if (test_bit(ATN_NUM, &board->status)) + return 1; + else + return 0; + } else { + if (test_bit(ATN_NUM, &board->status)) + return 0; + else + return 1; + } + return -1; +} + +int nec7210_command(struct gpib_board *board, struct nec7210_priv *priv, u8 + *buffer, size_t length, size_t *bytes_written) +{ + int retval = 0; + unsigned long flags; + + *bytes_written = 0; + + clear_bit(BUS_ERROR_BN, &priv->state); + + while (*bytes_written < length) { + if (wait_event_interruptible(board->wait, + test_bit(COMMAND_READY_BN, &priv->state) || + test_bit(BUS_ERROR_BN, &priv->state) || + test_bit(TIMO_NUM, &board->status))) { + dev_dbg(board->gpib_dev, "command wait interrupted\n"); + retval = -ERESTARTSYS; + break; + } + if (test_bit(TIMO_NUM, &board->status)) + break; + if (test_and_clear_bit(BUS_ERROR_BN, &priv->state)) + break; + spin_lock_irqsave(&board->spinlock, flags); + clear_bit(COMMAND_READY_BN, &priv->state); + write_byte(priv, buffer[*bytes_written], CDOR); + spin_unlock_irqrestore(&board->spinlock, flags); + + ++(*bytes_written); + + if (need_resched()) + schedule(); + } + // wait for last byte to get sent + if (wait_event_interruptible(board->wait, test_bit(COMMAND_READY_BN, &priv->state) || + test_bit(BUS_ERROR_BN, &priv->state) || + test_bit(TIMO_NUM, &board->status))) + retval = -ERESTARTSYS; + + if (test_bit(TIMO_NUM, &board->status)) + retval = -ETIMEDOUT; + + if (test_and_clear_bit(BUS_ERROR_BN, &priv->state)) + retval = -EIO; + + return retval; +} +EXPORT_SYMBOL(nec7210_command); + +static int pio_read(struct gpib_board *board, struct nec7210_priv *priv, u8 *buffer, + size_t length, int *end, size_t *bytes_read) +{ + ssize_t retval = 0; + + *bytes_read = 0; + *end = 0; + + while (*bytes_read < length) { + if (wait_event_interruptible(board->wait, + test_bit(READ_READY_BN, &priv->state) || + test_bit(DEV_CLEAR_BN, &priv->state) || + test_bit(TIMO_NUM, &board->status))) { + retval = -ERESTARTSYS; + break; + } + if (test_bit(READ_READY_BN, &priv->state)) { + if (*bytes_read == 0) { + /* + * We set the handshake mode here because we know + * no new bytes will arrive (it has already arrived + * and is awaiting being read out of the chip) while we are changing + * modes. This ensures we can reliably keep track + * of the holdoff state. + */ + nec7210_set_handshake_mode(board, priv, HR_HLDA); + } + buffer[(*bytes_read)++] = nec7210_read_data_in(board, priv, end); + if (*end) + break; + } + if (test_bit(TIMO_NUM, &board->status)) { + retval = -ETIMEDOUT; + break; + } + if (test_bit(DEV_CLEAR_BN, &priv->state)) { + retval = -EINTR; + break; + } + + if (*bytes_read < length) + nec7210_release_rfd_holdoff(board, priv); + + if (need_resched()) + schedule(); + } + return retval; +} + +#ifdef NEC_DMA +static ssize_t __dma_read(struct gpib_board *board, struct nec7210_priv *priv, size_t length) +{ + ssize_t retval = 0; + size_t count = 0; + unsigned long flags, dma_irq_flags; + + if (length == 0) + return 0; + + spin_lock_irqsave(&board->spinlock, flags); + + dma_irq_flags = claim_dma_lock(); + disable_dma(priv->dma_channel); + /* program dma controller */ + clear_dma_ff(priv->dma_channel); + set_dma_count(priv->dma_channel, length); + set_dma_addr(priv->dma_channel, priv->dma_buffer_addr); + set_dma_mode(priv->dma_channel, DMA_MODE_READ); + release_dma_lock(dma_irq_flags); + + enable_dma(priv->dma_channel); + + set_bit(DMA_READ_IN_PROGRESS_BN, &priv->state); + clear_bit(READ_READY_BN, &priv->state); + + // enable nec7210 dma + nec7210_set_reg_bits(priv, IMR2, HR_DMAI, HR_DMAI); + + spin_unlock_irqrestore(&board->spinlock, flags); + + // wait for data to transfer + if (wait_event_interruptible(board->wait, + test_bit(DMA_READ_IN_PROGRESS_BN, &priv->state) == 0 || + test_bit(DEV_CLEAR_BN, &priv->state) || + test_bit(TIMO_NUM, &board->status))) + retval = -ERESTARTSYS; + + if (test_bit(TIMO_NUM, &board->status)) + retval = -ETIMEDOUT; + if (test_bit(DEV_CLEAR_BN, &priv->state)) + retval = -EINTR; + + // disable nec7210 dma + nec7210_set_reg_bits(priv, IMR2, HR_DMAI, 0); + + // record how many bytes we transferred + flags = claim_dma_lock(); + clear_dma_ff(priv->dma_channel); + disable_dma(priv->dma_channel); + count += length - get_dma_residue(priv->dma_channel); + release_dma_lock(flags); + + return retval ? retval : count; +} + +static ssize_t dma_read(struct gpib_board *board, struct nec7210_priv *priv, u8 *buffer, + size_t length) +{ + size_t remain = length; + size_t transfer_size; + ssize_t retval = 0; + + while (remain > 0) { + transfer_size = (priv->dma_buffer_length < remain) ? + priv->dma_buffer_length : remain; + retval = __dma_read(board, priv, transfer_size); + if (retval < 0) + break; + memcpy(buffer, priv->dma_buffer, transfer_size); + remain -= retval; + buffer += retval; + if (test_bit(RECEIVED_END_BN, &priv->state)) + break; + } + + if (retval < 0) + return retval; + + return length - remain; +} +#endif + +int nec7210_read(struct gpib_board *board, struct nec7210_priv *priv, u8 *buffer, + size_t length, int *end, size_t *bytes_read) +{ + ssize_t retval = 0; + + *end = 0; + *bytes_read = 0; + + if (length == 0) + return 0; + + clear_bit(DEV_CLEAR_BN, &priv->state); // XXX wrong + + nec7210_release_rfd_holdoff(board, priv); + + retval = pio_read(board, priv, buffer, length, end, bytes_read); + + return retval; +} +EXPORT_SYMBOL(nec7210_read); + +static int pio_write_wait(struct gpib_board *board, struct nec7210_priv *priv, + short wake_on_lacs, short wake_on_atn, short wake_on_bus_error) +{ + // wait until byte is ready to be sent + if (wait_event_interruptible(board->wait, + (test_bit(TACS_NUM, &board->status) && + test_bit(WRITE_READY_BN, &priv->state)) || + test_bit(DEV_CLEAR_BN, &priv->state) || + (wake_on_bus_error && test_bit(BUS_ERROR_BN, &priv->state)) || + (wake_on_lacs && test_bit(LACS_NUM, &board->status)) || + (wake_on_atn && test_bit(ATN_NUM, &board->status)) || + test_bit(TIMO_NUM, &board->status))) + return -ERESTARTSYS; + + if (test_bit(TIMO_NUM, &board->status)) + return -ETIMEDOUT; + + if (test_bit(DEV_CLEAR_BN, &priv->state)) + return -EINTR; + + if (wake_on_bus_error && test_and_clear_bit(BUS_ERROR_BN, &priv->state)) + return -EIO; + + return 0; +} + +static int pio_write(struct gpib_board *board, struct nec7210_priv *priv, u8 *buffer, + size_t length, size_t *bytes_written) +{ + size_t last_count = 0; + ssize_t retval = 0; + unsigned long flags; + const int max_bus_errors = (length > 1000) ? length : 1000; + int bus_error_count = 0; + *bytes_written = 0; + + clear_bit(BUS_ERROR_BN, &priv->state); + + while (*bytes_written < length) { + if (need_resched()) + schedule(); + + retval = pio_write_wait(board, priv, 0, 0, priv->type == NEC7210); + if (retval == -EIO) { + /* resend last byte on bus error */ + *bytes_written = last_count; + /* + * we can get unrecoverable bus errors, + * so give up after a while + */ + bus_error_count++; + if (bus_error_count > max_bus_errors) + return retval; + continue; + } else { + if (retval < 0) + return retval; + } + spin_lock_irqsave(&board->spinlock, flags); + clear_bit(BUS_ERROR_BN, &priv->state); + clear_bit(WRITE_READY_BN, &priv->state); + last_count = *bytes_written; + write_byte(priv, buffer[(*bytes_written)++], CDOR); + spin_unlock_irqrestore(&board->spinlock, flags); + } + retval = pio_write_wait(board, priv, 1, 1, priv->type == NEC7210); + return retval; +} + +#ifdef NEC_DMA +static ssize_t __dma_write(struct gpib_board *board, struct nec7210_priv *priv, dma_addr_t address, + size_t length) +{ + unsigned long flags, dma_irq_flags; + int residue = 0; + int retval = 0; + + spin_lock_irqsave(&board->spinlock, flags); + + /* program dma controller */ + dma_irq_flags = claim_dma_lock(); + disable_dma(priv->dma_channel); + clear_dma_ff(priv->dma_channel); + set_dma_count(priv->dma_channel, length); + set_dma_addr(priv->dma_channel, address); + set_dma_mode(priv->dma_channel, DMA_MODE_WRITE); + enable_dma(priv->dma_channel); + release_dma_lock(dma_irq_flags); + + // enable board's dma for output + nec7210_set_reg_bits(priv, IMR2, HR_DMAO, HR_DMAO); + + clear_bit(WRITE_READY_BN, &priv->state); + set_bit(DMA_WRITE_IN_PROGRESS_BN, &priv->state); + + spin_unlock_irqrestore(&board->spinlock, flags); + + // suspend until message is sent + if (wait_event_interruptible(board->wait, + test_bit(DMA_WRITE_IN_PROGRESS_BN, &priv->state) == 0 || + test_bit(BUS_ERROR_BN, &priv->state) || + test_bit(DEV_CLEAR_BN, &priv->state) || + test_bit(TIMO_NUM, &board->status))) + retval = -ERESTARTSYS; + + if (test_bit(TIMO_NUM, &board->status)) + retval = -ETIMEDOUT; + if (test_and_clear_bit(DEV_CLEAR_BN, &priv->state)) + retval = -EINTR; + if (test_and_clear_bit(BUS_ERROR_BN, &priv->state)) + retval = -EIO; + + // disable board's dma + nec7210_set_reg_bits(priv, IMR2, HR_DMAO, 0); + + dma_irq_flags = claim_dma_lock(); + clear_dma_ff(priv->dma_channel); + disable_dma(priv->dma_channel); + residue = get_dma_residue(priv->dma_channel); + release_dma_lock(dma_irq_flags); + + if (residue) + retval = -EPIPE; + + return retval ? retval : length; +} + +static ssize_t dma_write(struct gpib_board *board, struct nec7210_priv *priv, u8 *buffer, + size_t length) +{ + size_t remain = length; + size_t transfer_size; + ssize_t retval = 0; + + while (remain > 0) { + transfer_size = (priv->dma_buffer_length < remain) ? + priv->dma_buffer_length : remain; + memcpy(priv->dma_buffer, buffer, transfer_size); + retval = __dma_write(board, priv, priv->dma_buffer_addr, transfer_size); + if (retval < 0) + break; + remain -= retval; + buffer += retval; + } + + if (retval < 0) + return retval; + + return length - remain; +} +#endif +int nec7210_write(struct gpib_board *board, struct nec7210_priv *priv, + u8 *buffer, size_t length, int send_eoi, + size_t *bytes_written) +{ + int retval = 0; + + *bytes_written = 0; + + clear_bit(DEV_CLEAR_BN, &priv->state); // XXX + + if (send_eoi) + length-- ; // save the last byte for sending EOI + + if (length > 0) { + // isa dma transfer + if (0 /*priv->dma_channel*/) { +/* + * dma writes are unreliable since they can't recover from bus errors + * (which happen when ATN is asserted in the middle of a write) + */ +#ifdef NEC_DMA + retval = dma_write(board, priv, buffer, length); + if (retval < 0) + return retval; + count += retval; +#endif + } else { // PIO transfer + size_t num_bytes; + + retval = pio_write(board, priv, buffer, length, &num_bytes); + + *bytes_written += num_bytes; + if (retval < 0) + return retval; + } + } + if (send_eoi) { + size_t num_bytes; + + /* + * We need to wait to make sure we will immediately be able to write the data byte + * into the chip before sending the associated AUX_SEOI command. This is really + * only needed for length==1 since otherwise the earlier calls to pio_write + * will have dont the wait already. + */ + retval = pio_write_wait(board, priv, 0, 0, priv->type == NEC7210); + if (retval < 0) + return retval; + /*send EOI */ + write_byte(priv, AUX_SEOI, AUXMR); + + retval = pio_write(board, priv, &buffer[*bytes_written], 1, &num_bytes); + *bytes_written += num_bytes; + if (retval < 0) + return retval; + } + + return retval; +} +EXPORT_SYMBOL(nec7210_write); + +/* + * interrupt service routine + */ +irqreturn_t nec7210_interrupt(struct gpib_board *board, struct nec7210_priv *priv) +{ + int status1, status2; + + // read interrupt status (also clears status) + status1 = read_byte(priv, ISR1); + status2 = read_byte(priv, ISR2); + + return nec7210_interrupt_have_status(board, priv, status1, status2); +} +EXPORT_SYMBOL(nec7210_interrupt); + +irqreturn_t nec7210_interrupt_have_status(struct gpib_board *board, + struct nec7210_priv *priv, int status1, int status2) +{ +#ifdef NEC_DMA + unsigned long dma_flags; +#endif + int retval = IRQ_NONE; + + // record service request in status + if (status2 & HR_SRQI) + set_bit(SRQI_NUM, &board->status); + + // change in lockout status + if (status2 & HR_LOKC) { + if (status2 & HR_LOK) + set_bit(LOK_NUM, &board->status); + else + clear_bit(LOK_NUM, &board->status); + } + + // change in remote status + if (status2 & HR_REMC) { + if (status2 & HR_REM) + set_bit(REM_NUM, &board->status); + else + clear_bit(REM_NUM, &board->status); + } + + // record reception of END + if (status1 & HR_END) { + set_bit(RECEIVED_END_BN, &priv->state); + if ((priv->auxa_bits & HR_HANDSHAKE_MASK) == HR_HLDE) + set_bit(RFD_HOLDOFF_BN, &priv->state); + } + + // get incoming data in PIO mode + if ((status1 & HR_DI)) { + set_bit(READ_READY_BN, &priv->state); + if ((priv->auxa_bits & HR_HANDSHAKE_MASK) == HR_HLDA) + set_bit(RFD_HOLDOFF_BN, &priv->state); + } +#ifdef NEC_DMA + // check for dma read transfer complete + if (test_bit(DMA_READ_IN_PROGRESS_BN, &priv->state)) { + dma_flags = claim_dma_lock(); + disable_dma(priv->dma_channel); + clear_dma_ff(priv->dma_channel); + if ((status1 & HR_END) || get_dma_residue(priv->dma_channel) == 0) + clear_bit(DMA_READ_IN_PROGRESS_BN, &priv->state); + else + enable_dma(priv->dma_channel); + release_dma_lock(dma_flags); + } +#endif + if ((status1 & HR_DO)) { + if (test_bit(DMA_WRITE_IN_PROGRESS_BN, &priv->state) == 0) + set_bit(WRITE_READY_BN, &priv->state); +#ifdef NEC_DMA + if (test_bit(DMA_WRITE_IN_PROGRESS_BN, &priv->state)) { // write data, isa dma mode + // check if dma transfer is complete + dma_flags = claim_dma_lock(); + disable_dma(priv->dma_channel); + clear_dma_ff(priv->dma_channel); + if (get_dma_residue(priv->dma_channel) == 0) { + clear_bit(DMA_WRITE_IN_PROGRESS_BN, &priv->state); + // XXX race? byte may still be in CDOR reg + } else { + clear_bit(WRITE_READY_BN, &priv->state); + enable_dma(priv->dma_channel); + } + release_dma_lock(dma_flags); + } +#endif + } + + // outgoing command can be sent + if (status2 & HR_CO) + set_bit(COMMAND_READY_BN, &priv->state); + + // command pass through received + if (status1 & HR_CPT) + write_byte(priv, AUX_NVAL, AUXMR); + + if (status1 & HR_ERR) + set_bit(BUS_ERROR_BN, &priv->state); + + if (status1 & HR_DEC) { + unsigned short address_status_bits = read_byte(priv, ADSR); + + // ignore device clear events if we are controller in charge + if ((address_status_bits & HR_CIC) == 0) { + push_gpib_event(board, EVENT_DEV_CLR); + set_bit(DEV_CLEAR_BN, &priv->state); + } + } + + if (status1 & HR_DET) + push_gpib_event(board, EVENT_DEV_TRG); + + // Addressing status has changed + if (status2 & HR_ADSC) + set_bit(ADR_CHANGE_BN, &priv->state); + + if ((status1 & priv->reg_bits[IMR1]) || + (status2 & (priv->reg_bits[IMR2] & IMR2_ENABLE_INTR_MASK)) || + nec7210_atn_has_changed(board, priv)) { + nec7210_update_status_nolock(board, priv); + dev_dbg(board->gpib_dev, "minor %i, stat %lx, isr1 0x%x, imr1 0x%x, isr2 0x%x, imr2 0x%x\n", + board->minor, board->status, status1, priv->reg_bits[IMR1], status2, + priv->reg_bits[IMR2]); + wake_up_interruptible(&board->wait); /* wake up sleeping process */ + retval = IRQ_HANDLED; + } + + return retval; +} +EXPORT_SYMBOL(nec7210_interrupt_have_status); + +void nec7210_board_reset(struct nec7210_priv *priv, const struct gpib_board *board) +{ + /* 7210 chip reset */ + write_byte(priv, AUX_CR, AUXMR); + + /* disable all interrupts */ + priv->reg_bits[IMR1] = 0; + write_byte(priv, priv->reg_bits[IMR1], IMR1); + priv->reg_bits[IMR2] = 0; + write_byte(priv, priv->reg_bits[IMR2], IMR2); + write_byte(priv, 0, SPMR); + + /* clear registers by reading */ + read_byte(priv, CPTR); + read_byte(priv, ISR1); + read_byte(priv, ISR2); + + /* parallel poll unconfigure */ + write_byte(priv, PPR | HR_PPU, AUXMR); + + priv->reg_bits[ADMR] = HR_TRM0 | HR_TRM1; + + priv->auxa_bits = AUXRA | HR_HLDA; + write_byte(priv, priv->auxa_bits, AUXMR); + + write_byte(priv, AUXRE | 0, AUXMR); + + /* set INT pin to active high, enable command pass through of unknown commands */ + priv->auxb_bits = AUXRB | HR_CPTE; + write_byte(priv, priv->auxb_bits, AUXMR); + write_byte(priv, AUXRE, AUXMR); +} +EXPORT_SYMBOL(nec7210_board_reset); + +void nec7210_board_online(struct nec7210_priv *priv, const struct gpib_board *board) +{ + /* set GPIB address */ + nec7210_primary_address(board, priv, board->pad); + nec7210_secondary_address(board, priv, board->sad, board->sad >= 0); + + /* enable interrupts */ + priv->reg_bits[IMR1] = HR_ERRIE | HR_DECIE | HR_ENDIE | + HR_DETIE | HR_CPTIE | HR_DOIE | HR_DIIE; + priv->reg_bits[IMR2] = IMR2_ENABLE_INTR_MASK; + write_byte(priv, priv->reg_bits[IMR1], IMR1); + write_byte(priv, priv->reg_bits[IMR2], IMR2); + + write_byte(priv, AUX_PON, AUXMR); +} +EXPORT_SYMBOL(nec7210_board_online); + +#ifdef CONFIG_HAS_IOPORT +/* wrappers for io */ +u8 nec7210_ioport_read_byte(struct nec7210_priv *priv, unsigned int register_num) +{ + return inb(priv->iobase + register_num * priv->offset); +} +EXPORT_SYMBOL(nec7210_ioport_read_byte); + +void nec7210_ioport_write_byte(struct nec7210_priv *priv, u8 data, unsigned int register_num) +{ + if (register_num == AUXMR) + /* + * locking makes absolutely sure noone accesses the + * AUXMR register faster than once per microsecond + */ + nec7210_locking_ioport_write_byte(priv, data, register_num); + else + outb(data, priv->iobase + register_num * priv->offset); +} +EXPORT_SYMBOL(nec7210_ioport_write_byte); + +/* locking variants of io wrappers, for chips that page-in registers */ +u8 nec7210_locking_ioport_read_byte(struct nec7210_priv *priv, unsigned int register_num) +{ + u8 retval; + unsigned long flags; + + spin_lock_irqsave(&priv->register_page_lock, flags); + retval = inb(priv->iobase + register_num * priv->offset); + spin_unlock_irqrestore(&priv->register_page_lock, flags); + return retval; +} +EXPORT_SYMBOL(nec7210_locking_ioport_read_byte); + +void nec7210_locking_ioport_write_byte(struct nec7210_priv *priv, u8 data, + unsigned int register_num) +{ + unsigned long flags; + + spin_lock_irqsave(&priv->register_page_lock, flags); + if (register_num == AUXMR) + udelay(1); + outb(data, priv->iobase + register_num * priv->offset); + spin_unlock_irqrestore(&priv->register_page_lock, flags); +} +EXPORT_SYMBOL(nec7210_locking_ioport_write_byte); +#endif + +u8 nec7210_iomem_read_byte(struct nec7210_priv *priv, unsigned int register_num) +{ + return readb(priv->mmiobase + register_num * priv->offset); +} +EXPORT_SYMBOL(nec7210_iomem_read_byte); + +void nec7210_iomem_write_byte(struct nec7210_priv *priv, u8 data, unsigned int register_num) +{ + if (register_num == AUXMR) + /* + * locking makes absolutely sure noone accesses the + * AUXMR register faster than once per microsecond + */ + nec7210_locking_iomem_write_byte(priv, data, register_num); + else + writeb(data, priv->mmiobase + register_num * priv->offset); +} +EXPORT_SYMBOL(nec7210_iomem_write_byte); + +u8 nec7210_locking_iomem_read_byte(struct nec7210_priv *priv, unsigned int register_num) +{ + u8 retval; + unsigned long flags; + + spin_lock_irqsave(&priv->register_page_lock, flags); + retval = readb(priv->mmiobase + register_num * priv->offset); + spin_unlock_irqrestore(&priv->register_page_lock, flags); + return retval; +} +EXPORT_SYMBOL(nec7210_locking_iomem_read_byte); + +void nec7210_locking_iomem_write_byte(struct nec7210_priv *priv, u8 data, + unsigned int register_num) +{ + unsigned long flags; + + spin_lock_irqsave(&priv->register_page_lock, flags); + if (register_num == AUXMR) + udelay(1); + writeb(data, priv->mmiobase + register_num * priv->offset); + spin_unlock_irqrestore(&priv->register_page_lock, flags); +} +EXPORT_SYMBOL(nec7210_locking_iomem_write_byte); + +static int __init nec7210_init_module(void) +{ + return 0; +} + +static void __exit nec7210_exit_module(void) +{ +} + +module_init(nec7210_init_module); +module_exit(nec7210_exit_module); diff --git a/drivers/gpib/ni_usb/Makefile b/drivers/gpib/ni_usb/Makefile new file mode 100644 index 000000000000..469c5d16add3 --- /dev/null +++ b/drivers/gpib/ni_usb/Makefile @@ -0,0 +1,4 @@ + +obj-$(CONFIG_GPIB_NI_USB) += ni_usb_gpib.o + + diff --git a/drivers/gpib/ni_usb/ni_usb_gpib.c b/drivers/gpib/ni_usb/ni_usb_gpib.c new file mode 100644 index 000000000000..1f8412de9fa3 --- /dev/null +++ b/drivers/gpib/ni_usb/ni_usb_gpib.c @@ -0,0 +1,2678 @@ +// SPDX-License-Identifier: GPL-2.0 + +/*************************************************************************** + * driver for National Instruments usb to gpib adapters + * copyright : (C) 2004 by Frank Mori Hess + ***************************************************************************/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#define dev_fmt pr_fmt +#define DRV_NAME KBUILD_MODNAME + +#include +#include +#include +#include "ni_usb_gpib.h" +#include "gpibP.h" +#include "nec7210.h" +#include "tnt4882_registers.h" + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("GPIB driver for National Instruments USB devices"); + +#define MAX_NUM_NI_USB_INTERFACES 128 +static struct usb_interface *ni_usb_driver_interfaces[MAX_NUM_NI_USB_INTERFACES]; + +static int ni_usb_parse_status_block(const u8 *buffer, struct ni_usb_status_block *status); +static int ni_usb_set_interrupt_monitor(struct gpib_board *board, unsigned int monitored_bits); +static void ni_usb_stop(struct ni_usb_priv *ni_priv); + +static DEFINE_MUTEX(ni_usb_hotplug_lock); + +// calculates a reasonable timeout in that can be passed to usb functions +static inline unsigned long ni_usb_timeout_msecs(unsigned int usec) +{ + if (usec == 0) + return 0; + return 2000 + usec / 500; +}; + +// returns timeout code byte for use in ni-usb-b instructions +static unsigned short ni_usb_timeout_code(unsigned int usec) +{ + if (usec == 0) + return 0xf0; + else if (usec <= 10) + return 0xf1; + else if (usec <= 30) + return 0xf2; + else if (usec <= 100) + return 0xf3; + else if (usec <= 300) + return 0xf4; + else if (usec <= 1000) + return 0xf5; + else if (usec <= 3000) + return 0xf6; + else if (usec <= 10000) + return 0xf7; + else if (usec <= 30000) + return 0xf8; + else if (usec <= 100000) + return 0xf9; + else if (usec <= 300000) + return 0xfa; + else if (usec <= 1000000) + return 0xfb; + else if (usec <= 3000000) + return 0xfc; + else if (usec <= 10000000) + return 0xfd; + else if (usec <= 30000000) + return 0xfe; + else if (usec <= 100000000) + return 0xff; + else if (usec <= 300000000) + return 0x01; + /* + * NI driver actually uses 0xff for timeout T1000s, which is a bug in their code. + * I've verified on a usb-b that a code of 0x2 is correct for a 1000 sec timeout + */ + else if (usec <= 1000000000) + return 0x02; + pr_err("bug? usec is greater than 1e9\n"); + return 0xf0; +} + +static void ni_usb_bulk_complete(struct urb *urb) +{ + struct ni_usb_urb_ctx *context = urb->context; + + complete(&context->complete); +} + +static void ni_usb_timeout_handler(struct timer_list *t) +{ + struct ni_usb_priv *ni_priv = timer_container_of(ni_priv, t, + bulk_timer); + struct ni_usb_urb_ctx *context = &ni_priv->context; + + context->timed_out = 1; + complete(&context->complete); +}; + +// I'm using nonblocking loosely here, it only means -EAGAIN can be returned in certain cases +static int ni_usb_nonblocking_send_bulk_msg(struct ni_usb_priv *ni_priv, void *data, + int data_length, int *actual_data_length, + int timeout_msecs) +{ + struct usb_device *usb_dev; + int retval; + unsigned int out_pipe; + struct ni_usb_urb_ctx *context = &ni_priv->context; + + *actual_data_length = 0; + mutex_lock(&ni_priv->bulk_transfer_lock); + if (!ni_priv->bus_interface) { + mutex_unlock(&ni_priv->bulk_transfer_lock); + return -ENODEV; + } + if (ni_priv->bulk_urb) { + mutex_unlock(&ni_priv->bulk_transfer_lock); + return -EAGAIN; + } + ni_priv->bulk_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!ni_priv->bulk_urb) { + mutex_unlock(&ni_priv->bulk_transfer_lock); + return -ENOMEM; + } + usb_dev = interface_to_usbdev(ni_priv->bus_interface); + out_pipe = usb_sndbulkpipe(usb_dev, ni_priv->bulk_out_endpoint); + init_completion(&context->complete); + context->timed_out = 0; + usb_fill_bulk_urb(ni_priv->bulk_urb, usb_dev, out_pipe, data, data_length, + &ni_usb_bulk_complete, context); + + if (timeout_msecs) + mod_timer(&ni_priv->bulk_timer, jiffies + msecs_to_jiffies(timeout_msecs)); + + retval = usb_submit_urb(ni_priv->bulk_urb, GFP_KERNEL); + if (retval) { + timer_delete_sync(&ni_priv->bulk_timer); + usb_free_urb(ni_priv->bulk_urb); + ni_priv->bulk_urb = NULL; + dev_err(&usb_dev->dev, "failed to submit bulk out urb, retval=%i\n", + retval); + mutex_unlock(&ni_priv->bulk_transfer_lock); + return retval; + } + mutex_unlock(&ni_priv->bulk_transfer_lock); + wait_for_completion(&context->complete); // wait for ni_usb_bulk_complete + if (context->timed_out) { + usb_kill_urb(ni_priv->bulk_urb); + dev_err(&usb_dev->dev, "killed urb due to timeout\n"); + retval = -ETIMEDOUT; + } else { + retval = ni_priv->bulk_urb->status; + } + + timer_delete_sync(&ni_priv->bulk_timer); + *actual_data_length = ni_priv->bulk_urb->actual_length; + mutex_lock(&ni_priv->bulk_transfer_lock); + usb_free_urb(ni_priv->bulk_urb); + ni_priv->bulk_urb = NULL; + mutex_unlock(&ni_priv->bulk_transfer_lock); + return retval; +} + +static int ni_usb_send_bulk_msg(struct ni_usb_priv *ni_priv, void *data, int data_length, + int *actual_data_length, int timeout_msecs) +{ + int retval; + int timeout_msecs_remaining = timeout_msecs; + + retval = ni_usb_nonblocking_send_bulk_msg(ni_priv, data, data_length, actual_data_length, + timeout_msecs_remaining); + while (retval == -EAGAIN && (timeout_msecs == 0 || timeout_msecs_remaining > 0)) { + usleep_range(1000, 1500); + retval = ni_usb_nonblocking_send_bulk_msg(ni_priv, data, data_length, + actual_data_length, + timeout_msecs_remaining); + if (timeout_msecs != 0) + --timeout_msecs_remaining; + } + if (timeout_msecs != 0 && timeout_msecs_remaining <= 0) + return -ETIMEDOUT; + return retval; +} + +// I'm using nonblocking loosely here, it only means -EAGAIN can be returned in certain cases +static int ni_usb_nonblocking_receive_bulk_msg(struct ni_usb_priv *ni_priv, + void *data, int data_length, + int *actual_data_length, int timeout_msecs, + int interruptible) +{ + struct usb_device *usb_dev; + int retval; + unsigned int in_pipe; + struct ni_usb_urb_ctx *context = &ni_priv->context; + + *actual_data_length = 0; + mutex_lock(&ni_priv->bulk_transfer_lock); + if (!ni_priv->bus_interface) { + mutex_unlock(&ni_priv->bulk_transfer_lock); + return -ENODEV; + } + if (ni_priv->bulk_urb) { + mutex_unlock(&ni_priv->bulk_transfer_lock); + return -EAGAIN; + } + ni_priv->bulk_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!ni_priv->bulk_urb) { + mutex_unlock(&ni_priv->bulk_transfer_lock); + return -ENOMEM; + } + usb_dev = interface_to_usbdev(ni_priv->bus_interface); + in_pipe = usb_rcvbulkpipe(usb_dev, ni_priv->bulk_in_endpoint); + init_completion(&context->complete); + context->timed_out = 0; + usb_fill_bulk_urb(ni_priv->bulk_urb, usb_dev, in_pipe, data, data_length, + &ni_usb_bulk_complete, context); + + if (timeout_msecs) + mod_timer(&ni_priv->bulk_timer, jiffies + msecs_to_jiffies(timeout_msecs)); + + retval = usb_submit_urb(ni_priv->bulk_urb, GFP_KERNEL); + if (retval) { + timer_delete_sync(&ni_priv->bulk_timer); + usb_free_urb(ni_priv->bulk_urb); + ni_priv->bulk_urb = NULL; + dev_err(&usb_dev->dev, "failed to submit bulk in urb, retval=%i\n", retval); + mutex_unlock(&ni_priv->bulk_transfer_lock); + return retval; + } + mutex_unlock(&ni_priv->bulk_transfer_lock); + if (interruptible) { + if (wait_for_completion_interruptible(&context->complete)) { + /* + * If we got interrupted by a signal while + * waiting for the usb gpib to respond, we + * should send a stop command so it will + * finish up with whatever it was doing and + * send its response now. + */ + ni_usb_stop(ni_priv); + retval = -ERESTARTSYS; + /* + * now do an uninterruptible wait, it shouldn't take long + * for the board to respond now. + */ + wait_for_completion(&context->complete); + } + } else { + wait_for_completion(&context->complete); + } + if (context->timed_out) { + usb_kill_urb(ni_priv->bulk_urb); + dev_err(&usb_dev->dev, "killed urb due to timeout\n"); + retval = -ETIMEDOUT; + } else { + if (ni_priv->bulk_urb->status) + retval = ni_priv->bulk_urb->status; + } + timer_delete_sync(&ni_priv->bulk_timer); + *actual_data_length = ni_priv->bulk_urb->actual_length; + mutex_lock(&ni_priv->bulk_transfer_lock); + usb_free_urb(ni_priv->bulk_urb); + ni_priv->bulk_urb = NULL; + mutex_unlock(&ni_priv->bulk_transfer_lock); + return retval; +} + +static int ni_usb_receive_bulk_msg(struct ni_usb_priv *ni_priv, void *data, + int data_length, int *actual_data_length, int timeout_msecs, + int interruptible) +{ + int retval; + int timeout_msecs_remaining = timeout_msecs; + + retval = ni_usb_nonblocking_receive_bulk_msg(ni_priv, data, data_length, + actual_data_length, timeout_msecs_remaining, + interruptible); + while (retval == -EAGAIN && (timeout_msecs == 0 || timeout_msecs_remaining > 0)) { + usleep_range(1000, 1500); + retval = ni_usb_nonblocking_receive_bulk_msg(ni_priv, data, data_length, + actual_data_length, + timeout_msecs_remaining, + interruptible); + if (timeout_msecs != 0) + --timeout_msecs_remaining; + } + if (timeout_msecs && timeout_msecs_remaining <= 0) + return -ETIMEDOUT; + return retval; +} + +static int ni_usb_receive_control_msg(struct ni_usb_priv *ni_priv, __u8 request, + __u8 requesttype, __u16 value, __u16 index, + void *data, __u16 size, int timeout_msecs) +{ + struct usb_device *usb_dev; + int retval; + unsigned int in_pipe; + + mutex_lock(&ni_priv->control_transfer_lock); + if (!ni_priv->bus_interface) { + mutex_unlock(&ni_priv->control_transfer_lock); + return -ENODEV; + } + usb_dev = interface_to_usbdev(ni_priv->bus_interface); + in_pipe = usb_rcvctrlpipe(usb_dev, 0); + retval = usb_control_msg(usb_dev, in_pipe, request, requesttype, value, index, data, + size, timeout_msecs); + mutex_unlock(&ni_priv->control_transfer_lock); + return retval; +} + +static void ni_usb_soft_update_status(struct gpib_board *board, unsigned int ni_usb_ibsta, + unsigned int clear_mask) +{ + static const unsigned int ni_usb_ibsta_mask = SRQI | ATN | CIC | REM | LACS | TACS | LOK; + + struct ni_usb_priv *ni_priv = board->private_data; + struct usb_device *usb_dev = interface_to_usbdev(ni_priv->bus_interface); + unsigned int need_monitoring_bits = ni_usb_ibsta_monitor_mask; + unsigned long flags; + + board->status &= ~clear_mask; + board->status &= ~ni_usb_ibsta_mask; + board->status |= ni_usb_ibsta & ni_usb_ibsta_mask; + if (ni_usb_ibsta & DCAS) + push_gpib_event(board, EVENT_DEV_CLR); + if (ni_usb_ibsta & DTAS) + push_gpib_event(board, EVENT_DEV_TRG); + + spin_lock_irqsave(&board->spinlock, flags); +/* remove set status bits from monitored set why ?***/ + ni_priv->monitored_ibsta_bits &= ~ni_usb_ibsta; + need_monitoring_bits &= ~ni_priv->monitored_ibsta_bits; /* mm - monitored set */ + spin_unlock_irqrestore(&board->spinlock, flags); + dev_dbg(&usb_dev->dev, "need_monitoring_bits=0x%x\n", need_monitoring_bits); + + if (need_monitoring_bits & ~ni_usb_ibsta) + ni_usb_set_interrupt_monitor(board, ni_usb_ibsta_monitor_mask); + else if (need_monitoring_bits & ni_usb_ibsta) + wake_up_interruptible(&board->wait); + + dev_dbg(&usb_dev->dev, "ibsta=0x%x\n", ni_usb_ibsta); +} + +static int ni_usb_parse_status_block(const u8 *buffer, struct ni_usb_status_block *status) +{ + u16 count; + + status->id = buffer[0]; + status->ibsta = (buffer[1] << 8) | buffer[2]; + status->error_code = buffer[3]; + count = buffer[4] | (buffer[5] << 8); + count = ~count; + count++; + status->count = count; + return 8; +}; + +static void ni_usb_dump_raw_block(const u8 *raw_data, int length) +{ + print_hex_dump(KERN_INFO, "", DUMP_PREFIX_NONE, 8, 1, raw_data, length, true); +} + +static int ni_usb_parse_register_read_block(const u8 *raw_data, unsigned int *results, + int num_results) +{ + int i = 0; + int j; + int unexpected = 0; + static const int results_per_chunk = 3; + + for (j = 0; j < num_results;) { + int k; + + if (raw_data[i++] != NIUSB_REGISTER_READ_DATA_START_ID) { + pr_err("parse error: wrong start id\n"); + unexpected = 1; + } + for (k = 0; k < results_per_chunk && j < num_results; ++k) + results[j++] = raw_data[i++]; + } + while (i % 4) + i++; + if (raw_data[i++] != NIUSB_REGISTER_READ_DATA_END_ID) { + pr_err("parse error: wrong end id\n"); + unexpected = 1; + } + if (raw_data[i++] % results_per_chunk != num_results % results_per_chunk) { + pr_err("parse error: wrong count=%i for NIUSB_REGISTER_READ_DATA_END\n", + (int)raw_data[i - 1]); + unexpected = 1; + } + while (i % 4) { + if (raw_data[i++] != 0) { + pr_err("unexpected data: raw_data[%i]=0x%x, expected 0\n", + i - 1, (int)raw_data[i - 1]); + unexpected = 1; + } + } + if (unexpected) + ni_usb_dump_raw_block(raw_data, i); + return i; +} + +static int ni_usb_parse_termination_block(const u8 *buffer) +{ + int i = 0; + + if (buffer[i++] != NIUSB_TERM_ID || + buffer[i++] != 0x0 || + buffer[i++] != 0x0 || + buffer[i++] != 0x0) { + pr_err("received unexpected termination block\n"); + pr_err(" expected: 0x%x 0x%x 0x%x 0x%x\n", NIUSB_TERM_ID, 0x0, 0x0, 0x0); + pr_err(" received: 0x%x 0x%x 0x%x 0x%x\n", + buffer[i - 4], buffer[i - 3], buffer[i - 2], buffer[i - 1]); + } + return i; +}; + +static int parse_board_ibrd_readback(const u8 *raw_data, struct ni_usb_status_block *status, + u8 *parsed_data, int parsed_data_length, + int *actual_bytes_read) +{ + static const int ibrd_data_block_length = 0xf; + static const int ibrd_extended_data_block_length = 0x1e; + int data_block_length = 0; + int i = 0; + int j = 0; + int k; + int num_data_blocks = 0; + struct ni_usb_status_block register_write_status; + int unexpected = 0; + + while (raw_data[i] == NIUSB_IBRD_DATA_ID || raw_data[i] == NIUSB_IBRD_EXTENDED_DATA_ID) { + if (raw_data[i] == NIUSB_IBRD_DATA_ID) { + data_block_length = ibrd_data_block_length; + } else if (raw_data[i] == NIUSB_IBRD_EXTENDED_DATA_ID) { + data_block_length = ibrd_extended_data_block_length; + if (raw_data[++i] != 0) { + pr_err("unexpected data: raw_data[%i]=0x%x, expected 0\n", + i, (int)raw_data[i]); + unexpected = 1; + } + } else { + pr_err("Unexpected NIUSB_IBRD ID\n"); + return -EINVAL; + } + ++i; + for (k = 0; k < data_block_length; k++) { + if (j < parsed_data_length) + parsed_data[j++] = raw_data[i++]; + else + ++i; + } + ++num_data_blocks; + } + i += ni_usb_parse_status_block(&raw_data[i], status); + if (status->id != NIUSB_IBRD_STATUS_ID) { + pr_err("bug: status->id=%i, != ibrd_status_id\n", status->id); + return -EIO; + } + i++; + if (num_data_blocks) { + *actual_bytes_read = (num_data_blocks - 1) * data_block_length + raw_data[i++]; + } else { + ++i; + *actual_bytes_read = 0; + } + if (*actual_bytes_read > j) + pr_err("bug: discarded data. actual_bytes_read=%i, j=%i\n", *actual_bytes_read, j); + for (k = 0; k < 2; k++) + if (raw_data[i++] != 0) { + pr_err("unexpected data: raw_data[%i]=0x%x, expected 0\n", + i - 1, (int)raw_data[i - 1]); + unexpected = 1; + } + i += ni_usb_parse_status_block(&raw_data[i], ®ister_write_status); + if (register_write_status.id != NIUSB_REG_WRITE_ID) { + pr_err("unexpected data: register write status id=0x%x, expected 0x%x\n", + register_write_status.id, NIUSB_REG_WRITE_ID); + unexpected = 1; + } + if (raw_data[i++] != 2) { + pr_err("unexpected data: register write count=%i, expected 2\n", + (int)raw_data[i - 1]); + unexpected = 1; + } + for (k = 0; k < 3; k++) + if (raw_data[i++] != 0) { + pr_err("unexpected data: raw_data[%i]=0x%x, expected 0\n", + i - 1, (int)raw_data[i - 1]); + unexpected = 1; + } + i += ni_usb_parse_termination_block(&raw_data[i]); + if (unexpected) + ni_usb_dump_raw_block(raw_data, i); + return i; +} + +static int ni_usb_parse_reg_write_status_block(const u8 *raw_data, + struct ni_usb_status_block *status, + int *writes_completed) +{ + int i = 0; + + i += ni_usb_parse_status_block(raw_data, status); + *writes_completed = raw_data[i++]; + while (i % 4) + i++; + return i; +} + +static int ni_usb_write_registers(struct ni_usb_priv *ni_priv, + const struct ni_usb_register *writes, int num_writes, + unsigned int *ibsta) +{ + struct usb_device *usb_dev = interface_to_usbdev(ni_priv->bus_interface); + int retval; + u8 *out_data, *in_data; + int out_data_length; + static const int in_data_length = 0x20; + int bytes_written = 0, bytes_read = 0; + int i = 0; + int j; + struct ni_usb_status_block status; + static const int bytes_per_write = 3; + int reg_writes_completed; + + out_data_length = num_writes * bytes_per_write + 0x10; + out_data = kmalloc(out_data_length, GFP_KERNEL); + if (!out_data) + return -ENOMEM; + i += ni_usb_bulk_register_write_header(&out_data[i], num_writes); + for (j = 0; j < num_writes; j++) + i += ni_usb_bulk_register_write(&out_data[i], writes[j]); + while (i % 4) + out_data[i++] = 0x00; + i += ni_usb_bulk_termination(&out_data[i]); + + mutex_lock(&ni_priv->addressed_transfer_lock); + + retval = ni_usb_send_bulk_msg(ni_priv, out_data, i, &bytes_written, 1000); + kfree(out_data); + if (retval) { + mutex_unlock(&ni_priv->addressed_transfer_lock); + dev_err(&usb_dev->dev, "send_bulk_msg returned %i, bytes_written=%i, i=%i\n", + retval, bytes_written, i); + return retval; + } + + in_data = kmalloc(in_data_length, GFP_KERNEL); + if (!in_data) { + mutex_unlock(&ni_priv->addressed_transfer_lock); + return -ENOMEM; + } + retval = ni_usb_receive_bulk_msg(ni_priv, in_data, in_data_length, &bytes_read, 1000, 0); + if (retval || bytes_read != 16) { + mutex_unlock(&ni_priv->addressed_transfer_lock); + dev_err(&usb_dev->dev, "receive_bulk_msg returned %i, bytes_read=%i\n", + retval, bytes_read); + ni_usb_dump_raw_block(in_data, bytes_read); + kfree(in_data); + return retval; + } + + mutex_unlock(&ni_priv->addressed_transfer_lock); + + ni_usb_parse_reg_write_status_block(in_data, &status, ®_writes_completed); + // FIXME parse extra 09 status bits and termination + kfree(in_data); + if (status.id != NIUSB_REG_WRITE_ID) { + dev_err(&usb_dev->dev, "parse error, id=0x%x != NIUSB_REG_WRITE_ID\n", status.id); + return -EIO; + } + if (status.error_code) { + dev_err(&usb_dev->dev, "nonzero error code 0x%x\n", status.error_code); + return -EIO; + } + if (reg_writes_completed != num_writes) { + dev_err(&usb_dev->dev, "reg_writes_completed=%i, num_writes=%i\n", + reg_writes_completed, num_writes); + return -EIO; + } + if (ibsta) + *ibsta = status.ibsta; + return 0; +} + +// interface functions +static int ni_usb_read(struct gpib_board *board, u8 *buffer, size_t length, + int *end, size_t *bytes_read) +{ + int retval, parse_retval; + struct ni_usb_priv *ni_priv = board->private_data; + struct usb_device *usb_dev; + u8 *out_data, *in_data; + static const int out_data_length = 0x20; + int in_data_length; + int usb_bytes_written = 0, usb_bytes_read = 0; + int i = 0; + int complement_count; + int actual_length; + struct ni_usb_status_block status; + static const int max_read_length = 0xffff; + struct ni_usb_register reg; + + *bytes_read = 0; + if (!ni_priv->bus_interface) + return -ENODEV; + if (length > max_read_length) + return -EINVAL; + usb_dev = interface_to_usbdev(ni_priv->bus_interface); + out_data = kmalloc(out_data_length, GFP_KERNEL); + if (!out_data) + return -ENOMEM; + out_data[i++] = 0x0a; + out_data[i++] = ni_priv->eos_mode >> 8; + out_data[i++] = ni_priv->eos_char; + out_data[i++] = ni_usb_timeout_code(board->usec_timeout); + complement_count = length - 1; + complement_count = ~complement_count; + out_data[i++] = complement_count & 0xff; + out_data[i++] = (complement_count >> 8) & 0xff; + out_data[i++] = 0x0; + out_data[i++] = 0x0; + i += ni_usb_bulk_register_write_header(&out_data[i], 2); + reg.device = NIUSB_SUBDEV_TNT4882; + reg.address = nec7210_to_tnt4882_offset(AUXMR); + reg.value = AUX_HLDI; + i += ni_usb_bulk_register_write(&out_data[i], reg); + reg.value = AUX_CLEAR_END; + i += ni_usb_bulk_register_write(&out_data[i], reg); + while (i % 4) // pad with zeros to 4-byte boundary + out_data[i++] = 0x0; + i += ni_usb_bulk_termination(&out_data[i]); + + mutex_lock(&ni_priv->addressed_transfer_lock); + + retval = ni_usb_send_bulk_msg(ni_priv, out_data, i, &usb_bytes_written, 1000); + kfree(out_data); + if (retval || usb_bytes_written != i) { + if (retval == 0) + retval = -EIO; + dev_err(&usb_dev->dev, "send_bulk_msg returned %i, usb_bytes_written=%i, i=%i\n", + retval, usb_bytes_written, i); + mutex_unlock(&ni_priv->addressed_transfer_lock); + return retval; + } + + in_data_length = (length / 30 + 1) * 0x20 + 0x20; + in_data = kmalloc(in_data_length, GFP_KERNEL); + if (!in_data) { + mutex_unlock(&ni_priv->addressed_transfer_lock); + return -ENOMEM; + } + retval = ni_usb_receive_bulk_msg(ni_priv, in_data, in_data_length, &usb_bytes_read, + ni_usb_timeout_msecs(board->usec_timeout), 1); + + mutex_unlock(&ni_priv->addressed_transfer_lock); + + if (retval == -ERESTARTSYS) { + } else if (retval) { + dev_err(&usb_dev->dev, "receive_bulk_msg returned %i, usb_bytes_read=%i\n", + retval, usb_bytes_read); + kfree(in_data); + return retval; + } + parse_retval = parse_board_ibrd_readback(in_data, &status, buffer, length, &actual_length); + if (parse_retval != usb_bytes_read) { + if (parse_retval >= 0) + parse_retval = -EIO; + dev_err(&usb_dev->dev, "retval=%i usb_bytes_read=%i\n", + parse_retval, usb_bytes_read); + kfree(in_data); + return parse_retval; + } + if (actual_length != length - status.count) { + dev_err(&usb_dev->dev, "actual_length=%i expected=%li\n", + actual_length, (long)(length - status.count)); + ni_usb_dump_raw_block(in_data, usb_bytes_read); + } + kfree(in_data); + switch (status.error_code) { + case NIUSB_NO_ERROR: + retval = 0; + break; + case NIUSB_ABORTED_ERROR: + /* + * this is expected if ni_usb_receive_bulk_msg got + * interrupted by a signal and returned -ERESTARTSYS + */ + break; + case NIUSB_ATN_STATE_ERROR: + if (status.ibsta & DCAS) { + retval = -EINTR; + } else { + retval = -EIO; + dev_dbg(&usb_dev->dev, "read when ATN set stat: 0x%06x\n", status.ibsta); + } + break; + case NIUSB_ADDRESSING_ERROR: + retval = -EIO; + break; + case NIUSB_TIMEOUT_ERROR: + retval = -ETIMEDOUT; + break; + case NIUSB_EOSMODE_ERROR: + dev_err(&usb_dev->dev, "driver bug, we should have been able to avoid NIUSB_EOSMODE_ERROR.\n"); + retval = -EINVAL; + break; + default: + dev_err(&usb_dev->dev, "unknown error code=%i\n", status.error_code); + retval = -EIO; + break; + } + ni_usb_soft_update_status(board, status.ibsta, 0); + if (status.ibsta & END) + *end = 1; + else + *end = 0; + *bytes_read = actual_length; + return retval; +} + +static int ni_usb_write(struct gpib_board *board, u8 *buffer, size_t length, + int send_eoi, size_t *bytes_written) +{ + int retval; + struct ni_usb_priv *ni_priv = board->private_data; + struct usb_device *usb_dev; + u8 *out_data, *in_data; + int out_data_length; + static const int in_data_length = 0x10; + int usb_bytes_written = 0, usb_bytes_read = 0; + int i = 0, j; + int complement_count; + struct ni_usb_status_block status; + static const int max_write_length = 0xffff; + + if (!ni_priv->bus_interface) + return -ENODEV; + if (length > max_write_length) + return -EINVAL; + usb_dev = interface_to_usbdev(ni_priv->bus_interface); + out_data_length = length + 0x10; + out_data = kmalloc(out_data_length, GFP_KERNEL); + if (!out_data) + return -ENOMEM; + out_data[i++] = 0x0d; + complement_count = length - 1; + complement_count = ~complement_count; + out_data[i++] = complement_count & 0xff; + out_data[i++] = (complement_count >> 8) & 0xff; + out_data[i++] = ni_usb_timeout_code(board->usec_timeout); + out_data[i++] = 0x0; + out_data[i++] = 0x0; + if (send_eoi) + out_data[i++] = 0x8; + else + out_data[i++] = 0x0; + out_data[i++] = 0x0; + for (j = 0; j < length; j++) + out_data[i++] = buffer[j]; + while (i % 4) // pad with zeros to 4-byte boundary + out_data[i++] = 0x0; + i += ni_usb_bulk_termination(&out_data[i]); + + mutex_lock(&ni_priv->addressed_transfer_lock); + + retval = ni_usb_send_bulk_msg(ni_priv, out_data, i, &usb_bytes_written, + ni_usb_timeout_msecs(board->usec_timeout)); + kfree(out_data); + if (retval || usb_bytes_written != i) { + mutex_unlock(&ni_priv->addressed_transfer_lock); + dev_err(&usb_dev->dev, "send_bulk_msg returned %i, usb_bytes_written=%i, i=%i\n", + retval, usb_bytes_written, i); + return retval; + } + + in_data = kmalloc(in_data_length, GFP_KERNEL); + if (!in_data) { + mutex_unlock(&ni_priv->addressed_transfer_lock); + return -ENOMEM; + } + retval = ni_usb_receive_bulk_msg(ni_priv, in_data, in_data_length, &usb_bytes_read, + ni_usb_timeout_msecs(board->usec_timeout), 1); + + mutex_unlock(&ni_priv->addressed_transfer_lock); + + if ((retval && retval != -ERESTARTSYS) || usb_bytes_read != 12) { + dev_err(&usb_dev->dev, "receive_bulk_msg returned %i, usb_bytes_read=%i\n", + retval, usb_bytes_read); + kfree(in_data); + return retval; + } + ni_usb_parse_status_block(in_data, &status); + kfree(in_data); + switch (status.error_code) { + case NIUSB_NO_ERROR: + retval = 0; + break; + case NIUSB_ABORTED_ERROR: + /* + * this is expected if ni_usb_receive_bulk_msg got + * interrupted by a signal and returned -ERESTARTSYS + */ + break; + case NIUSB_ADDRESSING_ERROR: + dev_err(&usb_dev->dev, "Addressing error retval %d error code=%i\n", + retval, status.error_code); + retval = -ENXIO; + break; + case NIUSB_NO_LISTENER_ERROR: + retval = -ECOMM; + break; + case NIUSB_TIMEOUT_ERROR: + retval = -ETIMEDOUT; + break; + default: + dev_err(&usb_dev->dev, "unknown error code=%i\n", status.error_code); + retval = -EPIPE; + break; + } + ni_usb_soft_update_status(board, status.ibsta, 0); + *bytes_written = length - status.count; + return retval; +} + +static int ni_usb_command_chunk(struct gpib_board *board, u8 *buffer, size_t length, + size_t *command_bytes_written) +{ + int retval; + struct ni_usb_priv *ni_priv = board->private_data; + struct usb_device *usb_dev; + u8 *out_data, *in_data; + int out_data_length; + static const int in_data_length = 0x10; + int bytes_written = 0, bytes_read = 0; + int i = 0, j; + unsigned int complement_count; + struct ni_usb_status_block status; + // usb-b gives error 4 if you try to send more than 16 command bytes at once + static const int max_command_length = 0x10; + + *command_bytes_written = 0; + if (!ni_priv->bus_interface) + return -ENODEV; + if (length > max_command_length) + length = max_command_length; + usb_dev = interface_to_usbdev(ni_priv->bus_interface); + out_data_length = length + 0x10; + out_data = kmalloc(out_data_length, GFP_KERNEL); + if (!out_data) + return -ENOMEM; + out_data[i++] = 0x0c; + complement_count = length - 1; + complement_count = ~complement_count; + out_data[i++] = complement_count; + out_data[i++] = 0x0; + out_data[i++] = ni_usb_timeout_code(board->usec_timeout); + for (j = 0; j < length; j++) + out_data[i++] = buffer[j]; + while (i % 4) // pad with zeros to 4-byte boundary + out_data[i++] = 0x0; + i += ni_usb_bulk_termination(&out_data[i]); + + mutex_lock(&ni_priv->addressed_transfer_lock); + + retval = ni_usb_send_bulk_msg(ni_priv, out_data, i, &bytes_written, + ni_usb_timeout_msecs(board->usec_timeout)); + kfree(out_data); + if (retval || bytes_written != i) { + mutex_unlock(&ni_priv->addressed_transfer_lock); + dev_err(&usb_dev->dev, "send_bulk_msg returned %i, bytes_written=%i, i=%i\n", + retval, bytes_written, i); + return retval; + } + + in_data = kmalloc(in_data_length, GFP_KERNEL); + if (!in_data) { + mutex_unlock(&ni_priv->addressed_transfer_lock); + return -ENOMEM; + } + + retval = ni_usb_receive_bulk_msg(ni_priv, in_data, in_data_length, &bytes_read, + ni_usb_timeout_msecs(board->usec_timeout), 1); + + mutex_unlock(&ni_priv->addressed_transfer_lock); + + if ((retval && retval != -ERESTARTSYS) || bytes_read != 12) { + dev_err(&usb_dev->dev, "receive_bulk_msg returned %i, bytes_read=%i\n", + retval, bytes_read); + kfree(in_data); + return retval; + } + ni_usb_parse_status_block(in_data, &status); + kfree(in_data); + *command_bytes_written = length - status.count; + switch (status.error_code) { + case NIUSB_NO_ERROR: + break; + case NIUSB_ABORTED_ERROR: + /* + * this is expected if ni_usb_receive_bulk_msg got + * interrupted by a signal and returned -ERESTARTSYS + */ + break; + case NIUSB_NO_BUS_ERROR: + return -ENOTCONN; + case NIUSB_EOSMODE_ERROR: + dev_err(&usb_dev->dev, "got eosmode error. Driver bug?\n"); + return -EIO; + case NIUSB_TIMEOUT_ERROR: + return -ETIMEDOUT; + default: + dev_err(&usb_dev->dev, "unknown error code=%i\n", status.error_code); + return -EIO; + } + ni_usb_soft_update_status(board, status.ibsta, 0); + return 0; +} + +static int ni_usb_command(struct gpib_board *board, u8 *buffer, size_t length, + size_t *bytes_written) +{ + size_t count; + int retval; + + *bytes_written = 0; + while (*bytes_written < length) { + retval = ni_usb_command_chunk(board, buffer + *bytes_written, + length - *bytes_written, &count); + *bytes_written += count; + if (retval < 0) + return retval; + } + return 0; +} + +static int ni_usb_take_control(struct gpib_board *board, int synchronous) +{ + int retval; + struct ni_usb_priv *ni_priv = board->private_data; + struct usb_device *usb_dev; + u8 *out_data, *in_data; + static const int out_data_length = 0x10; + static const int in_data_length = 0x10; + int bytes_written = 0, bytes_read = 0; + int i = 0; + struct ni_usb_status_block status; + + if (!ni_priv->bus_interface) + return -ENODEV; + usb_dev = interface_to_usbdev(ni_priv->bus_interface); + out_data = kmalloc(out_data_length, GFP_KERNEL); + if (!out_data) + return -ENOMEM; + out_data[i++] = NIUSB_IBCAC_ID; + if (synchronous) + out_data[i++] = 0x1; + else + out_data[i++] = 0x0; + out_data[i++] = 0x0; + out_data[i++] = 0x0; + i += ni_usb_bulk_termination(&out_data[i]); + + mutex_lock(&ni_priv->addressed_transfer_lock); + + retval = ni_usb_send_bulk_msg(ni_priv, out_data, i, &bytes_written, 1000); + kfree(out_data); + if (retval || bytes_written != i) { + mutex_unlock(&ni_priv->addressed_transfer_lock); + dev_err(&usb_dev->dev, "send_bulk_msg returned %i, bytes_written=%i, i=%i\n", + retval, bytes_written, i); + return retval; + } + + in_data = kmalloc(in_data_length, GFP_KERNEL); + if (!in_data) { + mutex_unlock(&ni_priv->addressed_transfer_lock); + return -ENOMEM; + } + retval = ni_usb_receive_bulk_msg(ni_priv, in_data, in_data_length, &bytes_read, 1000, 1); + + mutex_unlock(&ni_priv->addressed_transfer_lock); + + if ((retval && retval != -ERESTARTSYS) || bytes_read != 12) { + if (retval == 0) + retval = -EIO; + dev_err(&usb_dev->dev, "receive_bulk_msg returned %i, bytes_read=%i\n", + retval, bytes_read); + kfree(in_data); + return retval; + } + ni_usb_parse_status_block(in_data, &status); + kfree(in_data); + ni_usb_soft_update_status(board, status.ibsta, 0); + return retval; +} + +static int ni_usb_go_to_standby(struct gpib_board *board) +{ + int retval; + struct ni_usb_priv *ni_priv = board->private_data; + struct usb_device *usb_dev; + u8 *out_data, *in_data; + static const int out_data_length = 0x10; + static const int in_data_length = 0x20; + int bytes_written = 0, bytes_read = 0; + int i = 0; + struct ni_usb_status_block status; + + if (!ni_priv->bus_interface) + return -ENODEV; + usb_dev = interface_to_usbdev(ni_priv->bus_interface); + out_data = kmalloc(out_data_length, GFP_KERNEL); + if (!out_data) + return -ENOMEM; + + out_data[i++] = NIUSB_IBGTS_ID; + out_data[i++] = 0x0; + out_data[i++] = 0x0; + out_data[i++] = 0x0; + i += ni_usb_bulk_termination(&out_data[i]); + + mutex_lock(&ni_priv->addressed_transfer_lock); + + retval = ni_usb_send_bulk_msg(ni_priv, out_data, i, &bytes_written, 1000); + kfree(out_data); + if (retval || bytes_written != i) { + mutex_unlock(&ni_priv->addressed_transfer_lock); + dev_err(&usb_dev->dev, "send_bulk_msg returned %i, bytes_written=%i, i=%i\n", + retval, bytes_written, i); + return retval; + } + + in_data = kmalloc(in_data_length, GFP_KERNEL); + if (!in_data) { + mutex_unlock(&ni_priv->addressed_transfer_lock); + return -ENOMEM; + } + retval = ni_usb_receive_bulk_msg(ni_priv, in_data, in_data_length, &bytes_read, 1000, 0); + + mutex_unlock(&ni_priv->addressed_transfer_lock); + + if (retval || bytes_read != 12) { + dev_err(&usb_dev->dev, "receive_bulk_msg returned %i, bytes_read=%i\n", + retval, bytes_read); + kfree(in_data); + return retval; + } + ni_usb_parse_status_block(in_data, &status); + kfree(in_data); + if (status.id != NIUSB_IBGTS_ID) + dev_err(&usb_dev->dev, "bug: status.id 0x%x != INUSB_IBGTS_ID\n", status.id); + ni_usb_soft_update_status(board, status.ibsta, 0); + return 0; +} + +static int ni_usb_request_system_control(struct gpib_board *board, int request_control) +{ + int retval; + struct ni_usb_priv *ni_priv = board->private_data; + struct usb_device *usb_dev; + int i = 0; + struct ni_usb_register writes[4]; + unsigned int ibsta; + + if (!ni_priv->bus_interface) + return -ENODEV; + usb_dev = interface_to_usbdev(ni_priv->bus_interface); + if (request_control) { + writes[i].device = NIUSB_SUBDEV_TNT4882; + writes[i].address = CMDR; + writes[i].value = SETSC; + i++; + writes[i].device = NIUSB_SUBDEV_TNT4882; + writes[i].address = nec7210_to_tnt4882_offset(AUXMR); + writes[i].value = AUX_CIFC; + i++; + } else { + writes[i].device = NIUSB_SUBDEV_TNT4882; + writes[i].address = nec7210_to_tnt4882_offset(AUXMR); + writes[i].value = AUX_CREN; + i++; + writes[i].device = NIUSB_SUBDEV_TNT4882; + writes[i].address = nec7210_to_tnt4882_offset(AUXMR); + writes[i].value = AUX_CIFC; + i++; + writes[i].device = NIUSB_SUBDEV_TNT4882; + writes[i].address = nec7210_to_tnt4882_offset(AUXMR); + writes[i].value = AUX_DSC; + i++; + writes[i].device = NIUSB_SUBDEV_TNT4882; + writes[i].address = CMDR; + writes[i].value = CLRSC; + i++; + } + retval = ni_usb_write_registers(ni_priv, writes, i, &ibsta); + if (retval < 0) { + dev_err(&usb_dev->dev, "register write failed, retval=%i\n", retval); + return retval; + } + if (!request_control) + ni_priv->ren_state = 0; + ni_usb_soft_update_status(board, ibsta, 0); + return 0; +} + +// FIXME maybe the interface should have a "pulse interface clear" function that can return an error? +static void ni_usb_interface_clear(struct gpib_board *board, int assert) +{ + int retval; + struct ni_usb_priv *ni_priv = board->private_data; + struct usb_device *usb_dev; + u8 *out_data, *in_data; + static const int out_data_length = 0x10; + static const int in_data_length = 0x10; + int bytes_written = 0, bytes_read = 0; + int i = 0; + struct ni_usb_status_block status; + + if (!ni_priv->bus_interface) + return; // -ENODEV; + usb_dev = interface_to_usbdev(ni_priv->bus_interface); +// FIXME: we are going to pulse when assert is true, and ignore otherwise + if (assert == 0) + return; + out_data = kmalloc(out_data_length, GFP_KERNEL); + if (!out_data) + return; + out_data[i++] = NIUSB_IBSIC_ID; + out_data[i++] = 0x0; + out_data[i++] = 0x0; + out_data[i++] = 0x0; + i += ni_usb_bulk_termination(&out_data[i]); + retval = ni_usb_send_bulk_msg(ni_priv, out_data, i, &bytes_written, 1000); + kfree(out_data); + if (retval || bytes_written != i) { + dev_err(&usb_dev->dev, "send_bulk_msg returned %i, bytes_written=%i, i=%i\n", + retval, bytes_written, i); + return; + } + in_data = kmalloc(in_data_length, GFP_KERNEL); + if (!in_data) + return; + + retval = ni_usb_receive_bulk_msg(ni_priv, in_data, in_data_length, &bytes_read, 1000, 0); + if (retval || bytes_read != 12) { + dev_err(&usb_dev->dev, "receive_bulk_msg returned %i, bytes_read=%i\n", + retval, bytes_read); + kfree(in_data); + return; + } + ni_usb_parse_status_block(in_data, &status); + kfree(in_data); + ni_usb_soft_update_status(board, status.ibsta, 0); +} + +static void ni_usb_remote_enable(struct gpib_board *board, int enable) +{ + int retval; + struct ni_usb_priv *ni_priv = board->private_data; + struct usb_device *usb_dev; + struct ni_usb_register reg; + unsigned int ibsta; + + if (!ni_priv->bus_interface) + return; // -ENODEV; + usb_dev = interface_to_usbdev(ni_priv->bus_interface); + reg.device = NIUSB_SUBDEV_TNT4882; + reg.address = nec7210_to_tnt4882_offset(AUXMR); + if (enable) + reg.value = AUX_SREN; + else + reg.value = AUX_CREN; + retval = ni_usb_write_registers(ni_priv, ®, 1, &ibsta); + if (retval < 0) { + dev_err(&usb_dev->dev, "register write failed, retval=%i\n", retval); + return; //retval; + } + ni_priv->ren_state = enable; + ni_usb_soft_update_status(board, ibsta, 0); + return;// 0; +} + +static int ni_usb_enable_eos(struct gpib_board *board, u8 eos_byte, int compare_8_bits) +{ + struct ni_usb_priv *ni_priv = board->private_data; + + ni_priv->eos_char = eos_byte; + ni_priv->eos_mode |= REOS; + if (compare_8_bits) + ni_priv->eos_mode |= BIN; + else + ni_priv->eos_mode &= ~BIN; + return 0; +} + +static void ni_usb_disable_eos(struct gpib_board *board) +{ + struct ni_usb_priv *ni_priv = board->private_data; + /* + * adapter gets unhappy if you don't zero all the bits + * for the eos mode and eos char (returns error 4 on reads). + */ + ni_priv->eos_mode = 0; + ni_priv->eos_char = 0; +} + +static unsigned int ni_usb_update_status(struct gpib_board *board, unsigned int clear_mask) +{ + int retval; + struct ni_usb_priv *ni_priv = board->private_data; + struct usb_device *usb_dev; + static const int buffer_length = 8; + u8 *buffer; + struct ni_usb_status_block status; + + if (!ni_priv->bus_interface) + return -ENODEV; + usb_dev = interface_to_usbdev(ni_priv->bus_interface); + buffer = kmalloc(buffer_length, GFP_KERNEL); + if (!buffer) + return board->status; + + retval = ni_usb_receive_control_msg(ni_priv, NI_USB_WAIT_REQUEST, USB_DIR_IN | + USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x200, 0x0, buffer, buffer_length, 1000); + if (retval != buffer_length) { + dev_err(&usb_dev->dev, "usb_control_msg returned %i\n", retval); + kfree(buffer); + return board->status; + } + ni_usb_parse_status_block(buffer, &status); + kfree(buffer); + ni_usb_soft_update_status(board, status.ibsta, clear_mask); + return board->status; +} + +// tells ni-usb to immediately stop an ongoing i/o operation +static void ni_usb_stop(struct ni_usb_priv *ni_priv) +{ + struct usb_device *usb_dev = interface_to_usbdev(ni_priv->bus_interface); + int retval; + static const int buffer_length = 8; + u8 *buffer; + struct ni_usb_status_block status; + + buffer = kmalloc(buffer_length, GFP_KERNEL); + if (!buffer) + return; + + retval = ni_usb_receive_control_msg(ni_priv, NI_USB_STOP_REQUEST, USB_DIR_IN | + USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0, 0x0, buffer, buffer_length, 1000); + if (retval != buffer_length) { + dev_err(&usb_dev->dev, "usb_control_msg returned %i\n", retval); + kfree(buffer); + return; + } + ni_usb_parse_status_block(buffer, &status); + kfree(buffer); +} + +static int ni_usb_primary_address(struct gpib_board *board, unsigned int address) +{ + int retval; + struct ni_usb_priv *ni_priv = board->private_data; + struct usb_device *usb_dev; + int i = 0; + struct ni_usb_register writes[2]; + unsigned int ibsta; + + if (!ni_priv->bus_interface) + return -ENODEV; + usb_dev = interface_to_usbdev(ni_priv->bus_interface); + writes[i].device = NIUSB_SUBDEV_TNT4882; + writes[i].address = nec7210_to_tnt4882_offset(ADR); + writes[i].value = address; + i++; + writes[i].device = NIUSB_SUBDEV_UNKNOWN2; + writes[i].address = 0x0; + writes[i].value = address; + i++; + retval = ni_usb_write_registers(ni_priv, writes, i, &ibsta); + if (retval < 0) { + dev_err(&usb_dev->dev, "register write failed, retval=%i\n", retval); + return retval; + } + ni_usb_soft_update_status(board, ibsta, 0); + return 0; +} + +static int ni_usb_write_sad(struct ni_usb_register *writes, int address, int enable) +{ + unsigned int adr_bits, admr_bits; + int i = 0; + + adr_bits = HR_ARS; + admr_bits = HR_TRM0 | HR_TRM1; + if (enable) { + adr_bits |= address; + admr_bits |= HR_ADM1; + } else { + adr_bits |= HR_DT | HR_DL; + admr_bits |= HR_ADM0; + } + writes[i].device = NIUSB_SUBDEV_TNT4882; + writes[i].address = nec7210_to_tnt4882_offset(ADR); + writes[i].value = adr_bits; + i++; + writes[i].device = NIUSB_SUBDEV_TNT4882; + writes[i].address = nec7210_to_tnt4882_offset(ADMR); + writes[i].value = admr_bits; + i++; + writes[i].device = NIUSB_SUBDEV_UNKNOWN2; + writes[i].address = 0x1; + writes[i].value = enable ? MSA(address) : 0x0; + i++; + return i; +} + +static int ni_usb_secondary_address(struct gpib_board *board, unsigned int address, int enable) +{ + int retval; + struct ni_usb_priv *ni_priv = board->private_data; + struct usb_device *usb_dev; + int i = 0; + struct ni_usb_register writes[3]; + unsigned int ibsta; + + if (!ni_priv->bus_interface) + return -ENODEV; + usb_dev = interface_to_usbdev(ni_priv->bus_interface); + i += ni_usb_write_sad(writes, address, enable); + retval = ni_usb_write_registers(ni_priv, writes, i, &ibsta); + if (retval < 0) { + dev_err(&usb_dev->dev, "register write failed, retval=%i\n", retval); + return retval; + } + ni_usb_soft_update_status(board, ibsta, 0); + return 0; +} + +static int ni_usb_parallel_poll(struct gpib_board *board, u8 *result) +{ + int retval; + struct ni_usb_priv *ni_priv = board->private_data; + struct usb_device *usb_dev; + u8 *out_data, *in_data; + static const int out_data_length = 0x10; + static const int in_data_length = 0x20; + int bytes_written = 0, bytes_read = 0; + int i = 0; + int j = 0; + struct ni_usb_status_block status; + + if (!ni_priv->bus_interface) + return -ENODEV; + usb_dev = interface_to_usbdev(ni_priv->bus_interface); + out_data = kmalloc(out_data_length, GFP_KERNEL); + if (!out_data) + return -ENOMEM; + + out_data[i++] = NIUSB_IBRPP_ID; + out_data[i++] = 0xf0; // FIXME: this should be the parallel poll timeout code + out_data[i++] = 0x0; + out_data[i++] = 0x0; + i += ni_usb_bulk_termination(&out_data[i]); + /*FIXME: 1000 should use parallel poll timeout (not supported yet)*/ + retval = ni_usb_send_bulk_msg(ni_priv, out_data, i, &bytes_written, 1000); + + kfree(out_data); + if (retval || bytes_written != i) { + dev_err(&usb_dev->dev, "send_bulk_msg returned %i, bytes_written=%i, i=%i\n", + retval, bytes_written, i); + return retval; + } + in_data = kmalloc(in_data_length, GFP_KERNEL); + if (!in_data) + return -ENOMEM; + + /*FIXME: should use parallel poll timeout (not supported yet)*/ + retval = ni_usb_receive_bulk_msg(ni_priv, in_data, in_data_length, + &bytes_read, 1000, 1); + + if (retval && retval != -ERESTARTSYS) { + dev_err(&usb_dev->dev, "receive_bulk_msg returned %i, bytes_read=%i\n", + retval, bytes_read); + kfree(in_data); + return retval; + } + j += ni_usb_parse_status_block(in_data, &status); + *result = in_data[j++]; + kfree(in_data); + ni_usb_soft_update_status(board, status.ibsta, 0); + return retval; +} + +static void ni_usb_parallel_poll_configure(struct gpib_board *board, u8 config) +{ + int retval; + struct ni_usb_priv *ni_priv = board->private_data; + struct usb_device *usb_dev; + int i = 0; + struct ni_usb_register writes[1]; + unsigned int ibsta; + + if (!ni_priv->bus_interface) + return; // -ENODEV; + usb_dev = interface_to_usbdev(ni_priv->bus_interface); + writes[i].device = NIUSB_SUBDEV_TNT4882; + writes[i].address = nec7210_to_tnt4882_offset(AUXMR); + writes[i].value = PPR | config; + i++; + retval = ni_usb_write_registers(ni_priv, writes, i, &ibsta); + if (retval < 0) { + dev_err(&usb_dev->dev, "register write failed, retval=%i\n", retval); + return;// retval; + } + ni_usb_soft_update_status(board, ibsta, 0); + return;// 0; +} + +static void ni_usb_parallel_poll_response(struct gpib_board *board, int ist) +{ + int retval; + struct ni_usb_priv *ni_priv = board->private_data; + struct usb_device *usb_dev; + int i = 0; + struct ni_usb_register writes[1]; + unsigned int ibsta; + + if (!ni_priv->bus_interface) + return; // -ENODEV; + usb_dev = interface_to_usbdev(ni_priv->bus_interface); + writes[i].device = NIUSB_SUBDEV_TNT4882; + writes[i].address = nec7210_to_tnt4882_offset(AUXMR); + if (ist) + writes[i].value = AUX_SPPF; + else + writes[i].value = AUX_CPPF; + i++; + retval = ni_usb_write_registers(ni_priv, writes, i, &ibsta); + if (retval < 0) { + dev_err(&usb_dev->dev, "register write failed, retval=%i\n", retval); + return;// retval; + } + ni_usb_soft_update_status(board, ibsta, 0); + return;// 0; +} + +static void ni_usb_serial_poll_response(struct gpib_board *board, u8 status) +{ + int retval; + struct ni_usb_priv *ni_priv = board->private_data; + struct usb_device *usb_dev; + int i = 0; + struct ni_usb_register writes[1]; + unsigned int ibsta; + + if (!ni_priv->bus_interface) + return; // -ENODEV; + usb_dev = interface_to_usbdev(ni_priv->bus_interface); + writes[i].device = NIUSB_SUBDEV_TNT4882; + writes[i].address = nec7210_to_tnt4882_offset(SPMR); + writes[i].value = status; + i++; + retval = ni_usb_write_registers(ni_priv, writes, i, &ibsta); + if (retval < 0) { + dev_err(&usb_dev->dev, "register write failed, retval=%i\n", retval); + return;// retval; + } + ni_usb_soft_update_status(board, ibsta, 0); + return;// 0; +} + +static u8 ni_usb_serial_poll_status(struct gpib_board *board) +{ + return 0; +} + +static void ni_usb_return_to_local(struct gpib_board *board) +{ + int retval; + struct ni_usb_priv *ni_priv = board->private_data; + struct usb_device *usb_dev; + int i = 0; + struct ni_usb_register writes[1]; + unsigned int ibsta; + + if (!ni_priv->bus_interface) + return; // -ENODEV; + usb_dev = interface_to_usbdev(ni_priv->bus_interface); + writes[i].device = NIUSB_SUBDEV_TNT4882; + writes[i].address = nec7210_to_tnt4882_offset(AUXMR); + writes[i].value = AUX_RTL; + i++; + retval = ni_usb_write_registers(ni_priv, writes, i, &ibsta); + if (retval < 0) { + dev_err(&usb_dev->dev, "register write failed, retval=%i\n", retval); + return;// retval; + } + ni_usb_soft_update_status(board, ibsta, 0); + return;// 0; +} + +static int ni_usb_line_status(const struct gpib_board *board) +{ + int retval; + struct ni_usb_priv *ni_priv = board->private_data; + struct usb_device *usb_dev; + u8 *out_data, *in_data; + static const int out_data_length = 0x20; + static const int in_data_length = 0x20; + int bytes_written = 0, bytes_read = 0; + int i = 0; + unsigned int bsr_bits; + int line_status = VALID_ALL; + // NI windows driver reads 0xd(HSSEL), 0xc (ARD0), 0x1f (BSR) + + if (!ni_priv->bus_interface) + return -ENODEV; + usb_dev = interface_to_usbdev(ni_priv->bus_interface); + out_data = kmalloc(out_data_length, GFP_KERNEL); + if (!out_data) + return -ENOMEM; + + /* line status gets called during ibwait */ + retval = mutex_trylock(&ni_priv->addressed_transfer_lock); + + if (retval == 0) { + kfree(out_data); + return -EBUSY; + } + i += ni_usb_bulk_register_read_header(&out_data[i], 1); + i += ni_usb_bulk_register_read(&out_data[i], NIUSB_SUBDEV_TNT4882, BSR); + while (i % 4) + out_data[i++] = 0x0; + i += ni_usb_bulk_termination(&out_data[i]); + retval = ni_usb_nonblocking_send_bulk_msg(ni_priv, out_data, i, &bytes_written, 1000); + kfree(out_data); + if (retval || bytes_written != i) { + mutex_unlock(&ni_priv->addressed_transfer_lock); + if (retval != -EAGAIN) + dev_err(&usb_dev->dev, "send_bulk_msg returned %i, bytes_written=%i, i=%i\n", + retval, bytes_written, i); + return retval; + } + + in_data = kmalloc(in_data_length, GFP_KERNEL); + if (!in_data) { + mutex_unlock(&ni_priv->addressed_transfer_lock); + return -ENOMEM; + } + retval = ni_usb_nonblocking_receive_bulk_msg(ni_priv, in_data, in_data_length, + &bytes_read, 1000, 0); + + mutex_unlock(&ni_priv->addressed_transfer_lock); + + if (retval) { + if (retval != -EAGAIN) + dev_err(&usb_dev->dev, "receive_bulk_msg returned %i, bytes_read=%i\n", + retval, bytes_read); + kfree(in_data); + return retval; + } + + ni_usb_parse_register_read_block(in_data, &bsr_bits, 1); + kfree(in_data); + if (bsr_bits & BCSR_REN_BIT) + line_status |= BUS_REN; + if (bsr_bits & BCSR_IFC_BIT) + line_status |= BUS_IFC; + if (bsr_bits & BCSR_SRQ_BIT) + line_status |= BUS_SRQ; + if (bsr_bits & BCSR_EOI_BIT) + line_status |= BUS_EOI; + if (bsr_bits & BCSR_NRFD_BIT) + line_status |= BUS_NRFD; + if (bsr_bits & BCSR_NDAC_BIT) + line_status |= BUS_NDAC; + if (bsr_bits & BCSR_DAV_BIT) + line_status |= BUS_DAV; + if (bsr_bits & BCSR_ATN_BIT) + line_status |= BUS_ATN; + return line_status; +} + +static int ni_usb_setup_t1_delay(struct ni_usb_register *reg, unsigned int nano_sec, + unsigned int *actual_ns) +{ + int i = 0; + + *actual_ns = 2000; + + reg[i].device = NIUSB_SUBDEV_TNT4882; + reg[i].address = nec7210_to_tnt4882_offset(AUXMR); + if (nano_sec <= 1100) { + reg[i].value = AUXRI | USTD | SISB; + *actual_ns = 1100; + } else { + reg[i].value = AUXRI | SISB; + } + i++; + reg[i].device = NIUSB_SUBDEV_TNT4882; + reg[i].address = nec7210_to_tnt4882_offset(AUXMR); + if (nano_sec <= 500) { + reg[i].value = AUXRB | HR_TRI; + *actual_ns = 500; + } else { + reg[i].value = AUXRB; + } + i++; + reg[i].device = NIUSB_SUBDEV_TNT4882; + reg[i].address = KEYREG; + if (nano_sec <= 350) { + reg[i].value = MSTD; + *actual_ns = 350; + } else { + reg[i].value = 0x0; + } + i++; + return i; +} + +static int ni_usb_t1_delay(struct gpib_board *board, unsigned int nano_sec) +{ + int retval; + struct ni_usb_priv *ni_priv = board->private_data; + struct usb_device *usb_dev; + struct ni_usb_register writes[3]; + unsigned int ibsta; + unsigned int actual_ns; + int i; + + if (!ni_priv->bus_interface) + return -ENODEV; + usb_dev = interface_to_usbdev(ni_priv->bus_interface); + i = ni_usb_setup_t1_delay(writes, nano_sec, &actual_ns); + retval = ni_usb_write_registers(ni_priv, writes, i, &ibsta); + if (retval < 0) { + dev_err(&usb_dev->dev, "register write failed, retval=%i\n", retval); + return retval; + } + board->t1_nano_sec = actual_ns; + ni_usb_soft_update_status(board, ibsta, 0); + return actual_ns; +} + +static int ni_usb_allocate_private(struct gpib_board *board) +{ + struct ni_usb_priv *ni_priv; + + board->private_data = kmalloc(sizeof(struct ni_usb_priv), GFP_KERNEL); + if (!board->private_data) + return -ENOMEM; + ni_priv = board->private_data; + memset(ni_priv, 0, sizeof(struct ni_usb_priv)); + mutex_init(&ni_priv->bulk_transfer_lock); + mutex_init(&ni_priv->control_transfer_lock); + mutex_init(&ni_priv->interrupt_transfer_lock); + mutex_init(&ni_priv->addressed_transfer_lock); + return 0; +} + +static void ni_usb_free_private(struct ni_usb_priv *ni_priv) +{ + usb_free_urb(ni_priv->interrupt_urb); + kfree(ni_priv); +} + +#define NUM_INIT_WRITES 26 +static int ni_usb_setup_init(struct gpib_board *board, struct ni_usb_register *writes) +{ + struct ni_usb_priv *ni_priv = board->private_data; + struct usb_device *usb_dev = interface_to_usbdev(ni_priv->bus_interface); + unsigned int mask, actual_ns; + int i = 0; + + writes[i].device = NIUSB_SUBDEV_UNKNOWN3; + writes[i].address = 0x10; + writes[i].value = 0x0; + i++; + writes[i].device = NIUSB_SUBDEV_TNT4882; + writes[i].address = CMDR; + writes[i].value = SOFT_RESET; + i++; + writes[i].device = NIUSB_SUBDEV_TNT4882; + writes[i].address = nec7210_to_tnt4882_offset(AUXMR); + mask = AUXRA | HR_HLDA; + if (ni_priv->eos_mode & BIN) + mask |= HR_BIN; + writes[i].value = mask; + i++; + writes[i].device = NIUSB_SUBDEV_TNT4882; + writes[i].address = AUXCR; + writes[i].value = mask; + i++; + writes[i].device = NIUSB_SUBDEV_TNT4882; + writes[i].address = HSSEL; + writes[i].value = TNT_ONE_CHIP_BIT; + i++; + writes[i].device = NIUSB_SUBDEV_TNT4882; + writes[i].address = nec7210_to_tnt4882_offset(AUXMR); + writes[i].value = AUX_CR; + i++; + writes[i].device = NIUSB_SUBDEV_TNT4882; + writes[i].address = IMR0; + writes[i].value = TNT_IMR0_ALWAYS_BITS; + i++; + writes[i].device = NIUSB_SUBDEV_TNT4882; + writes[i].address = nec7210_to_tnt4882_offset(IMR1); + writes[i].value = 0x0; + i++; + writes[i].device = NIUSB_SUBDEV_TNT4882; + writes[i].address = nec7210_to_tnt4882_offset(IMR2); + writes[i].value = 0x0; + i++; + writes[i].device = NIUSB_SUBDEV_TNT4882; + writes[i].address = IMR3; + writes[i].value = 0x0; + i++; + writes[i].device = NIUSB_SUBDEV_TNT4882; + writes[i].address = nec7210_to_tnt4882_offset(AUXMR); + writes[i].value = AUX_HLDI; + i++; + + i += ni_usb_setup_t1_delay(&writes[i], board->t1_nano_sec, &actual_ns); + + writes[i].device = NIUSB_SUBDEV_TNT4882; + writes[i].address = nec7210_to_tnt4882_offset(AUXMR); + writes[i].value = AUXRG | NTNL_BIT; + i++; + writes[i].device = NIUSB_SUBDEV_TNT4882; + writes[i].address = CMDR; + if (board->master) + mask = SETSC; // set system controller + else + mask = CLRSC; // clear system controller + writes[i].value = mask; + i++; + writes[i].device = NIUSB_SUBDEV_TNT4882; + writes[i].address = nec7210_to_tnt4882_offset(AUXMR); + writes[i].value = AUX_CIFC; + i++; + writes[i].device = NIUSB_SUBDEV_TNT4882; + writes[i].address = nec7210_to_tnt4882_offset(ADR); + writes[i].value = board->pad; + i++; + writes[i].device = NIUSB_SUBDEV_UNKNOWN2; + writes[i].address = 0x0; + writes[i].value = board->pad; + i++; + + i += ni_usb_write_sad(&writes[i], board->sad, board->sad >= 0); + + writes[i].device = NIUSB_SUBDEV_UNKNOWN2; + writes[i].address = 0x2; // could this be a timeout ? + writes[i].value = 0xfd; + i++; + writes[i].device = NIUSB_SUBDEV_TNT4882; + writes[i].address = 0xf; // undocumented address + writes[i].value = 0x11; + i++; + writes[i].device = NIUSB_SUBDEV_TNT4882; + writes[i].address = nec7210_to_tnt4882_offset(AUXMR); + writes[i].value = AUX_PON; + i++; + writes[i].device = NIUSB_SUBDEV_TNT4882; + writes[i].address = nec7210_to_tnt4882_offset(AUXMR); + writes[i].value = AUX_CPPF; + i++; + if (i > NUM_INIT_WRITES) { + dev_err(&usb_dev->dev, "bug!, buffer overrun, i=%i\n", i); + return 0; + } + return i; +} + +static int ni_usb_init(struct gpib_board *board) +{ + int retval; + struct ni_usb_priv *ni_priv = board->private_data; + struct usb_device *usb_dev = interface_to_usbdev(ni_priv->bus_interface); + struct ni_usb_register *writes; + unsigned int ibsta; + int writes_len; + + writes = kmalloc_array(NUM_INIT_WRITES, sizeof(*writes), GFP_KERNEL); + if (!writes) + return -ENOMEM; + + writes_len = ni_usb_setup_init(board, writes); + if (writes_len) + retval = ni_usb_write_registers(ni_priv, writes, writes_len, &ibsta); + else + return -EFAULT; + kfree(writes); + if (retval) { + dev_err(&usb_dev->dev, "register write failed, retval=%i\n", retval); + return retval; + } + ni_usb_soft_update_status(board, ibsta, 0); + return 0; +} + +static void ni_usb_interrupt_complete(struct urb *urb) +{ + struct gpib_board *board = urb->context; + struct ni_usb_priv *ni_priv = board->private_data; + struct usb_device *usb_dev = interface_to_usbdev(ni_priv->bus_interface); + int retval; + struct ni_usb_status_block status; + unsigned long flags; + + switch (urb->status) { + /* success */ + case 0: + break; + /* unlinked, don't resubmit */ + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + return; + default: /* other error, resubmit */ + retval = usb_submit_urb(ni_priv->interrupt_urb, GFP_ATOMIC); + if (retval) + dev_err(&usb_dev->dev, "failed to resubmit interrupt urb\n"); + return; + } + + ni_usb_parse_status_block(urb->transfer_buffer, &status); + + spin_lock_irqsave(&board->spinlock, flags); + ni_priv->monitored_ibsta_bits &= ~status.ibsta; + spin_unlock_irqrestore(&board->spinlock, flags); + + wake_up_interruptible(&board->wait); + + retval = usb_submit_urb(ni_priv->interrupt_urb, GFP_ATOMIC); + if (retval) + dev_err(&usb_dev->dev, "failed to resubmit interrupt urb\n"); +} + +static int ni_usb_set_interrupt_monitor(struct gpib_board *board, unsigned int monitored_bits) +{ + int retval; + struct ni_usb_priv *ni_priv = board->private_data; + struct usb_device *usb_dev = interface_to_usbdev(ni_priv->bus_interface); + static const int buffer_length = 8; + u8 *buffer; + struct ni_usb_status_block status; + unsigned long flags; + + buffer = kmalloc(buffer_length, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + spin_lock_irqsave(&board->spinlock, flags); + ni_priv->monitored_ibsta_bits = ni_usb_ibsta_monitor_mask & monitored_bits; + spin_unlock_irqrestore(&board->spinlock, flags); + retval = ni_usb_receive_control_msg(ni_priv, NI_USB_WAIT_REQUEST, USB_DIR_IN | + USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x300, ni_usb_ibsta_monitor_mask & monitored_bits, + buffer, buffer_length, 1000); + if (retval != buffer_length) { + dev_err(&usb_dev->dev, "usb_control_msg returned %i\n", retval); + kfree(buffer); + return -1; + } + ni_usb_parse_status_block(buffer, &status); + kfree(buffer); + return 0; +} + +static int ni_usb_setup_urbs(struct gpib_board *board) +{ + struct ni_usb_priv *ni_priv = board->private_data; + struct usb_device *usb_dev; + int int_pipe; + int retval; + + if (ni_priv->interrupt_in_endpoint < 0) + return 0; + + mutex_lock(&ni_priv->interrupt_transfer_lock); + if (!ni_priv->bus_interface) { + mutex_unlock(&ni_priv->interrupt_transfer_lock); + return -ENODEV; + } + ni_priv->interrupt_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!ni_priv->interrupt_urb) { + mutex_unlock(&ni_priv->interrupt_transfer_lock); + return -ENOMEM; + } + usb_dev = interface_to_usbdev(ni_priv->bus_interface); + int_pipe = usb_rcvintpipe(usb_dev, ni_priv->interrupt_in_endpoint); + usb_fill_int_urb(ni_priv->interrupt_urb, usb_dev, int_pipe, ni_priv->interrupt_buffer, + sizeof(ni_priv->interrupt_buffer), &ni_usb_interrupt_complete, board, 1); + retval = usb_submit_urb(ni_priv->interrupt_urb, GFP_KERNEL); + mutex_unlock(&ni_priv->interrupt_transfer_lock); + if (retval) { + dev_err(&usb_dev->dev, "failed to submit first interrupt urb, retval=%i\n", retval); + return retval; + } + return 0; +} + +static void ni_usb_cleanup_urbs(struct ni_usb_priv *ni_priv) +{ + if (ni_priv && ni_priv->bus_interface) { + if (ni_priv->interrupt_urb) + usb_kill_urb(ni_priv->interrupt_urb); + if (ni_priv->bulk_urb) + usb_kill_urb(ni_priv->bulk_urb); + } +} + +static int ni_usb_b_read_serial_number(struct ni_usb_priv *ni_priv) +{ + struct usb_device *usb_dev = interface_to_usbdev(ni_priv->bus_interface); + int retval; + u8 *out_data; + u8 *in_data; + static const int out_data_length = 0x20; + static const int in_data_length = 0x20; + int bytes_written = 0, bytes_read = 0; + int i = 0; + static const int num_reads = 4; + unsigned int results[4]; + int j; + unsigned int serial_number; + + in_data = kmalloc(in_data_length, GFP_KERNEL); + if (!in_data) + return -ENOMEM; + + out_data = kmalloc(out_data_length, GFP_KERNEL); + if (!out_data) { + kfree(in_data); + return -ENOMEM; + } + i += ni_usb_bulk_register_read_header(&out_data[i], num_reads); + i += ni_usb_bulk_register_read(&out_data[i], NIUSB_SUBDEV_UNKNOWN3, SERIAL_NUMBER_1_REG); + i += ni_usb_bulk_register_read(&out_data[i], NIUSB_SUBDEV_UNKNOWN3, SERIAL_NUMBER_2_REG); + i += ni_usb_bulk_register_read(&out_data[i], NIUSB_SUBDEV_UNKNOWN3, SERIAL_NUMBER_3_REG); + i += ni_usb_bulk_register_read(&out_data[i], NIUSB_SUBDEV_UNKNOWN3, SERIAL_NUMBER_4_REG); + while (i % 4) + out_data[i++] = 0x0; + i += ni_usb_bulk_termination(&out_data[i]); + retval = ni_usb_send_bulk_msg(ni_priv, out_data, out_data_length, &bytes_written, 1000); + if (retval) { + dev_err(&usb_dev->dev, "send_bulk_msg returned %i, bytes_written=%i, i=%li\n", + retval, bytes_written, (long)out_data_length); + goto serial_out; + } + retval = ni_usb_receive_bulk_msg(ni_priv, in_data, in_data_length, &bytes_read, 1000, 0); + if (retval) { + dev_err(&usb_dev->dev, "receive_bulk_msg returned %i, bytes_read=%i\n", + retval, bytes_read); + ni_usb_dump_raw_block(in_data, bytes_read); + goto serial_out; + } + if (ARRAY_SIZE(results) < num_reads) { + dev_err(&usb_dev->dev, "serial number eetup bug\n"); + retval = -EINVAL; + goto serial_out; + } + ni_usb_parse_register_read_block(in_data, results, num_reads); + serial_number = 0; + for (j = 0; j < num_reads; ++j) + serial_number |= (results[j] & 0xff) << (8 * j); + dev_dbg(&usb_dev->dev, "board serial number is 0x%x\n", serial_number); + retval = 0; +serial_out: + kfree(in_data); + kfree(out_data); + return retval; +} + +static int ni_usb_hs_wait_for_ready(struct ni_usb_priv *ni_priv) +{ + struct usb_device *usb_dev = interface_to_usbdev(ni_priv->bus_interface); + static const int buffer_size = 0x10; + static const int timeout = 50; + static const int msec_sleep_duration = 100; + int i; int retval; + int j; + int unexpected = 0; + unsigned int serial_number; + u8 *buffer; + + buffer = kmalloc(buffer_size, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + retval = ni_usb_receive_control_msg(ni_priv, NI_USB_SERIAL_NUMBER_REQUEST, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0, 0x0, buffer, buffer_size, 1000); + if (retval < 0) { + dev_err(&usb_dev->dev, "usb_control_msg request 0x%x returned %i\n", + NI_USB_SERIAL_NUMBER_REQUEST, retval); + goto ready_out; + } + j = 0; + if (buffer[j] != NI_USB_SERIAL_NUMBER_REQUEST) { + dev_err(&usb_dev->dev, "unexpected data: buffer[%i]=0x%x, expected 0x%x\n", + j, (int)buffer[j], NI_USB_SERIAL_NUMBER_REQUEST); + unexpected = 1; + } + if (unexpected) + ni_usb_dump_raw_block(buffer, retval); + // NI-USB-HS+ pads the serial with 0x0 to make 16 bytes + if (retval != 5 && retval != 16) { + dev_err(&usb_dev->dev, "received unexpected number of bytes = %i, expected 5 or 16\n", + retval); + ni_usb_dump_raw_block(buffer, retval); + } + serial_number = 0; + serial_number |= buffer[++j]; + serial_number |= (buffer[++j] << 8); + serial_number |= (buffer[++j] << 16); + serial_number |= (buffer[++j] << 24); + dev_dbg(&usb_dev->dev, "board serial number is 0x%x\n", serial_number); + for (i = 0; i < timeout; ++i) { + int ready = 0; + + retval = ni_usb_receive_control_msg(ni_priv, NI_USB_POLL_READY_REQUEST, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0, 0x0, buffer, buffer_size, 100); + if (retval < 0) { + dev_err(&usb_dev->dev, "usb_control_msg request 0x%x returned %i\n", + NI_USB_POLL_READY_REQUEST, retval); + goto ready_out; + } + j = 0; + unexpected = 0; + if (buffer[j] != NI_USB_POLL_READY_REQUEST) { // [0] + dev_err(&usb_dev->dev, "unexpected data: buffer[%i]=0x%x, expected 0x%x\n", + j, (int)buffer[j], NI_USB_POLL_READY_REQUEST); + unexpected = 1; + } + ++j; + if (buffer[j] != 0x1 && buffer[j] != 0x0) { // [1] HS+ sends 0x0 + dev_err(&usb_dev->dev, "unexpected data: buffer[%i]=0x%x, expected 0x1 or 0x0\n", + j, (int)buffer[j]); + unexpected = 1; + } + if (buffer[++j] != 0x0) { // [2] + dev_err(&usb_dev->dev, "unexpected data: buffer[%i]=0x%x, expected 0x%x\n", + j, (int)buffer[j], 0x0); + unexpected = 1; + } + ++j; + /* + * MC usb-488 (and sometimes NI-USB-HS?) sends 0x8 here; MC usb-488A sends 0x7 here + * NI-USB-HS+ sends 0x0 + */ + if (buffer[j] != 0x1 && buffer[j] != 0x8 && buffer[j] != 0x7 && buffer[j] != 0x0) { + // [3] + dev_err(&usb_dev->dev, "unexpected data: buffer[%i]=0x%x, expected 0x0, 0x1, 0x7 or 0x8\n", + j, (int)buffer[j]); + unexpected = 1; + } + ++j; + // NI-USB-HS+ sends 0 here + if (buffer[j] != 0x30 && buffer[j] != 0x0) { // [4] + dev_err(&usb_dev->dev, "unexpected data: buffer[%i]=0x%x, expected 0x0 or 0x30\n", + j, (int)buffer[j]); + unexpected = 1; + } + ++j; + // MC usb-488 (and sometimes NI-USB-HS?) and NI-USB-HS+ sends 0x0 here + if (buffer[j] != 0x1 && buffer[j] != 0x0) { // [5] + dev_err(&usb_dev->dev, "unexpected data: buffer[%i]=0x%x, expected 0x1 or 0x0\n", + j, (int)buffer[j]); + unexpected = 1; + } + if (buffer[++j] != 0x0) { // [6] + ready = 1; + // NI-USB-HS+ sends 0xf or 0x19 here + if (buffer[j] != 0x2 && buffer[j] != 0xe && buffer[j] != 0xf && + buffer[j] != 0x16 && buffer[j] != 0x19) { + dev_err(&usb_dev->dev, "unexpected data: buffer[%i]=0x%x, expected 0x2, 0xe, 0xf, 0x16 or 0x19\n", + j, (int)buffer[j]); + unexpected = 1; + } + } + if (buffer[++j] != 0x0) { // [7] + ready = 1; + // MC usb-488 sends 0x5 here; MC usb-488A sends 0x6 here + if (buffer[j] != 0x3 && buffer[j] != 0x5 && buffer[j] != 0x6 && + buffer[j] != 0x8) { + dev_err(&usb_dev->dev, "unexpected data: buffer[%i]=0x%x, expected 0x3 or 0x5, 0x6 or 0x08\n", + j, (int)buffer[j]); + unexpected = 1; + } + } + ++j; + if (buffer[j] != 0x0 && buffer[j] != 0x2) { // [8] MC usb-488 sends 0x2 here + dev_err(&usb_dev->dev, " unexpected data: buffer[%i]=0x%x, expected 0x0 or 0x2\n", + j, (int)buffer[j]); + unexpected = 1; + } + ++j; + // MC usb-488A and NI-USB-HS sends 0x3 here; NI-USB-HS+ sends 0x30 here + if (buffer[j] != 0x0 && buffer[j] != 0x3 && buffer[j] != 0x30) { // [9] + dev_err(&usb_dev->dev, "unexpected data: buffer[%i]=0x%x, expected 0x0, 0x3 or 0x30\n", + j, (int)buffer[j]); + unexpected = 1; + } + if (buffer[++j] != 0x0) { // [10] MC usb-488 sends 0x7 here, new HS+ sends 0x59 + ready = 1; + if (buffer[j] != 0x96 && buffer[j] != 0x7 && buffer[j] != 0x6e && + buffer[j] != 0x59) { + dev_err(&usb_dev->dev, "unexpected data: buffer[%i]=0x%x, expected 0x96, 0x07, 0x6e or 0x59\n", + j, (int)buffer[j]); + unexpected = 1; + } + } + if (unexpected) + ni_usb_dump_raw_block(buffer, retval); + if (ready) + break; + retval = msleep_interruptible(msec_sleep_duration); + if (retval) { + retval = -ERESTARTSYS; + goto ready_out; + } + } + retval = 0; + +ready_out: + kfree(buffer); + dev_dbg(&usb_dev->dev, "exit retval=%d\n", retval); + return retval; +} + +/* + * This does some extra init for HS+ models, as observed on Windows. One of the + * control requests causes the LED to stop blinking. + * I'm not sure what the other 2 requests do. None of these requests are actually required + * for the adapter to work, maybe they do some init for the analyzer interface + * (which we don't use). + */ +static int ni_usb_hs_plus_extra_init(struct ni_usb_priv *ni_priv) +{ + struct usb_device *usb_dev = interface_to_usbdev(ni_priv->bus_interface); + int retval; + u8 *buffer; + static const int buffer_size = 16; + int transfer_size; + + buffer = kmalloc(buffer_size, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + do { + transfer_size = 16; + + retval = ni_usb_receive_control_msg(ni_priv, NI_USB_HS_PLUS_0x48_REQUEST, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x0, 0x0, buffer, transfer_size, 1000); + if (retval < 0) { + dev_err(&usb_dev->dev, "usb_control_msg request 0x%x returned %i\n", + NI_USB_HS_PLUS_0x48_REQUEST, retval); + break; + } + // expected response data: 48 f3 30 00 00 00 00 00 00 00 00 00 00 00 00 00 + if (buffer[0] != NI_USB_HS_PLUS_0x48_REQUEST) + dev_err(&usb_dev->dev, "unexpected data: buffer[0]=0x%x, expected 0x%x\n", + (int)buffer[0], NI_USB_HS_PLUS_0x48_REQUEST); + + transfer_size = 2; + + retval = ni_usb_receive_control_msg(ni_priv, NI_USB_HS_PLUS_LED_REQUEST, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x1, 0x0, buffer, transfer_size, 1000); + if (retval < 0) { + dev_err(&usb_dev->dev, "usb_control_msg request 0x%x returned %i\n", + NI_USB_HS_PLUS_LED_REQUEST, retval); + break; + } + // expected response data: 4b 00 + if (buffer[0] != NI_USB_HS_PLUS_LED_REQUEST) + dev_err(&usb_dev->dev, "unexpected data: buffer[0]=0x%x, expected 0x%x\n", + (int)buffer[0], NI_USB_HS_PLUS_LED_REQUEST); + + transfer_size = 9; + + retval = ni_usb_receive_control_msg(ni_priv, NI_USB_HS_PLUS_0xf8_REQUEST, + USB_DIR_IN | USB_TYPE_VENDOR | + USB_RECIP_INTERFACE, + 0x0, 0x1, buffer, transfer_size, 1000); + if (retval < 0) { + dev_err(&usb_dev->dev, "usb_control_msg request 0x%x returned %i\n", + NI_USB_HS_PLUS_0xf8_REQUEST, retval); + break; + } + // expected response data: f8 01 00 00 00 01 00 00 00 + if (buffer[0] != NI_USB_HS_PLUS_0xf8_REQUEST) + dev_err(&usb_dev->dev, "unexpected data: buffer[0]=0x%x, expected 0x%x\n", + (int)buffer[0], NI_USB_HS_PLUS_0xf8_REQUEST); + } while (0); + + // cleanup + kfree(buffer); + return retval; +} + +static inline int ni_usb_device_match(struct usb_interface *interface, + const struct gpib_board_config *config) +{ + if (gpib_match_device_path(&interface->dev, config->device_path) == 0) + return 0; + return 1; +} + +static int ni_usb_attach(struct gpib_board *board, const struct gpib_board_config *config) +{ + int retval; + int i, index; + struct ni_usb_priv *ni_priv; + int product_id; + struct usb_device *usb_dev; + + mutex_lock(&ni_usb_hotplug_lock); + retval = ni_usb_allocate_private(board); + if (retval < 0) { + mutex_unlock(&ni_usb_hotplug_lock); + return retval; + } + ni_priv = board->private_data; + for (i = 0; i < MAX_NUM_NI_USB_INTERFACES; i++) { + if (ni_usb_driver_interfaces[i] && + !usb_get_intfdata(ni_usb_driver_interfaces[i]) && + ni_usb_device_match(ni_usb_driver_interfaces[i], config)) { + ni_priv->bus_interface = ni_usb_driver_interfaces[i]; + usb_set_intfdata(ni_usb_driver_interfaces[i], board); + usb_dev = interface_to_usbdev(ni_priv->bus_interface); + index = i; + break; + } + } + if (i == MAX_NUM_NI_USB_INTERFACES) { + mutex_unlock(&ni_usb_hotplug_lock); + dev_err(board->gpib_dev, "No supported adapters found, have you loaded its firmware?\n"); + return -ENODEV; + } + if (usb_reset_configuration(interface_to_usbdev(ni_priv->bus_interface))) + dev_err(&usb_dev->dev, "usb_reset_configuration() failed.\n"); + + product_id = le16_to_cpu(usb_dev->descriptor.idProduct); + ni_priv->product_id = product_id; + + timer_setup(&ni_priv->bulk_timer, ni_usb_timeout_handler, 0); + + switch (product_id) { + case USB_DEVICE_ID_NI_USB_B: + ni_priv->bulk_out_endpoint = NIUSB_B_BULK_OUT_ENDPOINT; + ni_priv->bulk_in_endpoint = NIUSB_B_BULK_IN_ENDPOINT; + ni_priv->interrupt_in_endpoint = NIUSB_B_INTERRUPT_IN_ENDPOINT; + ni_usb_b_read_serial_number(ni_priv); + break; + case USB_DEVICE_ID_NI_USB_HS: + case USB_DEVICE_ID_MC_USB_488: + case USB_DEVICE_ID_KUSB_488A: + ni_priv->bulk_out_endpoint = NIUSB_HS_BULK_OUT_ENDPOINT; + ni_priv->bulk_in_endpoint = NIUSB_HS_BULK_IN_ENDPOINT; + ni_priv->interrupt_in_endpoint = NIUSB_HS_INTERRUPT_IN_ENDPOINT; + retval = ni_usb_hs_wait_for_ready(ni_priv); + if (retval < 0) { + mutex_unlock(&ni_usb_hotplug_lock); + return retval; + } + break; + case USB_DEVICE_ID_NI_USB_HS_PLUS: + ni_priv->bulk_out_endpoint = NIUSB_HS_PLUS_BULK_OUT_ENDPOINT; + ni_priv->bulk_in_endpoint = NIUSB_HS_PLUS_BULK_IN_ENDPOINT; + ni_priv->interrupt_in_endpoint = NIUSB_HS_PLUS_INTERRUPT_IN_ENDPOINT; + retval = ni_usb_hs_wait_for_ready(ni_priv); + if (retval < 0) { + mutex_unlock(&ni_usb_hotplug_lock); + return retval; + } + retval = ni_usb_hs_plus_extra_init(ni_priv); + if (retval < 0) { + mutex_unlock(&ni_usb_hotplug_lock); + return retval; + } + break; + default: + mutex_unlock(&ni_usb_hotplug_lock); + dev_err(&usb_dev->dev, "\tDriver bug: unknown endpoints for usb device id %x\n", + product_id); + return -EINVAL; + } + + retval = ni_usb_setup_urbs(board); + if (retval < 0) { + mutex_unlock(&ni_usb_hotplug_lock); + return retval; + } + retval = ni_usb_set_interrupt_monitor(board, 0); + if (retval < 0) { + mutex_unlock(&ni_usb_hotplug_lock); + return retval; + } + + board->t1_nano_sec = 500; + + retval = ni_usb_init(board); + if (retval < 0) { + mutex_unlock(&ni_usb_hotplug_lock); + return retval; + } + retval = ni_usb_set_interrupt_monitor(board, ni_usb_ibsta_monitor_mask); + if (retval < 0) { + mutex_unlock(&ni_usb_hotplug_lock); + return retval; + } + + mutex_unlock(&ni_usb_hotplug_lock); + dev_info(&usb_dev->dev, + "bus %d dev num %d attached to gpib%d, intf %i\n", + usb_dev->bus->busnum, usb_dev->devnum, board->minor, index); + return retval; +} + +static int ni_usb_shutdown_hardware(struct ni_usb_priv *ni_priv) +{ + struct usb_device *usb_dev = interface_to_usbdev(ni_priv->bus_interface); + int retval; + struct ni_usb_register writes[2]; + static const int writes_length = ARRAY_SIZE(writes); + unsigned int ibsta; + + writes[0].device = NIUSB_SUBDEV_TNT4882; + writes[0].address = nec7210_to_tnt4882_offset(AUXMR); + writes[0].value = AUX_CR; + writes[1].device = NIUSB_SUBDEV_UNKNOWN3; + writes[1].address = 0x10; + writes[1].value = 0x0; + retval = ni_usb_write_registers(ni_priv, writes, writes_length, &ibsta); + if (retval) { + dev_err(&usb_dev->dev, "register write failed, retval=%i\n", retval); + return retval; + } + return 0; +} + +static void ni_usb_detach(struct gpib_board *board) +{ + struct ni_usb_priv *ni_priv; + + mutex_lock(&ni_usb_hotplug_lock); + /* + * under windows, software unplug does chip_reset nec7210 aux command, + * then writes 0x0 to address 0x10 of device 3 + */ + ni_priv = board->private_data; + if (ni_priv) { + if (ni_priv->bus_interface) { + ni_usb_set_interrupt_monitor(board, 0); + ni_usb_shutdown_hardware(ni_priv); + usb_set_intfdata(ni_priv->bus_interface, NULL); + } + mutex_lock(&ni_priv->bulk_transfer_lock); + mutex_lock(&ni_priv->control_transfer_lock); + mutex_lock(&ni_priv->interrupt_transfer_lock); + ni_usb_cleanup_urbs(ni_priv); + ni_usb_free_private(ni_priv); + } + mutex_unlock(&ni_usb_hotplug_lock); +} + +static struct gpib_interface ni_usb_gpib_interface = { + .name = "ni_usb_b", + .attach = ni_usb_attach, + .detach = ni_usb_detach, + .read = ni_usb_read, + .write = ni_usb_write, + .command = ni_usb_command, + .take_control = ni_usb_take_control, + .go_to_standby = ni_usb_go_to_standby, + .request_system_control = ni_usb_request_system_control, + .interface_clear = ni_usb_interface_clear, + .remote_enable = ni_usb_remote_enable, + .enable_eos = ni_usb_enable_eos, + .disable_eos = ni_usb_disable_eos, + .parallel_poll = ni_usb_parallel_poll, + .parallel_poll_configure = ni_usb_parallel_poll_configure, + .parallel_poll_response = ni_usb_parallel_poll_response, + .local_parallel_poll_mode = NULL, // XXX + .line_status = ni_usb_line_status, + .update_status = ni_usb_update_status, + .primary_address = ni_usb_primary_address, + .secondary_address = ni_usb_secondary_address, + .serial_poll_response = ni_usb_serial_poll_response, + .serial_poll_status = ni_usb_serial_poll_status, + .t1_delay = ni_usb_t1_delay, + .return_to_local = ni_usb_return_to_local, + .skip_check_for_command_acceptors = 1 +}; + +// Table with the USB-devices: just now only testing IDs +static struct usb_device_id ni_usb_driver_device_table[] = { + {USB_DEVICE(USB_VENDOR_ID_NI, USB_DEVICE_ID_NI_USB_B)}, + {USB_DEVICE(USB_VENDOR_ID_NI, USB_DEVICE_ID_NI_USB_HS)}, + // gpib-usb-hs+ has a second interface for the analyzer, which we ignore + {USB_DEVICE_INTERFACE_NUMBER(USB_VENDOR_ID_NI, USB_DEVICE_ID_NI_USB_HS_PLUS, 0)}, + {USB_DEVICE(USB_VENDOR_ID_NI, USB_DEVICE_ID_KUSB_488A)}, + {USB_DEVICE(USB_VENDOR_ID_NI, USB_DEVICE_ID_MC_USB_488)}, + {} /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, ni_usb_driver_device_table); + +static int ni_usb_driver_probe(struct usb_interface *interface, const struct usb_device_id *id) +{ + struct usb_device *usb_dev = interface_to_usbdev(interface); + int i; + char *path; + static const int path_length = 1024; + + mutex_lock(&ni_usb_hotplug_lock); + usb_get_dev(usb_dev); + for (i = 0; i < MAX_NUM_NI_USB_INTERFACES; i++) { + if (!ni_usb_driver_interfaces[i]) { + ni_usb_driver_interfaces[i] = interface; + usb_set_intfdata(interface, NULL); + break; + } + } + if (i == MAX_NUM_NI_USB_INTERFACES) { + usb_put_dev(usb_dev); + mutex_unlock(&ni_usb_hotplug_lock); + dev_err(&usb_dev->dev, "ni_usb_driver_interfaces[] full\n"); + return -1; + } + path = kmalloc(path_length, GFP_KERNEL); + if (!path) { + usb_put_dev(usb_dev); + mutex_unlock(&ni_usb_hotplug_lock); + return -ENOMEM; + } + usb_make_path(usb_dev, path, path_length); + dev_info(&usb_dev->dev, "probe succeeded for path: %s\n", path); + kfree(path); + mutex_unlock(&ni_usb_hotplug_lock); + return 0; +} + +static void ni_usb_driver_disconnect(struct usb_interface *interface) +{ + struct usb_device *usb_dev = interface_to_usbdev(interface); + int i; + + mutex_lock(&ni_usb_hotplug_lock); + for (i = 0; i < MAX_NUM_NI_USB_INTERFACES; i++) { + if (ni_usb_driver_interfaces[i] == interface) { + struct gpib_board *board = usb_get_intfdata(interface); + + if (board) { + struct ni_usb_priv *ni_priv = board->private_data; + + if (ni_priv) { + mutex_lock(&ni_priv->bulk_transfer_lock); + mutex_lock(&ni_priv->control_transfer_lock); + mutex_lock(&ni_priv->interrupt_transfer_lock); + ni_usb_cleanup_urbs(ni_priv); + ni_priv->bus_interface = NULL; + mutex_unlock(&ni_priv->interrupt_transfer_lock); + mutex_unlock(&ni_priv->control_transfer_lock); + mutex_unlock(&ni_priv->bulk_transfer_lock); + } + } + ni_usb_driver_interfaces[i] = NULL; + break; + } + } + if (i == MAX_NUM_NI_USB_INTERFACES) + dev_err(&usb_dev->dev, "unable to find interface bug?\n"); + usb_put_dev(usb_dev); + mutex_unlock(&ni_usb_hotplug_lock); +} + +static int ni_usb_driver_suspend(struct usb_interface *interface, pm_message_t message) +{ + struct usb_device *usb_dev = interface_to_usbdev(interface); + struct gpib_board *board; + int i, retval; + + mutex_lock(&ni_usb_hotplug_lock); + + for (i = 0; i < MAX_NUM_NI_USB_INTERFACES; i++) { + if (ni_usb_driver_interfaces[i] == interface) { + board = usb_get_intfdata(interface); + if (board) + break; + } + } + if (i == MAX_NUM_NI_USB_INTERFACES) { + mutex_unlock(&ni_usb_hotplug_lock); + return 0; + } + + struct ni_usb_priv *ni_priv = board->private_data; + + if (ni_priv) { + ni_usb_set_interrupt_monitor(board, 0); + retval = ni_usb_shutdown_hardware(ni_priv); + if (retval) { + mutex_unlock(&ni_usb_hotplug_lock); + return retval; + } + if (ni_priv->interrupt_urb) { + mutex_lock(&ni_priv->interrupt_transfer_lock); + ni_usb_cleanup_urbs(ni_priv); + mutex_unlock(&ni_priv->interrupt_transfer_lock); + } + dev_dbg(&usb_dev->dev, + "bus %d dev num %d gpib%d, interface %i suspended\n", + usb_dev->bus->busnum, usb_dev->devnum, board->minor, i); + } + + mutex_unlock(&ni_usb_hotplug_lock); + return 0; +} + +static int ni_usb_driver_resume(struct usb_interface *interface) +{ + struct usb_device *usb_dev = interface_to_usbdev(interface); + + struct gpib_board *board; + int i, retval; + + mutex_lock(&ni_usb_hotplug_lock); + + for (i = 0; i < MAX_NUM_NI_USB_INTERFACES; i++) { + if (ni_usb_driver_interfaces[i] == interface) { + board = usb_get_intfdata(interface); + if (board) + break; + } + } + if (i == MAX_NUM_NI_USB_INTERFACES) { + mutex_unlock(&ni_usb_hotplug_lock); + return 0; + } + + struct ni_usb_priv *ni_priv = board->private_data; + + if (ni_priv) { + if (ni_priv->interrupt_urb) { + mutex_lock(&ni_priv->interrupt_transfer_lock); + retval = usb_submit_urb(ni_priv->interrupt_urb, GFP_KERNEL); + if (retval) { + dev_err(&usb_dev->dev, "resume failed to resubmit interrupt urb, retval=%i\n", + retval); + mutex_unlock(&ni_priv->interrupt_transfer_lock); + mutex_unlock(&ni_usb_hotplug_lock); + return retval; + } + mutex_unlock(&ni_priv->interrupt_transfer_lock); + } else { + dev_err(&usb_dev->dev, "bug! resume int urb not set up\n"); + mutex_unlock(&ni_usb_hotplug_lock); + return -EINVAL; + } + + switch (ni_priv->product_id) { + case USB_DEVICE_ID_NI_USB_B: + ni_usb_b_read_serial_number(ni_priv); + break; + case USB_DEVICE_ID_NI_USB_HS: + case USB_DEVICE_ID_MC_USB_488: + case USB_DEVICE_ID_KUSB_488A: + retval = ni_usb_hs_wait_for_ready(ni_priv); + if (retval < 0) { + mutex_unlock(&ni_usb_hotplug_lock); + return retval; + } + break; + case USB_DEVICE_ID_NI_USB_HS_PLUS: + retval = ni_usb_hs_wait_for_ready(ni_priv); + if (retval < 0) { + mutex_unlock(&ni_usb_hotplug_lock); + return retval; + } + retval = ni_usb_hs_plus_extra_init(ni_priv); + if (retval < 0) { + mutex_unlock(&ni_usb_hotplug_lock); + return retval; + } + break; + default: + mutex_unlock(&ni_usb_hotplug_lock); + dev_err(&usb_dev->dev, "\tDriver bug: unknown endpoints for usb device id\n"); + return -EINVAL; + } + + retval = ni_usb_set_interrupt_monitor(board, 0); + if (retval < 0) { + mutex_unlock(&ni_usb_hotplug_lock); + return retval; + } + + retval = ni_usb_init(board); + if (retval < 0) { + mutex_unlock(&ni_usb_hotplug_lock); + return retval; + } + retval = ni_usb_set_interrupt_monitor(board, ni_usb_ibsta_monitor_mask); + if (retval < 0) { + mutex_unlock(&ni_usb_hotplug_lock); + return retval; + } + if (board->master) + ni_usb_interface_clear(board, 1); // this is a pulsed action + if (ni_priv->ren_state) + ni_usb_remote_enable(board, 1); + + dev_dbg(&usb_dev->dev, + "bus %d dev num %d gpib%d, interface %i resumed\n", + usb_dev->bus->busnum, usb_dev->devnum, board->minor, i); + } + + mutex_unlock(&ni_usb_hotplug_lock); + return 0; +} + +static struct usb_driver ni_usb_bus_driver = { + .name = DRV_NAME, + .probe = ni_usb_driver_probe, + .disconnect = ni_usb_driver_disconnect, + .suspend = ni_usb_driver_suspend, + .resume = ni_usb_driver_resume, + .id_table = ni_usb_driver_device_table, +}; + +static int __init ni_usb_init_module(void) +{ + int i; + int ret; + + for (i = 0; i < MAX_NUM_NI_USB_INTERFACES; i++) + ni_usb_driver_interfaces[i] = NULL; + + ret = usb_register(&ni_usb_bus_driver); + if (ret) { + pr_err("usb_register failed: error = %d\n", ret); + return ret; + } + + ret = gpib_register_driver(&ni_usb_gpib_interface, THIS_MODULE); + if (ret) { + pr_err("gpib_register_driver failed: error = %d\n", ret); + return ret; + } + + return 0; +} + +static void __exit ni_usb_exit_module(void) +{ + gpib_unregister_driver(&ni_usb_gpib_interface); + usb_deregister(&ni_usb_bus_driver); +} + +module_init(ni_usb_init_module); +module_exit(ni_usb_exit_module); diff --git a/drivers/gpib/ni_usb/ni_usb_gpib.h b/drivers/gpib/ni_usb/ni_usb_gpib.h new file mode 100644 index 000000000000..688f5e08792f --- /dev/null +++ b/drivers/gpib/ni_usb/ni_usb_gpib.h @@ -0,0 +1,226 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/*************************************************************************** + * copyright : (C) 2004 by Frank Mori Hess + ***************************************************************************/ + +#ifndef _NI_USB_GPIB_H +#define _NI_USB_GPIB_H + +#include +#include +#include +#include +#include "gpibP.h" + +enum { + USB_VENDOR_ID_NI = 0x3923 +}; + +enum { + USB_DEVICE_ID_NI_USB_B = 0x702a, + USB_DEVICE_ID_NI_USB_B_PREINIT = 0x702b, // device id before firmware is loaded + USB_DEVICE_ID_NI_USB_HS = 0x709b, + USB_DEVICE_ID_NI_USB_HS_PLUS = 0x7618, + USB_DEVICE_ID_KUSB_488A = 0x725c, + USB_DEVICE_ID_MC_USB_488 = 0x725d +}; + +enum ni_usb_device { + NIUSB_SUBDEV_TNT4882 = 1, + NIUSB_SUBDEV_UNKNOWN2 = 2, + NIUSB_SUBDEV_UNKNOWN3 = 3, +}; + +enum endpoint_addresses { + NIUSB_B_BULK_OUT_ENDPOINT = 0x2, + NIUSB_B_BULK_IN_ENDPOINT = 0x2, + NIUSB_B_BULK_IN_ALT_ENDPOINT = 0x6, + NIUSB_B_INTERRUPT_IN_ENDPOINT = 0x4, +}; + +enum hs_enpoint_addresses { + NIUSB_HS_BULK_OUT_ENDPOINT = 0x2, + NIUSB_HS_BULK_OUT_ALT_ENDPOINT = 0x6, + NIUSB_HS_BULK_IN_ENDPOINT = 0x4, + NIUSB_HS_BULK_IN_ALT_ENDPOINT = 0x8, + NIUSB_HS_INTERRUPT_IN_ENDPOINT = 0x1, +}; + +enum hs_plus_endpoint_addresses { + NIUSB_HS_PLUS_BULK_OUT_ENDPOINT = 0x1, + NIUSB_HS_PLUS_BULK_OUT_ALT_ENDPOINT = 0x4, + NIUSB_HS_PLUS_BULK_IN_ENDPOINT = 0x2, + NIUSB_HS_PLUS_BULK_IN_ALT_ENDPOINT = 0x5, + NIUSB_HS_PLUS_INTERRUPT_IN_ENDPOINT = 0x3, +}; + +struct ni_usb_urb_ctx { + struct completion complete; + unsigned timed_out : 1; +}; + +// struct which defines private_data for ni_usb devices +struct ni_usb_priv { + struct usb_interface *bus_interface; + int bulk_out_endpoint; + int bulk_in_endpoint; + int interrupt_in_endpoint; + u8 eos_char; + unsigned short eos_mode; + unsigned int monitored_ibsta_bits; + struct urb *bulk_urb; + struct urb *interrupt_urb; + u8 interrupt_buffer[0x11]; + struct mutex addressed_transfer_lock; // protect transfer lock + struct mutex bulk_transfer_lock; // protect bulk message sends + struct mutex control_transfer_lock; // protect control messages + struct mutex interrupt_transfer_lock; // protect interrupt messages + struct timer_list bulk_timer; + struct ni_usb_urb_ctx context; + int product_id; + unsigned short ren_state; +}; + +struct ni_usb_status_block { + short id; + unsigned short ibsta; + short error_code; + unsigned short count; +}; + +struct ni_usb_register { + enum ni_usb_device device; + short address; + unsigned short value; +}; + +enum ni_usb_bulk_ids { + NIUSB_IBCAC_ID = 0x1, + NIUSB_UNKNOWN3_ID = 0x3, // device level function id? + NIUSB_TERM_ID = 0x4, + NIUSB_IBGTS_ID = 0x6, + NIUSB_IBRPP_ID = 0x7, + NIUSB_REG_READ_ID = 0x8, + NIUSB_REG_WRITE_ID = 0x9, + NIUSB_IBSIC_ID = 0xf, + NIUSB_REGISTER_READ_DATA_START_ID = 0x34, + NIUSB_REGISTER_READ_DATA_END_ID = 0x35, + NIUSB_IBRD_DATA_ID = 0x36, + NIUSB_IBRD_EXTENDED_DATA_ID = 0x37, + NIUSB_IBRD_STATUS_ID = 0x38 +}; + +enum ni_usb_error_codes { + NIUSB_NO_ERROR = 0, + /* + * NIUSB_ABORTED_ERROR occurs when I/O is interrupted early by + * doing a NI_USB_STOP_REQUEST on the control endpoint. + */ + NIUSB_ABORTED_ERROR = 1, + /* + * NIUSB_READ_ATN_ERROR occurs when you do a board read while + * ATN is set + */ + NIUSB_ATN_STATE_ERROR = 2, + /* + * NIUSB_ADDRESSING_ERROR occurs when you do a board + * read/write as CIC but are not in LACS/TACS + */ + NIUSB_ADDRESSING_ERROR = 3, + /* + * NIUSB_EOSMODE_ERROR occurs on reads if any eos mode or char + * bits are set when REOS is not set. + * Have also seen error 4 if you try to send more than 16 + * command bytes at once on a usb-b. + */ + NIUSB_EOSMODE_ERROR = 4, + /* + * NIUSB_NO_BUS_ERROR occurs when you try to write a command + * byte but there are no devices connected to the gpib bus + */ + NIUSB_NO_BUS_ERROR = 5, + /* + * NIUSB_NO_LISTENER_ERROR occurs when you do a board write as + * CIC with no listener + */ + NIUSB_NO_LISTENER_ERROR = 8, + /* get NIUSB_TIMEOUT_ERROR on board read/write timeout */ + NIUSB_TIMEOUT_ERROR = 10, +}; + +enum ni_usb_control_requests { + NI_USB_STOP_REQUEST = 0x20, + NI_USB_WAIT_REQUEST = 0x21, + NI_USB_POLL_READY_REQUEST = 0x40, + NI_USB_SERIAL_NUMBER_REQUEST = 0x41, + NI_USB_HS_PLUS_0x48_REQUEST = 0x48, + NI_USB_HS_PLUS_LED_REQUEST = 0x4b, + NI_USB_HS_PLUS_0xf8_REQUEST = 0xf8 +}; + +static const unsigned int ni_usb_ibsta_monitor_mask = + SRQI | LOK | REM | CIC | ATN | TACS | LACS | DTAS | DCAS; + +static inline int nec7210_to_tnt4882_offset(int offset) +{ + return 2 * offset; +}; + +static inline int ni_usb_bulk_termination(u8 *buffer) +{ + int i = 0; + + buffer[i++] = NIUSB_TERM_ID; + buffer[i++] = 0x0; + buffer[i++] = 0x0; + buffer[i++] = 0x0; + return i; +} + +enum ni_usb_unknown3_register { + SERIAL_NUMBER_4_REG = 0x8, + SERIAL_NUMBER_3_REG = 0x9, + SERIAL_NUMBER_2_REG = 0xa, + SERIAL_NUMBER_1_REG = 0xb, +}; + +static inline int ni_usb_bulk_register_write_header(u8 *buffer, int num_writes) +{ + int i = 0; + + buffer[i++] = NIUSB_REG_WRITE_ID; + buffer[i++] = num_writes; + buffer[i++] = 0x0; + return i; +} + +static inline int ni_usb_bulk_register_write(u8 *buffer, struct ni_usb_register reg) +{ + int i = 0; + + buffer[i++] = reg.device; + buffer[i++] = reg.address; + buffer[i++] = reg.value; + return i; +} + +static inline int ni_usb_bulk_register_read_header(u8 *buffer, int num_reads) +{ + int i = 0; + + buffer[i++] = NIUSB_REG_READ_ID; + buffer[i++] = num_reads; + return i; +} + +static inline int ni_usb_bulk_register_read(u8 *buffer, int device, int address) +{ + int i = 0; + + buffer[i++] = device; + buffer[i++] = address; + return i; +} + +#endif // _NI_USB_GPIB_H diff --git a/drivers/gpib/pc2/Makefile b/drivers/gpib/pc2/Makefile new file mode 100644 index 000000000000..481ee4296e1b --- /dev/null +++ b/drivers/gpib/pc2/Makefile @@ -0,0 +1,5 @@ + +obj-$(CONFIG_GPIB_PC2) += pc2_gpib.o + + + diff --git a/drivers/gpib/pc2/pc2_gpib.c b/drivers/gpib/pc2/pc2_gpib.c new file mode 100644 index 000000000000..9f3943d1df66 --- /dev/null +++ b/drivers/gpib/pc2/pc2_gpib.c @@ -0,0 +1,684 @@ +// SPDX-License-Identifier: GPL-2.0 + +/*************************************************************************** + * copyright : (C) 2001, 2002 by Frank Mori Hess + ***************************************************************************/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#define dev_fmt pr_fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "nec7210.h" +#include "gpibP.h" + +// struct which defines private_data for pc2 driver +struct pc2_priv { + struct nec7210_priv nec7210_priv; + unsigned int irq; + // io address that clears interrupt for pc2a (0x2f0 + irq) + unsigned int clear_intr_addr; +}; + +// pc2 uses 8 consecutive io addresses +static const int pc2_iosize = 8; +static const int pc2a_iosize = 8; +static const int pc2_2a_iosize = 16; + +// offset between io addresses of successive nec7210 registers +static const int pc2a_reg_offset = 0x400; +static const int pc2_reg_offset = 1; + +// interrupt service routine +static irqreturn_t pc2_interrupt(int irq, void *arg); +static irqreturn_t pc2a_interrupt(int irq, void *arg); + +// pc2 specific registers and bits + +// interrupt clear register address +static const int pc2a_clear_intr_iobase = 0x2f0; +static inline unsigned int CLEAR_INTR_REG(unsigned int irq) +{ + return pc2a_clear_intr_iobase + irq; +} + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("GPIB driver for PC2/PC2a and compatible devices"); + +/* + * GPIB interrupt service routines + */ + +irqreturn_t pc2_interrupt(int irq, void *arg) +{ + struct gpib_board *board = arg; + struct pc2_priv *priv = board->private_data; + unsigned long flags; + irqreturn_t retval; + + spin_lock_irqsave(&board->spinlock, flags); + retval = nec7210_interrupt(board, &priv->nec7210_priv); + spin_unlock_irqrestore(&board->spinlock, flags); + return retval; +} + +irqreturn_t pc2a_interrupt(int irq, void *arg) +{ + struct gpib_board *board = arg; + struct pc2_priv *priv = board->private_data; + int status1, status2; + unsigned long flags; + irqreturn_t retval; + + spin_lock_irqsave(&board->spinlock, flags); + // read interrupt status (also clears status) + status1 = read_byte(&priv->nec7210_priv, ISR1); + status2 = read_byte(&priv->nec7210_priv, ISR2); + /* clear interrupt circuit */ + if (priv->irq) + outb(0xff, CLEAR_INTR_REG(priv->irq)); + retval = nec7210_interrupt_have_status(board, &priv->nec7210_priv, status1, status2); + spin_unlock_irqrestore(&board->spinlock, flags); + return retval; +} + +// wrappers for interface functions +static int pc2_read(struct gpib_board *board, u8 *buffer, size_t length, int *end, + size_t *bytes_read) +{ + struct pc2_priv *priv = board->private_data; + + return nec7210_read(board, &priv->nec7210_priv, buffer, length, end, bytes_read); +} + +static int pc2_write(struct gpib_board *board, u8 *buffer, size_t length, int send_eoi, + size_t *bytes_written) +{ + struct pc2_priv *priv = board->private_data; + + return nec7210_write(board, &priv->nec7210_priv, buffer, length, send_eoi, bytes_written); +} + +static int pc2_command(struct gpib_board *board, u8 *buffer, + size_t length, size_t *bytes_written) +{ + struct pc2_priv *priv = board->private_data; + + return nec7210_command(board, &priv->nec7210_priv, buffer, length, bytes_written); +} + +static int pc2_take_control(struct gpib_board *board, int synchronous) +{ + struct pc2_priv *priv = board->private_data; + + return nec7210_take_control(board, &priv->nec7210_priv, synchronous); +} + +static int pc2_go_to_standby(struct gpib_board *board) +{ + struct pc2_priv *priv = board->private_data; + + return nec7210_go_to_standby(board, &priv->nec7210_priv); +} + +static int pc2_request_system_control(struct gpib_board *board, int request_control) +{ + struct pc2_priv *priv = board->private_data; + + return nec7210_request_system_control(board, &priv->nec7210_priv, request_control); +} + +static void pc2_interface_clear(struct gpib_board *board, int assert) +{ + struct pc2_priv *priv = board->private_data; + + nec7210_interface_clear(board, &priv->nec7210_priv, assert); +} + +static void pc2_remote_enable(struct gpib_board *board, int enable) +{ + struct pc2_priv *priv = board->private_data; + + nec7210_remote_enable(board, &priv->nec7210_priv, enable); +} + +static int pc2_enable_eos(struct gpib_board *board, u8 eos_byte, int compare_8_bits) +{ + struct pc2_priv *priv = board->private_data; + + return nec7210_enable_eos(board, &priv->nec7210_priv, eos_byte, compare_8_bits); +} + +static void pc2_disable_eos(struct gpib_board *board) +{ + struct pc2_priv *priv = board->private_data; + + nec7210_disable_eos(board, &priv->nec7210_priv); +} + +static unsigned int pc2_update_status(struct gpib_board *board, unsigned int clear_mask) +{ + struct pc2_priv *priv = board->private_data; + + return nec7210_update_status(board, &priv->nec7210_priv, clear_mask); +} + +static int pc2_primary_address(struct gpib_board *board, unsigned int address) +{ + struct pc2_priv *priv = board->private_data; + + return nec7210_primary_address(board, &priv->nec7210_priv, address); +} + +static int pc2_secondary_address(struct gpib_board *board, unsigned int address, int enable) +{ + struct pc2_priv *priv = board->private_data; + + return nec7210_secondary_address(board, &priv->nec7210_priv, address, enable); +} + +static int pc2_parallel_poll(struct gpib_board *board, u8 *result) +{ + struct pc2_priv *priv = board->private_data; + + return nec7210_parallel_poll(board, &priv->nec7210_priv, result); +} + +static void pc2_parallel_poll_configure(struct gpib_board *board, u8 config) +{ + struct pc2_priv *priv = board->private_data; + + nec7210_parallel_poll_configure(board, &priv->nec7210_priv, config); +} + +static void pc2_parallel_poll_response(struct gpib_board *board, int ist) +{ + struct pc2_priv *priv = board->private_data; + + nec7210_parallel_poll_response(board, &priv->nec7210_priv, ist); +} + +static void pc2_serial_poll_response(struct gpib_board *board, u8 status) +{ + struct pc2_priv *priv = board->private_data; + + nec7210_serial_poll_response(board, &priv->nec7210_priv, status); +} + +static u8 pc2_serial_poll_status(struct gpib_board *board) +{ + struct pc2_priv *priv = board->private_data; + + return nec7210_serial_poll_status(board, &priv->nec7210_priv); +} + +static int pc2_t1_delay(struct gpib_board *board, unsigned int nano_sec) +{ + struct pc2_priv *priv = board->private_data; + + return nec7210_t1_delay(board, &priv->nec7210_priv, nano_sec); +} + +static void pc2_return_to_local(struct gpib_board *board) +{ + struct pc2_priv *priv = board->private_data; + + nec7210_return_to_local(board, &priv->nec7210_priv); +} + +static int allocate_private(struct gpib_board *board) +{ + struct pc2_priv *priv; + + board->private_data = kmalloc(sizeof(struct pc2_priv), GFP_KERNEL); + if (!board->private_data) + return -1; + priv = board->private_data; + memset(priv, 0, sizeof(struct pc2_priv)); + init_nec7210_private(&priv->nec7210_priv); + return 0; +} + +static void free_private(struct gpib_board *board) +{ + kfree(board->private_data); + board->private_data = NULL; +} + +static int pc2_generic_attach(struct gpib_board *board, const struct gpib_board_config *config, + enum nec7210_chipset chipset) +{ + struct pc2_priv *pc2_priv; + struct nec7210_priv *nec_priv; + + board->status = 0; + if (allocate_private(board)) + return -ENOMEM; + pc2_priv = board->private_data; + nec_priv = &pc2_priv->nec7210_priv; + nec_priv->read_byte = nec7210_ioport_read_byte; + nec_priv->write_byte = nec7210_ioport_write_byte; + nec_priv->type = chipset; + +#ifndef PC2_DMA + /* + * board->dev hasn't been initialized, so forget about DMA until this driver + * is adapted to use isa_register_driver. + */ + if (config->ibdma) + // driver needs to be adapted to use isa_register_driver to get a struct device* + dev_err(board->gpib_dev, "DMA disabled for pc2 gpib"); +#else + if (config->ibdma) { + nec_priv->dma_buffer_length = 0x1000; + nec_priv->dma_buffer = dma_alloc_coherent(board->dev, + nec_priv->dma_buffer_length, & + nec_priv->dma_buffer_addr, GFP_ATOMIC); + if (!nec_priv->dma_buffer) + return -ENOMEM; + + // request isa dma channel + if (request_dma(config->ibdma, "pc2")) { + dev_err(board->gpib_dev, "can't request DMA %d\n", config->ibdma); + return -1; + } + nec_priv->dma_channel = config->ibdma; + } +#endif + + return 0; +} + +static int pc2_attach(struct gpib_board *board, const struct gpib_board_config *config) +{ + int isr_flags = 0; + struct pc2_priv *pc2_priv; + struct nec7210_priv *nec_priv; + int retval; + + retval = pc2_generic_attach(board, config, NEC7210); + if (retval) + return retval; + + pc2_priv = board->private_data; + nec_priv = &pc2_priv->nec7210_priv; + nec_priv->offset = pc2_reg_offset; + + if (!request_region(config->ibbase, pc2_iosize, "pc2")) { + dev_err(board->gpib_dev, "ioports are already in use\n"); + return -EBUSY; + } + nec_priv->iobase = config->ibbase; + + nec7210_board_reset(nec_priv, board); + + // install interrupt handler + if (config->ibirq) { + if (request_irq(config->ibirq, pc2_interrupt, isr_flags, "pc2", board)) { + dev_err(board->gpib_dev, "can't request IRQ %d\n", config->ibirq); + return -EBUSY; + } + } + pc2_priv->irq = config->ibirq; + /* poll so we can detect assertion of ATN */ + if (gpib_request_pseudo_irq(board, pc2_interrupt)) { + dev_err(board->gpib_dev, "failed to allocate pseudo_irq\n"); + return -1; + } + /* set internal counter register for 8 MHz input clock */ + write_byte(nec_priv, ICR | 8, AUXMR); + + nec7210_board_online(nec_priv, board); + + return 0; +} + +static void pc2_detach(struct gpib_board *board) +{ + struct pc2_priv *pc2_priv = board->private_data; + struct nec7210_priv *nec_priv; + + if (pc2_priv) { + nec_priv = &pc2_priv->nec7210_priv; +#ifdef PC2_DMA + if (nec_priv->dma_channel) + free_dma(nec_priv->dma_channel); +#endif + gpib_free_pseudo_irq(board); + if (pc2_priv->irq) + free_irq(pc2_priv->irq, board); + if (nec_priv->iobase) { + nec7210_board_reset(nec_priv, board); + release_region(nec_priv->iobase, pc2_iosize); + } + if (nec_priv->dma_buffer) { + dma_free_coherent(board->dev, nec_priv->dma_buffer_length, + nec_priv->dma_buffer, nec_priv->dma_buffer_addr); + nec_priv->dma_buffer = NULL; + } + } + free_private(board); +} + +static int pc2a_common_attach(struct gpib_board *board, const struct gpib_board_config *config, + unsigned int num_registers, enum nec7210_chipset chipset) +{ + unsigned int i, j; + struct pc2_priv *pc2_priv; + struct nec7210_priv *nec_priv; + int retval; + + retval = pc2_generic_attach(board, config, chipset); + if (retval) + return retval; + + pc2_priv = board->private_data; + nec_priv = &pc2_priv->nec7210_priv; + nec_priv->offset = pc2a_reg_offset; + + switch (config->ibbase) { + case 0x02e1: + case 0x22e1: + case 0x42e1: + case 0x62e1: + break; + default: + dev_err(board->gpib_dev, "PCIIa base range invalid, must be one of 0x[0246]2e1, but is 0x%x\n", + config->ibbase); + return -1; + } + + if (config->ibirq) { + if (config->ibirq < 2 || config->ibirq > 7) { + dev_err(board->gpib_dev, "illegal interrupt level %i\n", + config->ibirq); + return -1; + } + } else { + dev_err(board->gpib_dev, "interrupt disabled, using polling mode (slow)\n"); + } +#ifdef CHECK_IOPORTS + unsigned int err = 0; + + for (i = 0; i < num_registers; i++) { + if (check_region(config->ibbase + i * pc2a_reg_offset, 1)) + err++; + } + if (config->ibirq && check_region(pc2a_clear_intr_iobase + config->ibirq, 1)) + err++; + if (err) { + dev_err(board->gpib_dev, "ioports are already in use"); + return -EBUSY; + } +#endif + for (i = 0; i < num_registers; i++) { + if (!request_region(config->ibbase + + i * pc2a_reg_offset, 1, "pc2a")) { + dev_err(board->gpib_dev, "ioports are already in use"); + for (j = 0; j < i; j++) + release_region(config->ibbase + + j * pc2a_reg_offset, 1); + return -EBUSY; + } + } + nec_priv->iobase = config->ibbase; + if (config->ibirq) { + if (!request_region(pc2a_clear_intr_iobase + config->ibirq, 1, "pc2a")) { + dev_err(board->gpib_dev, "ioports are already in use"); + return -1; + } + pc2_priv->clear_intr_addr = pc2a_clear_intr_iobase + config->ibirq; + if (request_irq(config->ibirq, pc2a_interrupt, 0, "pc2a", board)) { + dev_err(board->gpib_dev, "can't request IRQ %d\n", config->ibirq); + return -EBUSY; + } + } + pc2_priv->irq = config->ibirq; + /* poll so we can detect assertion of ATN */ + if (gpib_request_pseudo_irq(board, pc2_interrupt)) { + dev_err(board->gpib_dev, "failed to allocate pseudo_irq\n"); + return -1; + } + + // make sure interrupt is clear + if (pc2_priv->irq) + outb(0xff, CLEAR_INTR_REG(pc2_priv->irq)); + + nec7210_board_reset(nec_priv, board); + + /* set internal counter register for 8 MHz input clock */ + write_byte(nec_priv, ICR | 8, AUXMR); + + nec7210_board_online(nec_priv, board); + + return 0; +} + +static int pc2a_attach(struct gpib_board *board, const struct gpib_board_config *config) +{ + return pc2a_common_attach(board, config, pc2a_iosize, NEC7210); +} + +static int pc2a_cb7210_attach(struct gpib_board *board, const struct gpib_board_config *config) +{ + return pc2a_common_attach(board, config, pc2a_iosize, CB7210); +} + +static int pc2_2a_attach(struct gpib_board *board, const struct gpib_board_config *config) +{ + return pc2a_common_attach(board, config, pc2_2a_iosize, NAT4882); +} + +static void pc2a_common_detach(struct gpib_board *board, unsigned int num_registers) +{ + int i; + struct pc2_priv *pc2_priv = board->private_data; + struct nec7210_priv *nec_priv; + + if (pc2_priv) { + nec_priv = &pc2_priv->nec7210_priv; +#ifdef PC2_DMA + if (nec_priv->dma_channel) + free_dma(nec_priv->dma_channel); +#endif + gpib_free_pseudo_irq(board); + if (pc2_priv->irq) + free_irq(pc2_priv->irq, board); + if (nec_priv->iobase) { + nec7210_board_reset(nec_priv, board); + for (i = 0; i < num_registers; i++) + release_region(nec_priv->iobase + + i * pc2a_reg_offset, 1); + } + if (pc2_priv->clear_intr_addr) + release_region(pc2_priv->clear_intr_addr, 1); + if (nec_priv->dma_buffer) { + dma_free_coherent(board->dev, nec_priv->dma_buffer_length, + nec_priv->dma_buffer, + nec_priv->dma_buffer_addr); + nec_priv->dma_buffer = NULL; + } + } + free_private(board); +} + +static void pc2a_detach(struct gpib_board *board) +{ + pc2a_common_detach(board, pc2a_iosize); +} + +static void pc2_2a_detach(struct gpib_board *board) +{ + pc2a_common_detach(board, pc2_2a_iosize); +} + +static struct gpib_interface pc2_interface = { + .name = "pcII", + .attach = pc2_attach, + .detach = pc2_detach, + .read = pc2_read, + .write = pc2_write, + .command = pc2_command, + .take_control = pc2_take_control, + .go_to_standby = pc2_go_to_standby, + .request_system_control = pc2_request_system_control, + .interface_clear = pc2_interface_clear, + .remote_enable = pc2_remote_enable, + .enable_eos = pc2_enable_eos, + .disable_eos = pc2_disable_eos, + .parallel_poll = pc2_parallel_poll, + .parallel_poll_configure = pc2_parallel_poll_configure, + .parallel_poll_response = pc2_parallel_poll_response, + .local_parallel_poll_mode = NULL, // XXX + .line_status = NULL, + .update_status = pc2_update_status, + .primary_address = pc2_primary_address, + .secondary_address = pc2_secondary_address, + .serial_poll_response = pc2_serial_poll_response, + .serial_poll_status = pc2_serial_poll_status, + .t1_delay = pc2_t1_delay, + .return_to_local = pc2_return_to_local, +}; + +static struct gpib_interface pc2a_interface = { + .name = "pcIIa", + .attach = pc2a_attach, + .detach = pc2a_detach, + .read = pc2_read, + .write = pc2_write, + .command = pc2_command, + .take_control = pc2_take_control, + .go_to_standby = pc2_go_to_standby, + .request_system_control = pc2_request_system_control, + .interface_clear = pc2_interface_clear, + .remote_enable = pc2_remote_enable, + .enable_eos = pc2_enable_eos, + .disable_eos = pc2_disable_eos, + .parallel_poll = pc2_parallel_poll, + .parallel_poll_configure = pc2_parallel_poll_configure, + .parallel_poll_response = pc2_parallel_poll_response, + .local_parallel_poll_mode = NULL, // XXX + .line_status = NULL, + .update_status = pc2_update_status, + .primary_address = pc2_primary_address, + .secondary_address = pc2_secondary_address, + .serial_poll_response = pc2_serial_poll_response, + .serial_poll_status = pc2_serial_poll_status, + .t1_delay = pc2_t1_delay, + .return_to_local = pc2_return_to_local, +}; + +static struct gpib_interface pc2a_cb7210_interface = { + .name = "pcIIa_cb7210", + .attach = pc2a_cb7210_attach, + .detach = pc2a_detach, + .read = pc2_read, + .write = pc2_write, + .command = pc2_command, + .take_control = pc2_take_control, + .go_to_standby = pc2_go_to_standby, + .request_system_control = pc2_request_system_control, + .interface_clear = pc2_interface_clear, + .remote_enable = pc2_remote_enable, + .enable_eos = pc2_enable_eos, + .disable_eos = pc2_disable_eos, + .parallel_poll = pc2_parallel_poll, + .parallel_poll_configure = pc2_parallel_poll_configure, + .parallel_poll_response = pc2_parallel_poll_response, + .local_parallel_poll_mode = NULL, // XXX + .line_status = NULL, // XXX + .update_status = pc2_update_status, + .primary_address = pc2_primary_address, + .secondary_address = pc2_secondary_address, + .serial_poll_response = pc2_serial_poll_response, + .serial_poll_status = pc2_serial_poll_status, + .t1_delay = pc2_t1_delay, + .return_to_local = pc2_return_to_local, +}; + +static struct gpib_interface pc2_2a_interface = { + .name = "pcII_IIa", + .attach = pc2_2a_attach, + .detach = pc2_2a_detach, + .read = pc2_read, + .write = pc2_write, + .command = pc2_command, + .take_control = pc2_take_control, + .go_to_standby = pc2_go_to_standby, + .request_system_control = pc2_request_system_control, + .interface_clear = pc2_interface_clear, + .remote_enable = pc2_remote_enable, + .enable_eos = pc2_enable_eos, + .disable_eos = pc2_disable_eos, + .parallel_poll = pc2_parallel_poll, + .parallel_poll_configure = pc2_parallel_poll_configure, + .parallel_poll_response = pc2_parallel_poll_response, + .local_parallel_poll_mode = NULL, // XXX + .line_status = NULL, + .update_status = pc2_update_status, + .primary_address = pc2_primary_address, + .secondary_address = pc2_secondary_address, + .serial_poll_response = pc2_serial_poll_response, + .serial_poll_status = pc2_serial_poll_status, + .t1_delay = pc2_t1_delay, + .return_to_local = pc2_return_to_local, +}; + +static int __init pc2_init_module(void) +{ + int ret; + + ret = gpib_register_driver(&pc2_interface, THIS_MODULE); + if (ret) { + pr_err("gpib_register_driver failed: error = %d\n", ret); + return ret; + } + + ret = gpib_register_driver(&pc2a_interface, THIS_MODULE); + if (ret) { + pr_err("gpib_register_driver failed: error = %d\n", ret); + goto err_pc2a; + } + + ret = gpib_register_driver(&pc2a_cb7210_interface, THIS_MODULE); + if (ret) { + pr_err("gpib_register_driver failed: error = %d\n", ret); + goto err_cb7210; + } + + ret = gpib_register_driver(&pc2_2a_interface, THIS_MODULE); + if (ret) { + pr_err("gpib_register_driver failed: error = %d\n", ret); + goto err_pc2_2a; + } + + return 0; + +err_pc2_2a: + gpib_unregister_driver(&pc2a_cb7210_interface); +err_cb7210: + gpib_unregister_driver(&pc2a_interface); +err_pc2a: + gpib_unregister_driver(&pc2_interface); + + return ret; +} + +static void __exit pc2_exit_module(void) +{ + gpib_unregister_driver(&pc2_interface); + gpib_unregister_driver(&pc2a_interface); + gpib_unregister_driver(&pc2a_cb7210_interface); + gpib_unregister_driver(&pc2_2a_interface); +} + +module_init(pc2_init_module); +module_exit(pc2_exit_module); + diff --git a/drivers/gpib/tms9914/Makefile b/drivers/gpib/tms9914/Makefile new file mode 100644 index 000000000000..4705ab07f413 --- /dev/null +++ b/drivers/gpib/tms9914/Makefile @@ -0,0 +1,6 @@ + +obj-$(CONFIG_GPIB_TMS9914) += tms9914.o + + + + diff --git a/drivers/gpib/tms9914/tms9914.c b/drivers/gpib/tms9914/tms9914.c new file mode 100644 index 000000000000..72a11596a35e --- /dev/null +++ b/drivers/gpib/tms9914/tms9914.c @@ -0,0 +1,914 @@ +// SPDX-License-Identifier: GPL-2.0 + +/*************************************************************************** + * copyright : (C) 2001, 2002 by Frank Mori Hess + ***************************************************************************/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#define dev_fmt pr_fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gpibP.h" +#include "tms9914.h" + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("GPIB library for tms9914"); + +static unsigned int update_status_nolock(struct gpib_board *board, struct tms9914_priv *priv); + +int tms9914_take_control(struct gpib_board *board, struct tms9914_priv *priv, int synchronous) +{ + int i; + const int timeout = 100; + + if (synchronous) + write_byte(priv, AUX_TCS, AUXCR); + else + write_byte(priv, AUX_TCA, AUXCR); + // busy wait until ATN is asserted + for (i = 0; i < timeout; i++) { + if ((read_byte(priv, ADSR) & HR_ATN)) + break; + udelay(1); + } + if (i == timeout) + return -ETIMEDOUT; + + clear_bit(WRITE_READY_BN, &priv->state); + + return 0; +} +EXPORT_SYMBOL_GPL(tms9914_take_control); + +/* + * The agilent 82350B has a buggy implementation of tcs which interferes with the + * operation of tca. It appears to be based on the controller state machine + * described in the TI 9900 TMS9914A data manual published in 1982. This + * manual describes tcs as putting the controller into a CWAS + * state where it waits indefinitely for ANRS and ignores tca. Since a + * functioning tca is far more important than tcs, we work around the + * problem by never issuing tcs. + * + * I don't know if this problem exists in the real tms9914a or just in the fpga + * of the 82350B. For now, only the agilent_82350b uses this workaround. + * The rest of the tms9914 based drivers still use tms9914_take_control + * directly (which does issue tcs). + */ +int tms9914_take_control_workaround(struct gpib_board *board, + struct tms9914_priv *priv, int synchronous) +{ + if (synchronous) + return -ETIMEDOUT; + return tms9914_take_control(board, priv, synchronous); +} +EXPORT_SYMBOL_GPL(tms9914_take_control_workaround); + +int tms9914_go_to_standby(struct gpib_board *board, struct tms9914_priv *priv) +{ + int i; + const int timeout = 1000; + + write_byte(priv, AUX_GTS, AUXCR); + // busy wait until ATN is released + for (i = 0; i < timeout; i++) { + if ((read_byte(priv, ADSR) & HR_ATN) == 0) + break; + udelay(1); + } + if (i == timeout) + return -ETIMEDOUT; + + clear_bit(COMMAND_READY_BN, &priv->state); + + return 0; +} +EXPORT_SYMBOL_GPL(tms9914_go_to_standby); + +void tms9914_interface_clear(struct gpib_board *board, struct tms9914_priv *priv, int assert) +{ + if (assert) { + write_byte(priv, AUX_SIC | AUX_CS, AUXCR); + + set_bit(CIC_NUM, &board->status); + } else { + write_byte(priv, AUX_SIC, AUXCR); + } +} +EXPORT_SYMBOL_GPL(tms9914_interface_clear); + +void tms9914_remote_enable(struct gpib_board *board, struct tms9914_priv *priv, int enable) +{ + if (enable) + write_byte(priv, AUX_SRE | AUX_CS, AUXCR); + else + write_byte(priv, AUX_SRE, AUXCR); +} +EXPORT_SYMBOL_GPL(tms9914_remote_enable); + +int tms9914_request_system_control(struct gpib_board *board, struct tms9914_priv *priv, + int request_control) +{ + if (request_control) { + write_byte(priv, AUX_RQC, AUXCR); + } else { + clear_bit(CIC_NUM, &board->status); + write_byte(priv, AUX_RLC, AUXCR); + } + return 0; +} +EXPORT_SYMBOL_GPL(tms9914_request_system_control); + +unsigned int tms9914_t1_delay(struct gpib_board *board, struct tms9914_priv *priv, + unsigned int nano_sec) +{ + static const int clock_period = 200; // assuming 5Mhz input clock + int num_cycles; + + num_cycles = 12; + + if (nano_sec <= 8 * clock_period) { + write_byte(priv, AUX_STDL | AUX_CS, AUXCR); + num_cycles = 8; + } else { + write_byte(priv, AUX_STDL, AUXCR); + } + + if (nano_sec <= 4 * clock_period) { + write_byte(priv, AUX_VSTDL | AUX_CS, AUXCR); + num_cycles = 4; + } else { + write_byte(priv, AUX_VSTDL, AUXCR); + } + + return num_cycles * clock_period; +} +EXPORT_SYMBOL_GPL(tms9914_t1_delay); + +void tms9914_return_to_local(const struct gpib_board *board, struct tms9914_priv *priv) +{ + write_byte(priv, AUX_RTL, AUXCR); +} +EXPORT_SYMBOL_GPL(tms9914_return_to_local); + +void tms9914_set_holdoff_mode(struct tms9914_priv *priv, enum tms9914_holdoff_mode mode) +{ + switch (mode) { + case TMS9914_HOLDOFF_NONE: + write_byte(priv, AUX_HLDE, AUXCR); + write_byte(priv, AUX_HLDA, AUXCR); + break; + case TMS9914_HOLDOFF_EOI: + write_byte(priv, AUX_HLDE | AUX_CS, AUXCR); + write_byte(priv, AUX_HLDA, AUXCR); + break; + case TMS9914_HOLDOFF_ALL: + write_byte(priv, AUX_HLDE, AUXCR); + write_byte(priv, AUX_HLDA | AUX_CS, AUXCR); + break; + default: + pr_err("bug! bad holdoff mode %i\n", mode); + break; + } + priv->holdoff_mode = mode; +} +EXPORT_SYMBOL_GPL(tms9914_set_holdoff_mode); + +void tms9914_release_holdoff(struct tms9914_priv *priv) +{ + if (priv->holdoff_active) { + write_byte(priv, AUX_RHDF, AUXCR); + priv->holdoff_active = 0; + } +} +EXPORT_SYMBOL_GPL(tms9914_release_holdoff); + +int tms9914_enable_eos(struct gpib_board *board, struct tms9914_priv *priv, u8 eos_byte, + int compare_8_bits) +{ + priv->eos = eos_byte; + priv->eos_flags = REOS; + if (compare_8_bits) + priv->eos_flags |= BIN; + return 0; +} +EXPORT_SYMBOL(tms9914_enable_eos); + +void tms9914_disable_eos(struct gpib_board *board, struct tms9914_priv *priv) +{ + priv->eos_flags &= ~REOS; +} +EXPORT_SYMBOL(tms9914_disable_eos); + +int tms9914_parallel_poll(struct gpib_board *board, struct tms9914_priv *priv, u8 *result) +{ + // execute parallel poll + write_byte(priv, AUX_CS | AUX_RPP, AUXCR); + udelay(2); + *result = read_byte(priv, CPTR); + // clear parallel poll state + write_byte(priv, AUX_RPP, AUXCR); + return 0; +} +EXPORT_SYMBOL(tms9914_parallel_poll); + +static void set_ppoll_reg(struct tms9914_priv *priv, int enable, + unsigned int dio_line, int sense, int ist) +{ + u8 dio_byte; + + if (enable && ((sense && ist) || (!sense && !ist))) { + dio_byte = 1 << (dio_line - 1); + write_byte(priv, dio_byte, PPR); + } else { + write_byte(priv, 0, PPR); + } +} + +void tms9914_parallel_poll_configure(struct gpib_board *board, + struct tms9914_priv *priv, u8 config) +{ + priv->ppoll_enable = (config & PPC_DISABLE) == 0; + priv->ppoll_line = (config & PPC_DIO_MASK) + 1; + priv->ppoll_sense = (config & PPC_SENSE) != 0; + set_ppoll_reg(priv, priv->ppoll_enable, priv->ppoll_line, priv->ppoll_sense, board->ist); +} +EXPORT_SYMBOL(tms9914_parallel_poll_configure); + +void tms9914_parallel_poll_response(struct gpib_board *board, + struct tms9914_priv *priv, int ist) +{ + set_ppoll_reg(priv, priv->ppoll_enable, priv->ppoll_line, priv->ppoll_sense, ist); +} +EXPORT_SYMBOL(tms9914_parallel_poll_response); + +void tms9914_serial_poll_response(struct gpib_board *board, + struct tms9914_priv *priv, u8 status) +{ + unsigned long flags; + + spin_lock_irqsave(&board->spinlock, flags); + write_byte(priv, status, SPMR); + priv->spoll_status = status; + if (status & request_service_bit) + write_byte(priv, AUX_RSV2 | AUX_CS, AUXCR); + else + write_byte(priv, AUX_RSV2, AUXCR); + spin_unlock_irqrestore(&board->spinlock, flags); +} +EXPORT_SYMBOL(tms9914_serial_poll_response); + +u8 tms9914_serial_poll_status(struct gpib_board *board, struct tms9914_priv *priv) +{ + u8 status; + unsigned long flags; + + spin_lock_irqsave(&board->spinlock, flags); + status = priv->spoll_status; + spin_unlock_irqrestore(&board->spinlock, flags); + + return status; +} +EXPORT_SYMBOL(tms9914_serial_poll_status); + +int tms9914_primary_address(struct gpib_board *board, + struct tms9914_priv *priv, unsigned int address) +{ + // put primary address in address0 + write_byte(priv, address & ADDRESS_MASK, ADR); + return 0; +} +EXPORT_SYMBOL(tms9914_primary_address); + +int tms9914_secondary_address(struct gpib_board *board, struct tms9914_priv *priv, + unsigned int address, int enable) +{ + if (enable) + priv->imr1_bits |= HR_APTIE; + else + priv->imr1_bits &= ~HR_APTIE; + + write_byte(priv, priv->imr1_bits, IMR1); + return 0; +} +EXPORT_SYMBOL(tms9914_secondary_address); + +unsigned int tms9914_update_status(struct gpib_board *board, struct tms9914_priv *priv, + unsigned int clear_mask) +{ + unsigned long flags; + unsigned int retval; + + spin_lock_irqsave(&board->spinlock, flags); + retval = update_status_nolock(board, priv); + board->status &= ~clear_mask; + spin_unlock_irqrestore(&board->spinlock, flags); + + return retval; +} +EXPORT_SYMBOL(tms9914_update_status); + +static void update_talker_state(struct tms9914_priv *priv, unsigned int address_status_bits) +{ + if (address_status_bits & HR_TA) { + if (address_status_bits & HR_ATN) + priv->talker_state = talker_addressed; + else + /* + * this could also be serial_poll_active, but the tms9914 provides no + * way to distinguish, so we'll assume talker_active + */ + priv->talker_state = talker_active; + } else { + priv->talker_state = talker_idle; + } +} + +static void update_listener_state(struct tms9914_priv *priv, unsigned int address_status_bits) +{ + if (address_status_bits & HR_LA) { + if (address_status_bits & HR_ATN) + priv->listener_state = listener_addressed; + else + priv->listener_state = listener_active; + } else { + priv->listener_state = listener_idle; + } +} + +static unsigned int update_status_nolock(struct gpib_board *board, struct tms9914_priv *priv) +{ + int address_status; + int bsr_bits; + + address_status = read_byte(priv, ADSR); + + // check for remote/local + if (address_status & HR_REM) + set_bit(REM_NUM, &board->status); + else + clear_bit(REM_NUM, &board->status); + // check for lockout + if (address_status & HR_LLO) + set_bit(LOK_NUM, &board->status); + else + clear_bit(LOK_NUM, &board->status); + // check for ATN + if (address_status & HR_ATN) + set_bit(ATN_NUM, &board->status); + else + clear_bit(ATN_NUM, &board->status); + // check for talker/listener addressed + update_talker_state(priv, address_status); + if (priv->talker_state == talker_active || priv->talker_state == talker_addressed) + set_bit(TACS_NUM, &board->status); + else + clear_bit(TACS_NUM, &board->status); + + update_listener_state(priv, address_status); + if (priv->listener_state == listener_active || priv->listener_state == listener_addressed) + set_bit(LACS_NUM, &board->status); + else + clear_bit(LACS_NUM, &board->status); + // Check for SRQI - not reset elsewhere except in autospoll + if (board->status & SRQI) { + bsr_bits = read_byte(priv, BSR); + if (!(bsr_bits & BSR_SRQ_BIT)) + clear_bit(SRQI_NUM, &board->status); + } + + dev_dbg(board->gpib_dev, "status 0x%lx, state 0x%lx\n", board->status, priv->state); + + return board->status; +} + +int tms9914_line_status(const struct gpib_board *board, struct tms9914_priv *priv) +{ + int bsr_bits; + int status = VALID_ALL; + + bsr_bits = read_byte(priv, BSR); + + if (bsr_bits & BSR_REN_BIT) + status |= BUS_REN; + if (bsr_bits & BSR_IFC_BIT) + status |= BUS_IFC; + if (bsr_bits & BSR_SRQ_BIT) + status |= BUS_SRQ; + if (bsr_bits & BSR_EOI_BIT) + status |= BUS_EOI; + if (bsr_bits & BSR_NRFD_BIT) + status |= BUS_NRFD; + if (bsr_bits & BSR_NDAC_BIT) + status |= BUS_NDAC; + if (bsr_bits & BSR_DAV_BIT) + status |= BUS_DAV; + if (bsr_bits & BSR_ATN_BIT) + status |= BUS_ATN; + + return status; +} +EXPORT_SYMBOL(tms9914_line_status); + +static int check_for_eos(struct tms9914_priv *priv, u8 byte) +{ + static const u8 seven_bit_compare_mask = 0x7f; + + if ((priv->eos_flags & REOS) == 0) + return 0; + + if (priv->eos_flags & BIN) { + if (priv->eos == byte) + return 1; + } else { + if ((priv->eos & seven_bit_compare_mask) == (byte & seven_bit_compare_mask)) + return 1; + } + return 0; +} + +static int wait_for_read_byte(struct gpib_board *board, struct tms9914_priv *priv) +{ + if (wait_event_interruptible(board->wait, + test_bit(READ_READY_BN, &priv->state) || + test_bit(DEV_CLEAR_BN, &priv->state) || + test_bit(TIMO_NUM, &board->status))) + return -ERESTARTSYS; + + if (test_bit(TIMO_NUM, &board->status)) + return -ETIMEDOUT; + + if (test_bit(DEV_CLEAR_BN, &priv->state)) + return -EINTR; + return 0; +} + +static inline u8 tms9914_read_data_in(struct gpib_board *board, + struct tms9914_priv *priv, int *end) +{ + unsigned long flags; + u8 data; + + spin_lock_irqsave(&board->spinlock, flags); + clear_bit(READ_READY_BN, &priv->state); + data = read_byte(priv, DIR); + if (test_and_clear_bit(RECEIVED_END_BN, &priv->state)) + *end = 1; + else + *end = 0; + switch (priv->holdoff_mode) { + case TMS9914_HOLDOFF_EOI: + if (*end) + priv->holdoff_active = 1; + break; + case TMS9914_HOLDOFF_ALL: + priv->holdoff_active = 1; + break; + case TMS9914_HOLDOFF_NONE: + break; + default: + dev_err(board->gpib_dev, "bug! bad holdoff mode %i\n", priv->holdoff_mode); + break; + } + spin_unlock_irqrestore(&board->spinlock, flags); + + return data; +} + +static int pio_read(struct gpib_board *board, struct tms9914_priv *priv, u8 *buffer, + size_t length, int *end, size_t *bytes_read) +{ + ssize_t retval = 0; + + *bytes_read = 0; + *end = 0; + while (*bytes_read < length && *end == 0) { + tms9914_release_holdoff(priv); + retval = wait_for_read_byte(board, priv); + if (retval < 0) + return retval; + buffer[(*bytes_read)++] = tms9914_read_data_in(board, priv, end); + + if (check_for_eos(priv, buffer[*bytes_read - 1])) + *end = 1; + } + + return retval; +} + +int tms9914_read(struct gpib_board *board, struct tms9914_priv *priv, u8 *buffer, + size_t length, int *end, size_t *bytes_read) +{ + ssize_t retval = 0; + size_t num_bytes; + + *end = 0; + *bytes_read = 0; + if (length == 0) + return 0; + + clear_bit(DEV_CLEAR_BN, &priv->state); + + // transfer data (except for last byte) + if (length > 1) { + if (priv->eos_flags & REOS) + tms9914_set_holdoff_mode(priv, TMS9914_HOLDOFF_ALL); + else + tms9914_set_holdoff_mode(priv, TMS9914_HOLDOFF_EOI); + // PIO transfer + retval = pio_read(board, priv, buffer, length - 1, end, &num_bytes); + *bytes_read += num_bytes; + if (retval < 0) + return retval; + buffer += num_bytes; + length -= num_bytes; + } + // read last bytes if we haven't received an END yet + if (*end == 0) { + // make sure we holdoff after last byte read + tms9914_set_holdoff_mode(priv, TMS9914_HOLDOFF_ALL); + retval = pio_read(board, priv, buffer, length, end, &num_bytes); + *bytes_read += num_bytes; + if (retval < 0) + return retval; + } + return 0; +} +EXPORT_SYMBOL(tms9914_read); + +static int pio_write_wait(struct gpib_board *board, struct tms9914_priv *priv) +{ + // wait until next byte is ready to be sent + if (wait_event_interruptible(board->wait, + test_bit(WRITE_READY_BN, &priv->state) || + test_bit(BUS_ERROR_BN, &priv->state) || + test_bit(DEV_CLEAR_BN, &priv->state) || + test_bit(TIMO_NUM, &board->status))) + return -ERESTARTSYS; + + if (test_bit(TIMO_NUM, &board->status)) + return -ETIMEDOUT; + if (test_bit(BUS_ERROR_BN, &priv->state)) + return -EIO; + if (test_bit(DEV_CLEAR_BN, &priv->state)) + return -EINTR; + + return 0; +} + +static int pio_write(struct gpib_board *board, struct tms9914_priv *priv, u8 *buffer, + size_t length, size_t *bytes_written) +{ + ssize_t retval = 0; + unsigned long flags; + + *bytes_written = 0; + while (*bytes_written < length) { + retval = pio_write_wait(board, priv); + if (retval < 0) + break; + + spin_lock_irqsave(&board->spinlock, flags); + clear_bit(WRITE_READY_BN, &priv->state); + write_byte(priv, buffer[(*bytes_written)++], CDOR); + spin_unlock_irqrestore(&board->spinlock, flags); + } + retval = pio_write_wait(board, priv); + if (retval < 0) + return retval; + + return length; +} + +int tms9914_write(struct gpib_board *board, struct tms9914_priv *priv, + u8 *buffer, size_t length, int send_eoi, size_t *bytes_written) +{ + ssize_t retval = 0; + + *bytes_written = 0; + if (length == 0) + return 0; + + clear_bit(BUS_ERROR_BN, &priv->state); + clear_bit(DEV_CLEAR_BN, &priv->state); + + if (send_eoi) + length-- ; /* save the last byte for sending EOI */ + + if (length > 0) { + size_t num_bytes; + // PIO transfer + retval = pio_write(board, priv, buffer, length, &num_bytes); + *bytes_written += num_bytes; + if (retval < 0) + return retval; + } + if (send_eoi) { + size_t num_bytes; + /*send EOI */ + write_byte(priv, AUX_SEOI, AUXCR); + + retval = pio_write(board, priv, &buffer[*bytes_written], 1, &num_bytes); + *bytes_written += num_bytes; + } + return retval; +} +EXPORT_SYMBOL(tms9914_write); + +static void check_my_address_state(struct gpib_board *board, + struct tms9914_priv *priv, int cmd_byte) +{ + if (cmd_byte == MLA(board->pad)) { + priv->primary_listen_addressed = 1; + // become active listener + if (board->sad < 0) + write_byte(priv, AUX_LON | AUX_CS, AUXCR); + } else if (board->sad >= 0 && priv->primary_listen_addressed && + cmd_byte == MSA(board->sad)) { + // become active listener + write_byte(priv, AUX_LON | AUX_CS, AUXCR); + } else if (cmd_byte != MLA(board->pad) && (cmd_byte & 0xe0) == LAD) { + priv->primary_listen_addressed = 0; + } else if (cmd_byte == UNL) { + priv->primary_listen_addressed = 0; + write_byte(priv, AUX_LON, AUXCR); + } else if (cmd_byte == MTA(board->pad)) { + priv->primary_talk_addressed = 1; + if (board->sad < 0) + // make active talker + write_byte(priv, AUX_TON | AUX_CS, AUXCR); + } else if (board->sad >= 0 && priv->primary_talk_addressed && + cmd_byte == MSA(board->sad)) { + // become active talker + write_byte(priv, AUX_TON | AUX_CS, AUXCR); + } else if (cmd_byte != MTA(board->pad) && (cmd_byte & 0xe0) == TAD) { + // Other Talk Address + priv->primary_talk_addressed = 0; + write_byte(priv, AUX_TON, AUXCR); + } else if (cmd_byte == UNT) { + priv->primary_talk_addressed = 0; + write_byte(priv, AUX_TON, AUXCR); + } +} + +int tms9914_command(struct gpib_board *board, struct tms9914_priv *priv, u8 *buffer, + size_t length, size_t *bytes_written) +{ + int retval = 0; + unsigned long flags; + + *bytes_written = 0; + while (*bytes_written < length) { + if (wait_event_interruptible(board->wait, + test_bit(COMMAND_READY_BN, + &priv->state) || + test_bit(TIMO_NUM, &board->status))) + break; + if (test_bit(TIMO_NUM, &board->status)) + break; + + spin_lock_irqsave(&board->spinlock, flags); + clear_bit(COMMAND_READY_BN, &priv->state); + write_byte(priv, buffer[*bytes_written], CDOR); + spin_unlock_irqrestore(&board->spinlock, flags); + + check_my_address_state(board, priv, buffer[*bytes_written]); + + ++(*bytes_written); + } + // wait until last command byte is written + if (wait_event_interruptible(board->wait, + test_bit(COMMAND_READY_BN, + &priv->state) || test_bit(TIMO_NUM, &board->status))) + retval = -ERESTARTSYS; + if (test_bit(TIMO_NUM, &board->status)) + retval = -ETIMEDOUT; + + return retval; +} +EXPORT_SYMBOL(tms9914_command); + +irqreturn_t tms9914_interrupt(struct gpib_board *board, struct tms9914_priv *priv) +{ + int status0, status1; + + // read interrupt status (also clears status) + status0 = read_byte(priv, ISR0); + status1 = read_byte(priv, ISR1); + return tms9914_interrupt_have_status(board, priv, status0, status1); +} +EXPORT_SYMBOL(tms9914_interrupt); + +irqreturn_t tms9914_interrupt_have_status(struct gpib_board *board, struct tms9914_priv *priv, + int status0, int status1) +{ + // record reception of END + if (status0 & HR_END) + set_bit(RECEIVED_END_BN, &priv->state); + // get incoming data in PIO mode + if ((status0 & HR_BI)) + set_bit(READ_READY_BN, &priv->state); + if ((status0 & HR_BO)) { + if (read_byte(priv, ADSR) & HR_ATN) + set_bit(COMMAND_READY_BN, &priv->state); + else + set_bit(WRITE_READY_BN, &priv->state); + } + + if (status0 & HR_SPAS) { + priv->spoll_status &= ~request_service_bit; + write_byte(priv, priv->spoll_status, SPMR); + // FIXME: set SPOLL status bit + } + // record service request in status + if (status1 & HR_SRQ) + set_bit(SRQI_NUM, &board->status); + // have been addressed (with secondary addressing disabled) + if (status1 & HR_MA) + // clear dac holdoff + write_byte(priv, AUX_VAL, AUXCR); + // unrecognized command received + if (status1 & HR_UNC) { + unsigned short command_byte = read_byte(priv, CPTR) & gpib_command_mask; + + switch (command_byte) { + case PP_CONFIG: + priv->ppoll_configure_state = 1; + /* + * AUX_PTS generates another UNC interrupt on the next command byte + * if it is in the secondary address group (such as PPE and PPD). + */ + write_byte(priv, AUX_PTS, AUXCR); + write_byte(priv, AUX_VAL, AUXCR); + break; + case PPU: + tms9914_parallel_poll_configure(board, priv, command_byte); + write_byte(priv, AUX_VAL, AUXCR); + break; + default: + if (is_PPE(command_byte) || is_PPD(command_byte)) { + if (priv->ppoll_configure_state) { + tms9914_parallel_poll_configure(board, priv, command_byte); + write_byte(priv, AUX_VAL, AUXCR); + } else {// bad parallel poll configure byte + // clear dac holdoff + write_byte(priv, AUX_INVAL, AUXCR); + } + } else { + // clear dac holdoff + write_byte(priv, AUX_INVAL, AUXCR); + } + break; + } + + if (in_primary_command_group(command_byte) && command_byte != PP_CONFIG) + priv->ppoll_configure_state = 0; + } + + if (status1 & HR_ERR) { + dev_dbg(board->gpib_dev, "gpib bus error\n"); + set_bit(BUS_ERROR_BN, &priv->state); + } + + if (status1 & HR_IFC) { + push_gpib_event(board, EVENT_IFC); + clear_bit(CIC_NUM, &board->status); + } + + if (status1 & HR_GET) { + push_gpib_event(board, EVENT_DEV_TRG); + // clear dac holdoff + write_byte(priv, AUX_VAL, AUXCR); + } + + if (status1 & HR_DCAS) { + push_gpib_event(board, EVENT_DEV_CLR); + // clear dac holdoff + write_byte(priv, AUX_VAL, AUXCR); + set_bit(DEV_CLEAR_BN, &priv->state); + } + + // check for being addressed with secondary addressing + if (status1 & HR_APT) { + if (board->sad < 0) + dev_err(board->gpib_dev, "bug, APT interrupt without secondary addressing?\n"); + if ((read_byte(priv, CPTR) & gpib_command_mask) == MSA(board->sad)) + write_byte(priv, AUX_VAL, AUXCR); + else + write_byte(priv, AUX_INVAL, AUXCR); + } + + if ((status0 & priv->imr0_bits) || (status1 & priv->imr1_bits)) { + dev_dbg(board->gpib_dev, "isr0 0x%x, imr0 0x%x, isr1 0x%x, imr1 0x%x\n", + status0, priv->imr0_bits, status1, priv->imr1_bits); + update_status_nolock(board, priv); + wake_up_interruptible(&board->wait); + } + return IRQ_HANDLED; +} +EXPORT_SYMBOL(tms9914_interrupt_have_status); + +void tms9914_board_reset(struct tms9914_priv *priv) +{ + /* chip reset */ + write_byte(priv, AUX_CHIP_RESET | AUX_CS, AUXCR); + + /* disable all interrupts */ + priv->imr0_bits = 0; + write_byte(priv, priv->imr0_bits, IMR0); + priv->imr1_bits = 0; + write_byte(priv, priv->imr1_bits, IMR1); + write_byte(priv, AUX_DAI | AUX_CS, AUXCR); + + /* clear registers by reading */ + read_byte(priv, CPTR); + read_byte(priv, ISR0); + read_byte(priv, ISR1); + + write_byte(priv, 0, SPMR); + + /* parallel poll unconfigure */ + write_byte(priv, 0, PPR); + /* request for data holdoff */ + tms9914_set_holdoff_mode(priv, TMS9914_HOLDOFF_ALL); +} +EXPORT_SYMBOL_GPL(tms9914_board_reset); + +void tms9914_online(struct gpib_board *board, struct tms9914_priv *priv) +{ + /* set GPIB address */ + tms9914_primary_address(board, priv, board->pad); + tms9914_secondary_address(board, priv, board->sad, board->sad >= 0); + + /* enable tms9914 interrupts */ + priv->imr0_bits |= HR_MACIE | HR_RLCIE | HR_ENDIE | HR_BOIE | HR_BIIE | + HR_SPASIE; + priv->imr1_bits |= HR_MAIE | HR_SRQIE | HR_UNCIE | HR_ERRIE | HR_IFCIE | + HR_GETIE | HR_DCASIE; + write_byte(priv, priv->imr0_bits, IMR0); + write_byte(priv, priv->imr1_bits, IMR1); + write_byte(priv, AUX_DAI, AUXCR); + + /* turn off reset state */ + write_byte(priv, AUX_CHIP_RESET, AUXCR); +} +EXPORT_SYMBOL_GPL(tms9914_online); + +#ifdef CONFIG_HAS_IOPORT +// wrapper for inb +u8 tms9914_ioport_read_byte(struct tms9914_priv *priv, unsigned int register_num) +{ + return inb(priv->iobase + register_num * priv->offset); +} +EXPORT_SYMBOL_GPL(tms9914_ioport_read_byte); + +// wrapper for outb +void tms9914_ioport_write_byte(struct tms9914_priv *priv, u8 data, unsigned int register_num) +{ + outb(data, priv->iobase + register_num * priv->offset); + if (register_num == AUXCR) + udelay(1); +} +EXPORT_SYMBOL_GPL(tms9914_ioport_write_byte); +#endif + +// wrapper for readb +u8 tms9914_iomem_read_byte(struct tms9914_priv *priv, unsigned int register_num) +{ + return readb(priv->mmiobase + register_num * priv->offset); +} +EXPORT_SYMBOL_GPL(tms9914_iomem_read_byte); + +// wrapper for writeb +void tms9914_iomem_write_byte(struct tms9914_priv *priv, u8 data, unsigned int register_num) +{ + writeb(data, priv->mmiobase + register_num * priv->offset); + if (register_num == AUXCR) + udelay(1); +} +EXPORT_SYMBOL_GPL(tms9914_iomem_write_byte); + +static int __init tms9914_init_module(void) +{ + return 0; +} + +static void __exit tms9914_exit_module(void) +{ +} + +module_init(tms9914_init_module); +module_exit(tms9914_exit_module); + diff --git a/drivers/gpib/tnt4882/Makefile b/drivers/gpib/tnt4882/Makefile new file mode 100644 index 000000000000..fa1687ad0d1b --- /dev/null +++ b/drivers/gpib/tnt4882/Makefile @@ -0,0 +1,6 @@ +obj-$(CONFIG_GPIB_NI_PCI_ISA) += tnt4882.o + +tnt4882-objs := tnt4882_gpib.o mite.o + + + diff --git a/drivers/gpib/tnt4882/mite.c b/drivers/gpib/tnt4882/mite.c new file mode 100644 index 000000000000..847b96f411bd --- /dev/null +++ b/drivers/gpib/tnt4882/mite.c @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: GPL-2.0-only + +/* + * Hardware driver for NI Mite PCI interface chip, + * adapted from COMEDI + * + * Copyright (C) 1997-8 David A. Schleef + * Copyright (C) 2002 Frank Mori Hess + * + * The PCI-MIO E series driver was originally written by + * Tomasz Motylewski <...>, and ported to comedi by ds. + * + * References for specifications: + * + * 321747b.pdf Register Level Programmer Manual (obsolete) + * 321747c.pdf Register Level Programmer Manual (new) + * DAQ-STC reference manual + * + * Other possibly relevant info: + * + * 320517c.pdf User manual (obsolete) + * 320517f.pdf User manual (new) + * 320889a.pdf delete + * 320906c.pdf maximum signal ratings + * 321066a.pdf about 16x + * 321791a.pdf discontinuation of at-mio-16e-10 rev. c + * 321808a.pdf about at-mio-16e-10 rev P + * 321837a.pdf discontinuation of at-mio-16de-10 rev d + * 321838a.pdf about at-mio-16de-10 rev N + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mite.h" + +#define PCI_MITE_SIZE 4096 +#define PCI_DAQ_SIZE 4096 + +struct mite_struct *mite_devices; + +#define TOP_OF_PAGE(x) ((x) | (~(PAGE_MASK))) + +void mite_init(void) +{ + struct pci_dev *pcidev; + struct mite_struct *mite; + + for (pcidev = pci_get_device(PCI_VENDOR_ID_NATINST, PCI_ANY_ID, NULL); + pcidev; + pcidev = pci_get_device(PCI_VENDOR_ID_NATINST, PCI_ANY_ID, pcidev)) { + mite = kzalloc(sizeof(*mite), GFP_KERNEL); + if (!mite) + return; + + mite->pcidev = pcidev; + pci_dev_get(mite->pcidev); + mite->next = mite_devices; + mite_devices = mite; + } +} + +int mite_setup(struct mite_struct *mite) +{ + u32 addr; + + if (pci_enable_device(mite->pcidev)) { + pr_err("mite: error enabling mite.\n"); + return -EIO; + } + pci_set_master(mite->pcidev); + if (pci_request_regions(mite->pcidev, "mite")) { + pr_err("mite: failed to request mite io regions.\n"); + return -EIO; + } + addr = pci_resource_start(mite->pcidev, 0); + mite->mite_phys_addr = addr; + mite->mite_io_addr = ioremap(addr, pci_resource_len(mite->pcidev, 0)); + if (!mite->mite_io_addr) { + pr_err("mite: failed to remap mite io memory address.\n"); + return -ENOMEM; + } + addr = pci_resource_start(mite->pcidev, 1); + mite->daq_phys_addr = addr; + mite->daq_io_addr = ioremap(mite->daq_phys_addr, pci_resource_len(mite->pcidev, 1)); + if (!mite->daq_io_addr) { + pr_err("mite: failed to remap daq io memory address.\n"); + return -ENOMEM; + } + writel(mite->daq_phys_addr | WENAB, mite->mite_io_addr + MITE_IODWBSR); + mite->used = 1; + return 0; +} + +void mite_cleanup(void) +{ + struct mite_struct *mite, *next; + + for (mite = mite_devices; mite; mite = next) { + next = mite->next; + if (mite->pcidev) + pci_dev_put(mite->pcidev); + kfree(mite); + } +} + +void mite_unsetup(struct mite_struct *mite) +{ + if (!mite) + return; + if (mite->mite_io_addr) { + iounmap(mite->mite_io_addr); + mite->mite_io_addr = NULL; + } + if (mite->daq_io_addr) { + iounmap(mite->daq_io_addr); + mite->daq_io_addr = NULL; + } + if (mite->mite_phys_addr) { + pci_release_regions(mite->pcidev); + pci_disable_device(mite->pcidev); + mite->mite_phys_addr = 0; + } + mite->used = 0; +} diff --git a/drivers/gpib/tnt4882/mite.h b/drivers/gpib/tnt4882/mite.h new file mode 100644 index 000000000000..a1fdba9672a0 --- /dev/null +++ b/drivers/gpib/tnt4882/mite.h @@ -0,0 +1,234 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +/* + * Hardware driver for NI Mite PCI interface chip + * + * Copyright (C) 1999 David A. Schleef + */ + +#ifndef _MITE_H_ +#define _MITE_H_ + +#include + +#define PCI_VENDOR_ID_NATINST 0x1093 + +//#define DEBUG_MITE + +#ifdef DEBUG_MITE +#define MDPRINTK(format, args...) pr_debug(format, ## args) +#else +#define MDPRINTK(args...) +#endif + +#define MITE_RING_SIZE 3000 +struct mite_dma_chain { + u32 count; + u32 addr; + u32 next; +}; + +struct mite_struct { + struct mite_struct *next; + int used; + + struct pci_dev *pcidev; + unsigned long mite_phys_addr; + void __iomem *mite_io_addr; + unsigned long daq_phys_addr; + void __iomem *daq_io_addr; + + int DMA_CheckNearEnd; + + struct mite_dma_chain ring[MITE_RING_SIZE]; +}; + +extern struct mite_struct *mite_devices; + +extern inline unsigned int mite_irq(struct mite_struct *mite) +{ + return mite->pcidev->irq; +}; + +extern inline unsigned int mite_device_id(struct mite_struct *mite) +{ + return mite->pcidev->device; +}; + +void mite_init(void); +void mite_cleanup(void); +int mite_setup(struct mite_struct *mite); +void mite_unsetup(struct mite_struct *mite); +void mite_list_devices(void); + +#define CHAN_OFFSET(x) (0x100 * (x)) + +/* DMA base for chan 0 is 0x500, chan 1 is 0x600 */ + +#define MITE_CHOR 0x500 +#define CHOR_DMARESET BIT(31) +#define CHOR_SET_SEND_TC BIT(11) +#define CHOR_CLR_SEND_TC BIT(10) +#define CHOR_SET_LPAUSE BIT(9) +#define CHOR_CLR_LPAUSE BIT(8) +#define CHOR_CLRDONE BIT(7) +#define CHOR_CLRRB BIT(6) +#define CHOR_CLRLC BIT(5) +#define CHOR_FRESET BIT(4) +#define CHOR_ABORT BIT(3) +#define CHOR_STOP BIT(2) +#define CHOR_CONT BIT(1) +#define CHOR_START BIT(0) +#define CHOR_PON (CHOR_CLR_SEND_TC | CHOR_CLR_LPAUSE) + +#define MITE_CHCR 0x504 +#define CHCR_SET_DMA_IE BIT(31) +#define CHCR_CLR_DMA_IE BIT(30) +#define CHCR_SET_LINKP_IE BIT(29) +#define CHCR_CLR_LINKP_IE BIT(28) +#define CHCR_SET_SAR_IE BIT(27) +#define CHCR_CLR_SAR_IE BIT(26) +#define CHCR_SET_DONE_IE BIT(25) +#define CHCR_CLR_DONE_IE BIT(24) +#define CHCR_SET_MRDY_IE BIT(23) +#define CHCR_CLR_MRDY_IE BIT(22) +#define CHCR_SET_DRDY_IE BIT(21) +#define CHCR_CLR_DRDY_IE BIT(20) +#define CHCR_SET_LC_IE BIT(19) +#define CHCR_CLR_LC_IE BIT(18) +#define CHCR_SET_CONT_RB_IE BIT(17) +#define CHCR_CLR_CONT_RB_IE BIT(16) +#define CHCR_FIFODIS BIT(15) +#define CHCR_FIFO_ON 0 +#define CHCR_BURSTEN BIT(14) +#define CHCR_NO_BURSTEN 0 +#define CHCR_NFTP(x) ((x) << 11) +#define CHCR_NFTP0 CHCR_NFTP(0) +#define CHCR_NFTP1 CHCR_NFTP(1) +#define CHCR_NFTP2 CHCR_NFTP(2) +#define CHCR_NFTP4 CHCR_NFTP(3) +#define CHCR_NFTP8 CHCR_NFTP(4) +#define CHCR_NFTP16 CHCR_NFTP(5) +#define CHCR_NETP(x) ((x) << 11) +#define CHCR_NETP0 CHCR_NETP(0) +#define CHCR_NETP1 CHCR_NETP(1) +#define CHCR_NETP2 CHCR_NETP(2) +#define CHCR_NETP4 CHCR_NETP(3) +#define CHCR_NETP8 CHCR_NETP(4) +#define CHCR_CHEND1 BIT(5) +#define CHCR_CHEND0 BIT(4) +#define CHCR_DIR BIT(3) +#define CHCR_DEV_TO_MEM CHCR_DIR +#define CHCR_MEM_TO_DEV 0 +#define CHCR_NORMAL ((0) << 0) +#define CHCR_CONTINUE ((1) << 0) +#define CHCR_RINGBUFF ((2) << 0) +#define CHCR_LINKSHORT ((4) << 0) +#define CHCR_LINKLONG ((5) << 0) +#define CHCRPON (CHCR_CLR_DMA_IE | CHCR_CLR_LINKP_IE | CHCR_CLR_SAR_IE | \ + CHCR_CLR_DONE_IE | CHCR_CLR_MRDY_IE | CHCR_CLR_DRDY_IE | \ + CHCR_CLR_LC_IE | CHCR_CLR_CONT_IE) + +#define MITE_TCR 0x508 + +/* CR bits */ +#define CR_RL(x) ((x) << 21) +#define CR_RL0 CR_RL(0) +#define CR_RL1 CR_RL(1) +#define CR_RL2 CR_RL(2) +#define CR_RL4 CR_RL(3) +#define CR_RL8 CR_RL(4) +#define CR_RL16 CR_RL(5) +#define CR_RL32 CR_RL(6) +#define CR_RL64 CR_RL(7) +#define CR_RD(x) ((x) << 19) +#define CR_RD0 CR_RD(0) +#define CR_RD32 CR_RD(1) +#define CR_RD512 CR_RD(2) +#define CR_RD8192 CR_RD(3) +#define CR_REQS(x) ((x) << 16) +#define CR_REQSDRQ0 CR_REQS(4) +#define CR_REQSDRQ1 CR_REQS(5) +#define CR_REQSDRQ2 CR_REQS(6) +#define CR_REQSDRQ3 CR_REQS(7) +#define CR_ASEQX(x) ((x) << 10) +#define CR_ASEQX0 CR_ASEQX(0) +#define CR_ASEQDONT CR_ASEQX0 +#define CR_ASEQXP1 CR_ASEQX(1) +#define CR_ASEQUP CR_ASEQXP1 +#define CR_ASEQXP2 CR_ASEQX(2) +#define CR_ASEQDOWN CR_ASEQXP2 +#define CR_ASEQXP4 CR_ASEQX(3) +#define CR_ASEQXP8 CR_ASEQX(4) +#define CR_ASEQXP16 CR_ASEQX(5) +#define CR_ASEQXP32 CR_ASEQX(6) +#define CR_ASEQXP64 CR_ASEQX(7) +#define CR_ASEQXM1 CR_ASEQX(9) +#define CR_ASEQXM2 CR_ASEQX(10) +#define CR_ASEQXM4 CR_ASEQX(11) +#define CR_ASEQXM8 CR_ASEQX(12) +#define CR_ASEQXM16 CR_ASEQX(13) +#define CR_ASEQXM32 CR_ASEQX(14) +#define CR_ASEQXM64 CR_ASEQX(15) +#define CR_PSIZEBYTE BIT(8) +#define CR_PSIZEHALF (2 << 8) +#define CR_PSIZEWORD (3 << 8) +#define CR_PORTCPU (0 << 6) +#define CR_PORTIO BIT(6) +#define CR_PORTVXI (2 << 6) +#define CR_PORTMXI (3 << 6) +#define CR_AMDEVICE BIT(0) + +#define CHSR_INT 0x80000000 +#define CHSR_DONE 0x02000000 +#define CHSR_LINKC 0x00080000 + +#define MITE_MCR 0x50c +#define MCRPON 0 + +#define MITE_MAR 0x510 + +#define MITE_DCR 0x514 +#define DCR_NORMAL BIT(29) +#define DCRPON 0 + +#define MITE_DAR 0x518 + +#define MITE_LKCR 0x51c + +#define MITE_LKAR 0x520 +#define MITE_LLKAR 0x524 +#define MITE_BAR 0x528 +#define MITE_BCR 0x52c +#define MITE_SAR 0x530 +#define MITE_WSCR 0x534 +#define MITE_WSER 0x538 +#define MITE_CHSR 0x53c +#define MITE_FCR 0x540 + +#define MITE_FIFO 0x80 +#define MITE_FIFOEND 0xff + +#define MITE_AMRAM 0x00 +#define MITE_AMDEVICE 0x01 +#define MITE_AMHOST_A32_SINGLE 0x09 +#define MITE_AMHOST_A24_SINGLE 0x39 +#define MITE_AMHOST_A16_SINGLE 0x29 +#define MITE_AMHOST_A32_BLOCK 0x0b +#define MITE_AMHOST_A32D64_BLOCK 0x08 +#define MITE_AMHOST_A24_BLOCK 0x3b + +enum mite_registers { + MITE_IODWBSR = 0xc0, // IO Device Window Base Size Register + MITE_CSIGR = 0x460, // chip signature + MITE_IODWBSR_1 = 0xc4, // IO Device Window Base Size Register 1 (used by 6602 boards) + MITE_IODWCR_1 = 0xf4 +}; + +enum MITE_IODWBSR_bits { + WENAB = 0x80, // window enable + WENAB_6602 = 0x8c // window enable for 6602 boards +}; + +#endif + diff --git a/drivers/gpib/tnt4882/tnt4882_gpib.c b/drivers/gpib/tnt4882/tnt4882_gpib.c new file mode 100644 index 000000000000..c03a976b7380 --- /dev/null +++ b/drivers/gpib/tnt4882/tnt4882_gpib.c @@ -0,0 +1,1838 @@ +// SPDX-License-Identifier: GPL-2.0 + +/*************************************************************************** + * National Instruments boards using tnt4882 or compatible chips (at-gpib, etc). + * copyright : (C) 2001, 2002 by Frank Mori Hess + ***************************************************************************/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#define dev_fmt pr_fmt +#define DRV_NAME KBUILD_MODNAME + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nec7210.h" +#include "gpibP.h" +#include "mite.h" +#include "tnt4882_registers.h" + +static const int ISAPNP_VENDOR_ID_NI = ISAPNP_VENDOR('N', 'I', 'C'); +static const int ISAPNP_ID_NI_ATGPIB_TNT = 0xc601; +enum { + PCI_DEVICE_ID_NI_GPIB = 0xc801, + PCI_DEVICE_ID_NI_GPIB_PLUS = 0xc811, + PCI_DEVICE_ID_NI_GPIB_PLUS2 = 0x71ad, + PCI_DEVICE_ID_NI_PXIGPIB = 0xc821, + PCI_DEVICE_ID_NI_PMCGPIB = 0xc831, + PCI_DEVICE_ID_NI_PCIEGPIB = 0x70cf, + PCI_DEVICE_ID_NI_PCIE2GPIB = 0x710e, +// Measurement Computing PCI-488 same design as PCI-GPIB with TNT5004 + PCI_DEVICE_ID_MC_PCI488 = 0x7259, + PCI_DEVICE_ID_CEC_NI_GPIB = 0x7258 +}; + +// struct which defines private_data for tnt4882 devices +struct tnt4882_priv { + struct nec7210_priv nec7210_priv; + struct mite_struct *mite; + struct pnp_dev *pnp_dev; + unsigned int irq; + unsigned short imr0_bits; + unsigned short imr3_bits; + unsigned short auxg_bits; // bits written to auxiliary register G +}; + +static irqreturn_t tnt4882_internal_interrupt(struct gpib_board *board); + +// register offset for nec7210 compatible registers +static const int atgpib_reg_offset = 2; + +// number of ioports used +static const int atgpib_iosize = 32; + +/* paged io */ +static inline unsigned int tnt_paged_readb(struct tnt4882_priv *priv, unsigned long offset) +{ + iowrite8(AUX_PAGEIN, priv->nec7210_priv.mmiobase + AUXMR * priv->nec7210_priv.offset); + udelay(1); + return ioread8(priv->nec7210_priv.mmiobase + offset); +} + +static inline void tnt_paged_writeb(struct tnt4882_priv *priv, unsigned int value, + unsigned long offset) +{ + iowrite8(AUX_PAGEIN, priv->nec7210_priv.mmiobase + AUXMR * priv->nec7210_priv.offset); + udelay(1); + iowrite8(value, priv->nec7210_priv.mmiobase + offset); +} + +/* readb/writeb wrappers */ +static inline unsigned short tnt_readb(struct tnt4882_priv *priv, unsigned long offset) +{ + void __iomem *address = priv->nec7210_priv.mmiobase + offset; + unsigned long flags; + unsigned short retval; + spinlock_t *register_lock = &priv->nec7210_priv.register_page_lock; + + spin_lock_irqsave(register_lock, flags); + switch (offset) { + case CSR: + case SASR: + case ISR0: + case BSR: + switch (priv->nec7210_priv.type) { + case TNT4882: + case TNT5004: + retval = ioread8(address); + break; + case NAT4882: + retval = tnt_paged_readb(priv, offset - tnt_pagein_offset); + break; + case NEC7210: + retval = 0; + break; + default: + retval = 0; + break; + } + break; + default: + retval = ioread8(address); + break; + } + spin_unlock_irqrestore(register_lock, flags); + return retval; +} + +static inline void tnt_writeb(struct tnt4882_priv *priv, unsigned short value, unsigned long offset) +{ + void __iomem *address = priv->nec7210_priv.mmiobase + offset; + unsigned long flags; + spinlock_t *register_lock = &priv->nec7210_priv.register_page_lock; + + spin_lock_irqsave(register_lock, flags); + switch (offset) { + case KEYREG: + case IMR0: + case BCR: + switch (priv->nec7210_priv.type) { + case TNT4882: + case TNT5004: + iowrite8(value, address); + break; + case NAT4882: + tnt_paged_writeb(priv, value, offset - tnt_pagein_offset); + break; + case NEC7210: + break; + default: + break; + } + break; + default: + iowrite8(value, address); + break; + } + spin_unlock_irqrestore(register_lock, flags); +} + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("GPIB driver for National Instruments boards using tnt4882 or compatible chips"); + +static int tnt4882_line_status(const struct gpib_board *board) +{ + int status = VALID_ALL; + int bcsr_bits; + struct tnt4882_priv *tnt_priv; + + tnt_priv = board->private_data; + + bcsr_bits = tnt_readb(tnt_priv, BSR); + + if (bcsr_bits & BCSR_REN_BIT) + status |= BUS_REN; + if (bcsr_bits & BCSR_IFC_BIT) + status |= BUS_IFC; + if (bcsr_bits & BCSR_SRQ_BIT) + status |= BUS_SRQ; + if (bcsr_bits & BCSR_EOI_BIT) + status |= BUS_EOI; + if (bcsr_bits & BCSR_NRFD_BIT) + status |= BUS_NRFD; + if (bcsr_bits & BCSR_NDAC_BIT) + status |= BUS_NDAC; + if (bcsr_bits & BCSR_DAV_BIT) + status |= BUS_DAV; + if (bcsr_bits & BCSR_ATN_BIT) + status |= BUS_ATN; + + return status; +} + +static int tnt4882_t1_delay(struct gpib_board *board, unsigned int nano_sec) +{ + struct tnt4882_priv *tnt_priv = board->private_data; + struct nec7210_priv *nec_priv = &tnt_priv->nec7210_priv; + unsigned int retval; + + retval = nec7210_t1_delay(board, nec_priv, nano_sec); + if (nec_priv->type == NEC7210) + return retval; + + if (nano_sec <= 350) { + tnt_writeb(tnt_priv, MSTD, KEYREG); + retval = 350; + } else { + tnt_writeb(tnt_priv, 0, KEYREG); + } + if (nano_sec > 500 && nano_sec <= 1100) { + write_byte(nec_priv, AUXRI | USTD, AUXMR); + retval = 1100; + } else { + write_byte(nec_priv, AUXRI, AUXMR); + } + return retval; +} + +static int fifo_word_available(struct tnt4882_priv *tnt_priv) +{ + int status2; + int retval; + + status2 = tnt_readb(tnt_priv, STS2); + retval = (status2 & AEFN) && (status2 & BEFN); + + return retval; +} + +static int fifo_byte_available(struct tnt4882_priv *tnt_priv) +{ + int status2; + int retval; + + status2 = tnt_readb(tnt_priv, STS2); + retval = (status2 & AEFN) || (status2 & BEFN); + + return retval; +} + +static int fifo_xfer_done(struct tnt4882_priv *tnt_priv) +{ + int status1; + int retval; + + status1 = tnt_readb(tnt_priv, STS1); + retval = status1 & (S_DONE | S_HALT); + + return retval; +} + +static int drain_fifo_words(struct tnt4882_priv *tnt_priv, u8 *buffer, int num_bytes) +{ + int count = 0; + struct nec7210_priv *nec_priv = &tnt_priv->nec7210_priv; + + while (fifo_word_available(tnt_priv) && count + 2 <= num_bytes) { + short word; + + word = ioread16(nec_priv->mmiobase + FIFOB); + buffer[count++] = word & 0xff; + buffer[count++] = (word >> 8) & 0xff; + } + return count; +} + +static void tnt4882_release_holdoff(struct gpib_board *board, struct tnt4882_priv *tnt_priv) +{ + struct nec7210_priv *nec_priv = &tnt_priv->nec7210_priv; + unsigned short sasr_bits; + + sasr_bits = tnt_readb(tnt_priv, SASR); + + /* + * tnt4882 not in one-chip mode won't always release holdoff unless we + * are in the right mode when release handshake command is given + */ + if (sasr_bits & AEHS_BIT) /* holding off due to holdoff on end mode*/ { + nec7210_set_handshake_mode(board, nec_priv, HR_HLDE); + write_byte(nec_priv, AUX_FH, AUXMR); + } else if (sasr_bits & ANHS1_BIT) { /* held off due to holdoff on all data mode*/ + nec7210_set_handshake_mode(board, nec_priv, HR_HLDA); + write_byte(nec_priv, AUX_FH, AUXMR); + nec7210_set_handshake_mode(board, nec_priv, HR_HLDE); + } else { /* held off due to holdoff immediately command */ + nec7210_set_handshake_mode(board, nec_priv, HR_HLDE); + write_byte(nec_priv, AUX_FH, AUXMR); + } +} + +static int tnt4882_accel_read(struct gpib_board *board, u8 *buffer, size_t length, int *end, + size_t *bytes_read) +{ + size_t count = 0; + ssize_t retval = 0; + struct tnt4882_priv *tnt_priv = board->private_data; + struct nec7210_priv *nec_priv = &tnt_priv->nec7210_priv; + unsigned int bits; + s32 hw_count; + unsigned long flags; + + *bytes_read = 0; + // FIXME: really, DEV_CLEAR_BN should happen elsewhere to prevent race + clear_bit(DEV_CLEAR_BN, &nec_priv->state); + clear_bit(ADR_CHANGE_BN, &nec_priv->state); + + nec7210_set_reg_bits(nec_priv, IMR1, HR_ENDIE, HR_ENDIE); + if (nec_priv->type != TNT4882 && nec_priv->type != TNT5004) + nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAI, HR_DMAI); + else + nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAI, 0); + tnt_writeb(tnt_priv, nec_priv->auxa_bits | HR_HLDA, CCR); + bits = TNT_B_16BIT | TNT_IN | TNT_CCEN; + tnt_writeb(tnt_priv, bits, CFG); + tnt_writeb(tnt_priv, RESET_FIFO, CMDR); + udelay(1); + // load 2's complement of count into hardware counters + hw_count = -length; + tnt_writeb(tnt_priv, hw_count & 0xff, CNT0); + tnt_writeb(tnt_priv, (hw_count >> 8) & 0xff, CNT1); + tnt_writeb(tnt_priv, (hw_count >> 16) & 0xff, CNT2); + tnt_writeb(tnt_priv, (hw_count >> 24) & 0xff, CNT3); + + tnt4882_release_holdoff(board, tnt_priv); + + tnt_writeb(tnt_priv, GO, CMDR); + udelay(1); + + spin_lock_irqsave(&board->spinlock, flags); + tnt_priv->imr3_bits |= HR_DONE | HR_NEF; + tnt_writeb(tnt_priv, tnt_priv->imr3_bits, IMR3); + spin_unlock_irqrestore(&board->spinlock, flags); + + while (count + 2 <= length && + test_bit(RECEIVED_END_BN, &nec_priv->state) == 0 && + fifo_xfer_done(tnt_priv) == 0) { + // wait until a word is ready + if (wait_event_interruptible(board->wait, + fifo_word_available(tnt_priv) || + fifo_xfer_done(tnt_priv) || + test_bit(RECEIVED_END_BN, &nec_priv->state) || + test_bit(DEV_CLEAR_BN, &nec_priv->state) || + test_bit(ADR_CHANGE_BN, &nec_priv->state) || + test_bit(TIMO_NUM, &board->status))) { + retval = -ERESTARTSYS; + break; + } + if (test_bit(TIMO_NUM, &board->status)) { + retval = -ETIMEDOUT; + break; + } + if (test_bit(DEV_CLEAR_BN, &nec_priv->state)) { + retval = -EINTR; + break; + } + if (test_bit(ADR_CHANGE_BN, &nec_priv->state)) { + retval = -EINTR; + break; + } + + spin_lock_irqsave(&board->spinlock, flags); + count += drain_fifo_words(tnt_priv, &buffer[count], length - count); + tnt_priv->imr3_bits |= HR_NEF; + tnt_writeb(tnt_priv, tnt_priv->imr3_bits, IMR3); + spin_unlock_irqrestore(&board->spinlock, flags); + + if (need_resched()) + schedule(); + } + // wait for last byte + if (count < length) { + spin_lock_irqsave(&board->spinlock, flags); + tnt_priv->imr3_bits |= HR_DONE | HR_NEF; + tnt_writeb(tnt_priv, tnt_priv->imr3_bits, IMR3); + spin_unlock_irqrestore(&board->spinlock, flags); + + if (wait_event_interruptible(board->wait, + fifo_xfer_done(tnt_priv) || + test_bit(RECEIVED_END_BN, &nec_priv->state) || + test_bit(DEV_CLEAR_BN, &nec_priv->state) || + test_bit(ADR_CHANGE_BN, &nec_priv->state) || + test_bit(TIMO_NUM, &board->status))) { + retval = -ERESTARTSYS; + } + if (test_bit(TIMO_NUM, &board->status)) + retval = -ETIMEDOUT; + if (test_bit(DEV_CLEAR_BN, &nec_priv->state)) + retval = -EINTR; + if (test_bit(ADR_CHANGE_BN, &nec_priv->state)) + retval = -EINTR; + count += drain_fifo_words(tnt_priv, &buffer[count], length - count); + if (fifo_byte_available(tnt_priv) && count < length) + buffer[count++] = tnt_readb(tnt_priv, FIFOB); + } + if (count < length) + tnt_writeb(tnt_priv, STOP, CMDR); + udelay(1); + + nec7210_set_reg_bits(nec_priv, IMR1, HR_ENDIE, 0); + nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAI, 0); + /* + * force handling of any pending interrupts (seems to be needed + * to keep interrupts from getting hosed, plus for syncing + * with RECEIVED_END below) + */ + tnt4882_internal_interrupt(board); + /* RECEIVED_END should be in sync now */ + if (test_and_clear_bit(RECEIVED_END_BN, &nec_priv->state)) + *end = 1; + if (retval < 0) { + // force immediate holdoff + write_byte(nec_priv, AUX_HLDI, AUXMR); + + set_bit(RFD_HOLDOFF_BN, &nec_priv->state); + } + *bytes_read = count; + + return retval; +} + +static int fifo_space_available(struct tnt4882_priv *tnt_priv) +{ + int status2; + int retval; + + status2 = tnt_readb(tnt_priv, STS2); + retval = (status2 & AFFN) && (status2 & BFFN); + + return retval; +} + +static unsigned int tnt_transfer_count(struct tnt4882_priv *tnt_priv) +{ + unsigned int count = 0; + + count |= tnt_readb(tnt_priv, CNT0) & 0xff; + count |= (tnt_readb(tnt_priv, CNT1) << 8) & 0xff00; + count |= (tnt_readb(tnt_priv, CNT2) << 16) & 0xff0000; + count |= (tnt_readb(tnt_priv, CNT3) << 24) & 0xff000000; + // return two's complement + return -count; +}; + +static int write_wait(struct gpib_board *board, struct tnt4882_priv *tnt_priv, + int wait_for_done, int send_commands) +{ + struct nec7210_priv *nec_priv = &tnt_priv->nec7210_priv; + + if (wait_event_interruptible(board->wait, + (!wait_for_done && fifo_space_available(tnt_priv)) || + fifo_xfer_done(tnt_priv) || + test_bit(BUS_ERROR_BN, &nec_priv->state) || + test_bit(DEV_CLEAR_BN, &nec_priv->state) || + test_bit(TIMO_NUM, &board->status))) + return -ERESTARTSYS; + + if (test_bit(TIMO_NUM, &board->status)) + return -ETIMEDOUT; + if (test_and_clear_bit(BUS_ERROR_BN, &nec_priv->state)) + return (send_commands) ? -ENOTCONN : -ECOMM; + if (test_bit(DEV_CLEAR_BN, &nec_priv->state)) + return -EINTR; + return 0; +} + +static int generic_write(struct gpib_board *board, u8 *buffer, size_t length, + int send_eoi, int send_commands, size_t *bytes_written) +{ + size_t count = 0; + ssize_t retval = 0; + struct tnt4882_priv *tnt_priv = board->private_data; + struct nec7210_priv *nec_priv = &tnt_priv->nec7210_priv; + unsigned int bits; + s32 hw_count; + unsigned long flags; + + *bytes_written = 0; + // FIXME: really, DEV_CLEAR_BN should happen elsewhere to prevent race + clear_bit(DEV_CLEAR_BN, &nec_priv->state); + + nec7210_set_reg_bits(nec_priv, IMR1, HR_ERRIE, HR_ERRIE); + + if (nec_priv->type != TNT4882 && nec_priv->type != TNT5004) + nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAO, HR_DMAO); + else + nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAO, 0); + + tnt_writeb(tnt_priv, RESET_FIFO, CMDR); + udelay(1); + + bits = TNT_B_16BIT; + if (send_eoi) { + bits |= TNT_CCEN; + if (nec_priv->type != TNT4882 && nec_priv->type != TNT5004) + tnt_writeb(tnt_priv, AUX_SEOI, CCR); + } + if (send_commands) + bits |= TNT_COMMAND; + tnt_writeb(tnt_priv, bits, CFG); + + // load 2's complement of count into hardware counters + hw_count = -length; + tnt_writeb(tnt_priv, hw_count & 0xff, CNT0); + tnt_writeb(tnt_priv, (hw_count >> 8) & 0xff, CNT1); + tnt_writeb(tnt_priv, (hw_count >> 16) & 0xff, CNT2); + tnt_writeb(tnt_priv, (hw_count >> 24) & 0xff, CNT3); + + tnt_writeb(tnt_priv, GO, CMDR); + udelay(1); + + spin_lock_irqsave(&board->spinlock, flags); + tnt_priv->imr3_bits |= HR_DONE; + tnt_writeb(tnt_priv, tnt_priv->imr3_bits, IMR3); + spin_unlock_irqrestore(&board->spinlock, flags); + + while (count < length) { + // wait until byte is ready to be sent + retval = write_wait(board, tnt_priv, 0, send_commands); + if (retval < 0) + break; + if (fifo_xfer_done(tnt_priv)) + break; + spin_lock_irqsave(&board->spinlock, flags); + while (fifo_space_available(tnt_priv) && count < length) { + u16 word; + + word = buffer[count++] & 0xff; + if (count < length) + word |= (buffer[count++] << 8) & 0xff00; + iowrite16(word, nec_priv->mmiobase + FIFOB); + } +// avoid unnecessary HR_NFF interrupts +// tnt_priv->imr3_bits |= HR_NFF; +// tnt_writeb(tnt_priv, tnt_priv->imr3_bits, IMR3); + spin_unlock_irqrestore(&board->spinlock, flags); + + if (need_resched()) + schedule(); + } + // wait last byte has been sent + if (retval == 0) + retval = write_wait(board, tnt_priv, 1, send_commands); + + tnt_writeb(tnt_priv, STOP, CMDR); + udelay(1); + + nec7210_set_reg_bits(nec_priv, IMR1, HR_ERR, 0x0); + nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAO, 0x0); + /* + * force handling of any interrupts that happened + * while they were masked (this appears to be needed) + */ + tnt4882_internal_interrupt(board); + *bytes_written = length - tnt_transfer_count(tnt_priv); + return retval; +} + +static int tnt4882_accel_write(struct gpib_board *board, u8 *buffer, + size_t length, int send_eoi, size_t *bytes_written) +{ + return generic_write(board, buffer, length, send_eoi, 0, bytes_written); +} + +static int tnt4882_command(struct gpib_board *board, u8 *buffer, size_t length, + size_t *bytes_written) +{ + return generic_write(board, buffer, length, 0, 1, bytes_written); +} + +static irqreturn_t tnt4882_internal_interrupt(struct gpib_board *board) +{ + struct tnt4882_priv *priv = board->private_data; + int isr0_bits, isr3_bits, imr3_bits; + unsigned long flags; + + spin_lock_irqsave(&board->spinlock, flags); + + nec7210_interrupt(board, &priv->nec7210_priv); + + isr0_bits = tnt_readb(priv, ISR0); + isr3_bits = tnt_readb(priv, ISR3); + imr3_bits = priv->imr3_bits; + + if (isr0_bits & TNT_IFCI_BIT) + push_gpib_event(board, EVENT_IFC); + // XXX don't need this wakeup, one below should do? +// wake_up_interruptible(&board->wait); + + if (isr3_bits & HR_NFF) + priv->imr3_bits &= ~HR_NFF; + if (isr3_bits & HR_NEF) + priv->imr3_bits &= ~HR_NEF; + if (isr3_bits & HR_DONE) + priv->imr3_bits &= ~HR_DONE; + if (isr3_bits & (HR_INTR | HR_TLCI)) { + dev_dbg(board->gpib_dev, "minor %i isr0 0x%x imr0 0x%x isr3 0x%x imr3 0x%x\n", + board->minor, isr0_bits, priv->imr0_bits, isr3_bits, imr3_bits); + tnt_writeb(priv, priv->imr3_bits, IMR3); + wake_up_interruptible(&board->wait); + } + spin_unlock_irqrestore(&board->spinlock, flags); + return IRQ_HANDLED; +} + +static irqreturn_t tnt4882_interrupt(int irq, void *arg) +{ + return tnt4882_internal_interrupt(arg); +} + +// wrappers for interface functions +static int tnt4882_read(struct gpib_board *board, u8 *buffer, size_t length, int *end, + size_t *bytes_read) +{ + struct tnt4882_priv *priv = board->private_data; + struct nec7210_priv *nec_priv = &priv->nec7210_priv; + int retval; + int dummy; + + retval = nec7210_read(board, &priv->nec7210_priv, buffer, length, end, bytes_read); + + if (retval < 0) { // force immediate holdoff + write_byte(nec_priv, AUX_HLDI, AUXMR); + + set_bit(RFD_HOLDOFF_BN, &nec_priv->state); + + nec7210_read_data_in(board, nec_priv, &dummy); + } + return retval; +} + +static int tnt4882_write(struct gpib_board *board, u8 *buffer, size_t length, int send_eoi, + size_t *bytes_written) +{ + struct tnt4882_priv *priv = board->private_data; + + return nec7210_write(board, &priv->nec7210_priv, buffer, length, send_eoi, bytes_written); +} + +static int tnt4882_command_unaccel(struct gpib_board *board, u8 *buffer, + size_t length, size_t *bytes_written) +{ + struct tnt4882_priv *priv = board->private_data; + + return nec7210_command(board, &priv->nec7210_priv, buffer, length, bytes_written); +} + +static int tnt4882_take_control(struct gpib_board *board, int synchronous) +{ + struct tnt4882_priv *priv = board->private_data; + + return nec7210_take_control(board, &priv->nec7210_priv, synchronous); +} + +static int tnt4882_go_to_standby(struct gpib_board *board) +{ + struct tnt4882_priv *priv = board->private_data; + + return nec7210_go_to_standby(board, &priv->nec7210_priv); +} + +static int tnt4882_request_system_control(struct gpib_board *board, int request_control) +{ + struct tnt4882_priv *priv = board->private_data; + int retval; + + if (request_control) { + tnt_writeb(priv, SETSC, CMDR); + udelay(1); + } + retval = nec7210_request_system_control(board, &priv->nec7210_priv, request_control); + if (!request_control) { + tnt_writeb(priv, CLRSC, CMDR); + udelay(1); + } + return retval; +} + +static void tnt4882_interface_clear(struct gpib_board *board, int assert) +{ + struct tnt4882_priv *priv = board->private_data; + + nec7210_interface_clear(board, &priv->nec7210_priv, assert); +} + +static void tnt4882_remote_enable(struct gpib_board *board, int enable) +{ + struct tnt4882_priv *priv = board->private_data; + + nec7210_remote_enable(board, &priv->nec7210_priv, enable); +} + +static int tnt4882_enable_eos(struct gpib_board *board, u8 eos_byte, int compare_8_bits) +{ + struct tnt4882_priv *priv = board->private_data; + + return nec7210_enable_eos(board, &priv->nec7210_priv, eos_byte, compare_8_bits); +} + +static void tnt4882_disable_eos(struct gpib_board *board) +{ + struct tnt4882_priv *priv = board->private_data; + + nec7210_disable_eos(board, &priv->nec7210_priv); +} + +static unsigned int tnt4882_update_status(struct gpib_board *board, unsigned int clear_mask) +{ + unsigned long flags; + u8 line_status; + struct tnt4882_priv *priv = board->private_data; + + spin_lock_irqsave(&board->spinlock, flags); + board->status &= ~clear_mask; + nec7210_update_status_nolock(board, &priv->nec7210_priv); + /* set / clear SRQ state since it is not cleared by interrupt */ + line_status = tnt_readb(priv, BSR); + if (line_status & BCSR_SRQ_BIT) + set_bit(SRQI_NUM, &board->status); + else + clear_bit(SRQI_NUM, &board->status); + spin_unlock_irqrestore(&board->spinlock, flags); + return board->status; +} + +static int tnt4882_primary_address(struct gpib_board *board, unsigned int address) +{ + struct tnt4882_priv *priv = board->private_data; + + return nec7210_primary_address(board, &priv->nec7210_priv, address); +} + +static int tnt4882_secondary_address(struct gpib_board *board, unsigned int address, int enable) +{ + struct tnt4882_priv *priv = board->private_data; + + return nec7210_secondary_address(board, &priv->nec7210_priv, address, enable); +} + +static int tnt4882_parallel_poll(struct gpib_board *board, u8 *result) +{ + struct tnt4882_priv *tnt_priv = board->private_data; + + if (tnt_priv->nec7210_priv.type != NEC7210) { + tnt_priv->auxg_bits |= RPP2_BIT; + write_byte(&tnt_priv->nec7210_priv, tnt_priv->auxg_bits, AUXMR); + udelay(2); // FIXME use parallel poll timeout + *result = read_byte(&tnt_priv->nec7210_priv, CPTR); + tnt_priv->auxg_bits &= ~RPP2_BIT; + write_byte(&tnt_priv->nec7210_priv, tnt_priv->auxg_bits, AUXMR); + return 0; + } else { + return nec7210_parallel_poll(board, &tnt_priv->nec7210_priv, result); + } +} + +static void tnt4882_parallel_poll_configure(struct gpib_board *board, u8 config) +{ + struct tnt4882_priv *priv = board->private_data; + + if (priv->nec7210_priv.type == TNT5004) { + /* configure locally */ + write_byte(&priv->nec7210_priv, AUXRI | 0x4, AUXMR); + if (config) + /* set response + clear sense */ + write_byte(&priv->nec7210_priv, PPR | config, AUXMR); + else + /* disable ppoll */ + write_byte(&priv->nec7210_priv, PPR | 0x10, AUXMR); + } else { + nec7210_parallel_poll_configure(board, &priv->nec7210_priv, config); + } +} + +static void tnt4882_parallel_poll_response(struct gpib_board *board, int ist) +{ + struct tnt4882_priv *priv = board->private_data; + + nec7210_parallel_poll_response(board, &priv->nec7210_priv, ist); +} + +/* + * this is just used by the old nec7210 isa interfaces, the newer + * boards use tnt4882_serial_poll_response2 + */ +static void tnt4882_serial_poll_response(struct gpib_board *board, u8 status) +{ + struct tnt4882_priv *priv = board->private_data; + + nec7210_serial_poll_response(board, &priv->nec7210_priv, status); +} + +static void tnt4882_serial_poll_response2(struct gpib_board *board, u8 status, + int new_reason_for_service) +{ + struct tnt4882_priv *priv = board->private_data; + unsigned long flags; + const int MSS = status & request_service_bit; + const int reqt = MSS && new_reason_for_service; + const int reqf = MSS == 0; + + spin_lock_irqsave(&board->spinlock, flags); + if (reqt) { + priv->nec7210_priv.srq_pending = 1; + clear_bit(SPOLL_NUM, &board->status); + } else { + if (reqf) + priv->nec7210_priv.srq_pending = 0; + } + if (reqt) + /* + * It may seem like a race to issue reqt before updating + * the status byte, but it is not. The chip does not + * issue the reqt until the SPMR is written to at + * a later time. + */ + write_byte(&priv->nec7210_priv, AUX_REQT, AUXMR); + else if (reqf) + write_byte(&priv->nec7210_priv, AUX_REQF, AUXMR); + /* + * We need to always zero bit 6 of the status byte before writing it to + * the SPMR to insure we are using + * serial poll mode SP1, and not accidentally triggering mode SP3. + */ + write_byte(&priv->nec7210_priv, status & ~request_service_bit, SPMR); + spin_unlock_irqrestore(&board->spinlock, flags); +} + +static u8 tnt4882_serial_poll_status(struct gpib_board *board) +{ + struct tnt4882_priv *priv = board->private_data; + + return nec7210_serial_poll_status(board, &priv->nec7210_priv); +} + +static void tnt4882_return_to_local(struct gpib_board *board) +{ + struct tnt4882_priv *priv = board->private_data; + + nec7210_return_to_local(board, &priv->nec7210_priv); +} + +static void tnt4882_board_reset(struct tnt4882_priv *tnt_priv, struct gpib_board *board) +{ + struct nec7210_priv *nec_priv = &tnt_priv->nec7210_priv; + + tnt_priv->imr0_bits = 0; + tnt_writeb(tnt_priv, tnt_priv->imr0_bits, IMR0); + tnt_priv->imr3_bits = 0; + tnt_writeb(tnt_priv, tnt_priv->imr3_bits, IMR3); + tnt_readb(tnt_priv, IMR0); + tnt_readb(tnt_priv, IMR3); + nec7210_board_reset(nec_priv, board); +} + +static int tnt4882_allocate_private(struct gpib_board *board) +{ + struct tnt4882_priv *tnt_priv; + + board->private_data = kmalloc(sizeof(struct tnt4882_priv), GFP_KERNEL); + if (!board->private_data) + return -1; + tnt_priv = board->private_data; + memset(tnt_priv, 0, sizeof(struct tnt4882_priv)); + init_nec7210_private(&tnt_priv->nec7210_priv); + return 0; +} + +static void tnt4882_free_private(struct gpib_board *board) +{ + kfree(board->private_data); + board->private_data = NULL; +} + +static void tnt4882_init(struct tnt4882_priv *tnt_priv, const struct gpib_board *board) +{ + struct nec7210_priv *nec_priv = &tnt_priv->nec7210_priv; + + /* Turbo488 software reset */ + tnt_writeb(tnt_priv, SOFT_RESET, CMDR); + udelay(1); + + // turn off one-chip mode + tnt_writeb(tnt_priv, NODMA, HSSEL); + tnt_writeb(tnt_priv, 0, ACCWR); + // make sure we are in 7210 mode + tnt_writeb(tnt_priv, AUX_7210, AUXCR); + udelay(1); + // registers might be swapped, so write it to the swapped address too + tnt_writeb(tnt_priv, AUX_7210, SWAPPED_AUXCR); + udelay(1); + // turn on one-chip mode + if (nec_priv->type == TNT4882 || nec_priv->type == TNT5004) + tnt_writeb(tnt_priv, NODMA | TNT_ONE_CHIP_BIT, HSSEL); + else + tnt_writeb(tnt_priv, NODMA, HSSEL); + + nec7210_board_reset(nec_priv, board); + // read-clear isr0 + tnt_readb(tnt_priv, ISR0); + + // enable passing of nat4882 interrupts + tnt_priv->imr3_bits = HR_TLCI; + tnt_writeb(tnt_priv, tnt_priv->imr3_bits, IMR3); + + // enable interrupt + tnt_writeb(tnt_priv, 0x1, INTRT); + + // force immediate holdoff + write_byte(&tnt_priv->nec7210_priv, AUX_HLDI, AUXMR); + + set_bit(RFD_HOLDOFF_BN, &nec_priv->state); + + tnt_priv->auxg_bits = AUXRG | NTNL_BIT; + write_byte(&tnt_priv->nec7210_priv, tnt_priv->auxg_bits, AUXMR); + + nec7210_board_online(nec_priv, board); + // enable interface clear interrupt for event queue + tnt_priv->imr0_bits = TNT_IMR0_ALWAYS_BITS | TNT_ATNI_BIT | TNT_IFCIE_BIT; + tnt_writeb(tnt_priv, tnt_priv->imr0_bits, IMR0); +} + +static int ni_pci_attach(struct gpib_board *board, const struct gpib_board_config *config) +{ + struct tnt4882_priv *tnt_priv; + struct nec7210_priv *nec_priv; + int isr_flags = IRQF_SHARED; + int retval; + struct mite_struct *mite; + + board->status = 0; + + if (tnt4882_allocate_private(board)) + return -ENOMEM; + tnt_priv = board->private_data; + nec_priv = &tnt_priv->nec7210_priv; + nec_priv->type = TNT4882; + nec_priv->read_byte = nec7210_locking_iomem_read_byte; + nec_priv->write_byte = nec7210_locking_iomem_write_byte; + nec_priv->offset = atgpib_reg_offset; + + if (!mite_devices) + return -ENODEV; + + for (mite = mite_devices; mite; mite = mite->next) { + short found_board; + + if (mite->used) + continue; + if (config->pci_bus >= 0 && config->pci_bus != mite->pcidev->bus->number) + continue; + if (config->pci_slot >= 0 && config->pci_slot != PCI_SLOT(mite->pcidev->devfn)) + continue; + switch (mite_device_id(mite)) { + case PCI_DEVICE_ID_NI_GPIB: + case PCI_DEVICE_ID_NI_GPIB_PLUS: + case PCI_DEVICE_ID_NI_GPIB_PLUS2: + case PCI_DEVICE_ID_NI_PXIGPIB: + case PCI_DEVICE_ID_NI_PMCGPIB: + case PCI_DEVICE_ID_NI_PCIEGPIB: + case PCI_DEVICE_ID_NI_PCIE2GPIB: +// support for Measurement Computing PCI-488 + case PCI_DEVICE_ID_MC_PCI488: + case PCI_DEVICE_ID_CEC_NI_GPIB: + found_board = 1; + break; + default: + found_board = 0; + break; + } + if (found_board) + break; + } + if (!mite) + return -ENODEV; + + tnt_priv->mite = mite; + retval = mite_setup(tnt_priv->mite); + if (retval < 0) + return retval; + + nec_priv->mmiobase = tnt_priv->mite->daq_io_addr; + + // get irq + retval = request_irq(mite_irq(tnt_priv->mite), tnt4882_interrupt, isr_flags, "ni-pci-gpib", + board); + if (retval) { + dev_err(board->gpib_dev, "failed to obtain pci irq %d\n", mite_irq(tnt_priv->mite)); + return retval; + } + tnt_priv->irq = mite_irq(tnt_priv->mite); + + // TNT5004 detection + switch (tnt_readb(tnt_priv, CSR) & 0xf0) { + case 0x30: + nec_priv->type = TNT4882; + break; + case 0x40: + nec_priv->type = TNT5004; + break; + } + tnt4882_init(tnt_priv, board); + + return 0; +} + +static void ni_pci_detach(struct gpib_board *board) +{ + struct tnt4882_priv *tnt_priv = board->private_data; + struct nec7210_priv *nec_priv; + + if (tnt_priv) { + nec_priv = &tnt_priv->nec7210_priv; + + if (nec_priv->mmiobase) + tnt4882_board_reset(tnt_priv, board); + if (tnt_priv->irq) + free_irq(tnt_priv->irq, board); + if (tnt_priv->mite) + mite_unsetup(tnt_priv->mite); + } + tnt4882_free_private(board); +} + +static int ni_isapnp_find(struct pnp_dev **dev) +{ + *dev = pnp_find_dev(NULL, ISAPNP_VENDOR_ID_NI, + ISAPNP_FUNCTION(ISAPNP_ID_NI_ATGPIB_TNT), NULL); + if (!*dev || !(*dev)->card) + return -ENODEV; + if (pnp_device_attach(*dev) < 0) + return -EBUSY; + if (pnp_activate_dev(*dev) < 0) { + pnp_device_detach(*dev); + return -EAGAIN; + } + if (!pnp_port_valid(*dev, 0) || !pnp_irq_valid(*dev, 0)) { + pnp_device_detach(*dev); + return -EINVAL; + } + return 0; +} + +static int ni_isa_attach_common(struct gpib_board *board, const struct gpib_board_config *config, + enum nec7210_chipset chipset) +{ + struct tnt4882_priv *tnt_priv; + struct nec7210_priv *nec_priv; + int isr_flags = 0; + u32 iobase; + int irq; + int retval; + + board->status = 0; + + if (tnt4882_allocate_private(board)) + return -ENOMEM; + tnt_priv = board->private_data; + nec_priv = &tnt_priv->nec7210_priv; + nec_priv->type = chipset; + nec_priv->read_byte = nec7210_locking_ioport_read_byte; + nec_priv->write_byte = nec7210_locking_ioport_write_byte; + nec_priv->offset = atgpib_reg_offset; + + // look for plug-n-play board + if (config->ibbase == 0) { + struct pnp_dev *dev; + + retval = ni_isapnp_find(&dev); + if (retval < 0) + return retval; + tnt_priv->pnp_dev = dev; + iobase = pnp_port_start(dev, 0); + irq = pnp_irq(dev, 0); + } else { + iobase = config->ibbase; + irq = config->ibirq; + } + // allocate ioports + if (!request_region(iobase, atgpib_iosize, "atgpib")) + return -EBUSY; + + nec_priv->mmiobase = ioport_map(iobase, atgpib_iosize); + if (!nec_priv->mmiobase) + return -EBUSY; + + // get irq + retval = request_irq(irq, tnt4882_interrupt, isr_flags, "atgpib", board); + if (retval) { + dev_err(board->gpib_dev, "failed to request ISA irq %d\n", irq); + return retval; + } + tnt_priv->irq = irq; + + tnt4882_init(tnt_priv, board); + + return 0; +} + +static int ni_tnt_isa_attach(struct gpib_board *board, const struct gpib_board_config *config) +{ + return ni_isa_attach_common(board, config, TNT4882); +} + +static int ni_nat4882_isa_attach(struct gpib_board *board, const struct gpib_board_config *config) +{ + return ni_isa_attach_common(board, config, NAT4882); +} + +static int ni_nec_isa_attach(struct gpib_board *board, const struct gpib_board_config *config) +{ + return ni_isa_attach_common(board, config, NEC7210); +} + +static void ni_isa_detach(struct gpib_board *board) +{ + struct tnt4882_priv *tnt_priv = board->private_data; + struct nec7210_priv *nec_priv; + + if (tnt_priv) { + nec_priv = &tnt_priv->nec7210_priv; + if (nec_priv->iobase) + tnt4882_board_reset(tnt_priv, board); + if (tnt_priv->irq) + free_irq(tnt_priv->irq, board); + if (nec_priv->mmiobase) + ioport_unmap(nec_priv->mmiobase); + if (nec_priv->iobase) + release_region(nec_priv->iobase, atgpib_iosize); + if (tnt_priv->pnp_dev) + pnp_device_detach(tnt_priv->pnp_dev); + } + tnt4882_free_private(board); +} + +static int tnt4882_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + return 0; +} + +static struct gpib_interface ni_pci_interface = { + .name = "ni_pci", + .attach = ni_pci_attach, + .detach = ni_pci_detach, + .read = tnt4882_accel_read, + .write = tnt4882_accel_write, + .command = tnt4882_command, + .take_control = tnt4882_take_control, + .go_to_standby = tnt4882_go_to_standby, + .request_system_control = tnt4882_request_system_control, + .interface_clear = tnt4882_interface_clear, + .remote_enable = tnt4882_remote_enable, + .enable_eos = tnt4882_enable_eos, + .disable_eos = tnt4882_disable_eos, + .parallel_poll = tnt4882_parallel_poll, + .parallel_poll_configure = tnt4882_parallel_poll_configure, + .parallel_poll_response = tnt4882_parallel_poll_response, + .local_parallel_poll_mode = NULL, // XXX + .line_status = tnt4882_line_status, + .update_status = tnt4882_update_status, + .primary_address = tnt4882_primary_address, + .secondary_address = tnt4882_secondary_address, + .serial_poll_response2 = tnt4882_serial_poll_response2, + .serial_poll_status = tnt4882_serial_poll_status, + .t1_delay = tnt4882_t1_delay, + .return_to_local = tnt4882_return_to_local, +}; + +static struct gpib_interface ni_pci_accel_interface = { + .name = "ni_pci_accel", + .attach = ni_pci_attach, + .detach = ni_pci_detach, + .read = tnt4882_accel_read, + .write = tnt4882_accel_write, + .command = tnt4882_command, + .take_control = tnt4882_take_control, + .go_to_standby = tnt4882_go_to_standby, + .request_system_control = tnt4882_request_system_control, + .interface_clear = tnt4882_interface_clear, + .remote_enable = tnt4882_remote_enable, + .enable_eos = tnt4882_enable_eos, + .disable_eos = tnt4882_disable_eos, + .parallel_poll = tnt4882_parallel_poll, + .parallel_poll_configure = tnt4882_parallel_poll_configure, + .parallel_poll_response = tnt4882_parallel_poll_response, + .local_parallel_poll_mode = NULL, // XXX + .line_status = tnt4882_line_status, + .update_status = tnt4882_update_status, + .primary_address = tnt4882_primary_address, + .secondary_address = tnt4882_secondary_address, + .serial_poll_response2 = tnt4882_serial_poll_response2, + .serial_poll_status = tnt4882_serial_poll_status, + .t1_delay = tnt4882_t1_delay, + .return_to_local = tnt4882_return_to_local, +}; + +static struct gpib_interface ni_isa_interface = { + .name = "ni_isa", + .attach = ni_tnt_isa_attach, + .detach = ni_isa_detach, + .read = tnt4882_accel_read, + .write = tnt4882_accel_write, + .command = tnt4882_command, + .take_control = tnt4882_take_control, + .go_to_standby = tnt4882_go_to_standby, + .request_system_control = tnt4882_request_system_control, + .interface_clear = tnt4882_interface_clear, + .remote_enable = tnt4882_remote_enable, + .enable_eos = tnt4882_enable_eos, + .disable_eos = tnt4882_disable_eos, + .parallel_poll = tnt4882_parallel_poll, + .parallel_poll_configure = tnt4882_parallel_poll_configure, + .parallel_poll_response = tnt4882_parallel_poll_response, + .local_parallel_poll_mode = NULL, // XXX + .line_status = tnt4882_line_status, + .update_status = tnt4882_update_status, + .primary_address = tnt4882_primary_address, + .secondary_address = tnt4882_secondary_address, + .serial_poll_response2 = tnt4882_serial_poll_response2, + .serial_poll_status = tnt4882_serial_poll_status, + .t1_delay = tnt4882_t1_delay, + .return_to_local = tnt4882_return_to_local, +}; + +static struct gpib_interface ni_nat4882_isa_interface = { + .name = "ni_nat4882_isa", + .attach = ni_nat4882_isa_attach, + .detach = ni_isa_detach, + .read = tnt4882_read, + .write = tnt4882_write, + .command = tnt4882_command_unaccel, + .take_control = tnt4882_take_control, + .go_to_standby = tnt4882_go_to_standby, + .request_system_control = tnt4882_request_system_control, + .interface_clear = tnt4882_interface_clear, + .remote_enable = tnt4882_remote_enable, + .enable_eos = tnt4882_enable_eos, + .disable_eos = tnt4882_disable_eos, + .parallel_poll = tnt4882_parallel_poll, + .parallel_poll_configure = tnt4882_parallel_poll_configure, + .parallel_poll_response = tnt4882_parallel_poll_response, + .local_parallel_poll_mode = NULL, // XXX + .line_status = tnt4882_line_status, + .update_status = tnt4882_update_status, + .primary_address = tnt4882_primary_address, + .secondary_address = tnt4882_secondary_address, + .serial_poll_response2 = tnt4882_serial_poll_response2, + .serial_poll_status = tnt4882_serial_poll_status, + .t1_delay = tnt4882_t1_delay, + .return_to_local = tnt4882_return_to_local, +}; + +static struct gpib_interface ni_nec_isa_interface = { + .name = "ni_nec_isa", + .attach = ni_nec_isa_attach, + .detach = ni_isa_detach, + .read = tnt4882_read, + .write = tnt4882_write, + .command = tnt4882_command_unaccel, + .take_control = tnt4882_take_control, + .go_to_standby = tnt4882_go_to_standby, + .request_system_control = tnt4882_request_system_control, + .interface_clear = tnt4882_interface_clear, + .remote_enable = tnt4882_remote_enable, + .enable_eos = tnt4882_enable_eos, + .disable_eos = tnt4882_disable_eos, + .parallel_poll = tnt4882_parallel_poll, + .parallel_poll_configure = tnt4882_parallel_poll_configure, + .parallel_poll_response = tnt4882_parallel_poll_response, + .local_parallel_poll_mode = NULL, // XXX + .line_status = NULL, + .update_status = tnt4882_update_status, + .primary_address = tnt4882_primary_address, + .secondary_address = tnt4882_secondary_address, + .serial_poll_response = tnt4882_serial_poll_response, + .serial_poll_status = tnt4882_serial_poll_status, + .t1_delay = tnt4882_t1_delay, + .return_to_local = tnt4882_return_to_local, +}; + +static struct gpib_interface ni_isa_accel_interface = { + .name = "ni_isa_accel", + .attach = ni_tnt_isa_attach, + .detach = ni_isa_detach, + .read = tnt4882_accel_read, + .write = tnt4882_accel_write, + .command = tnt4882_command, + .take_control = tnt4882_take_control, + .go_to_standby = tnt4882_go_to_standby, + .request_system_control = tnt4882_request_system_control, + .interface_clear = tnt4882_interface_clear, + .remote_enable = tnt4882_remote_enable, + .enable_eos = tnt4882_enable_eos, + .disable_eos = tnt4882_disable_eos, + .parallel_poll = tnt4882_parallel_poll, + .parallel_poll_configure = tnt4882_parallel_poll_configure, + .parallel_poll_response = tnt4882_parallel_poll_response, + .local_parallel_poll_mode = NULL, // XXX + .line_status = tnt4882_line_status, + .update_status = tnt4882_update_status, + .primary_address = tnt4882_primary_address, + .secondary_address = tnt4882_secondary_address, + .serial_poll_response2 = tnt4882_serial_poll_response2, + .serial_poll_status = tnt4882_serial_poll_status, + .t1_delay = tnt4882_t1_delay, + .return_to_local = tnt4882_return_to_local, +}; + +static struct gpib_interface ni_nat4882_isa_accel_interface = { + .name = "ni_nat4882_isa_accel", + .attach = ni_nat4882_isa_attach, + .detach = ni_isa_detach, + .read = tnt4882_accel_read, + .write = tnt4882_accel_write, + .command = tnt4882_command_unaccel, + .take_control = tnt4882_take_control, + .go_to_standby = tnt4882_go_to_standby, + .request_system_control = tnt4882_request_system_control, + .interface_clear = tnt4882_interface_clear, + .remote_enable = tnt4882_remote_enable, + .enable_eos = tnt4882_enable_eos, + .disable_eos = tnt4882_disable_eos, + .parallel_poll = tnt4882_parallel_poll, + .parallel_poll_configure = tnt4882_parallel_poll_configure, + .parallel_poll_response = tnt4882_parallel_poll_response, + .local_parallel_poll_mode = NULL, // XXX + .line_status = tnt4882_line_status, + .update_status = tnt4882_update_status, + .primary_address = tnt4882_primary_address, + .secondary_address = tnt4882_secondary_address, + .serial_poll_response2 = tnt4882_serial_poll_response2, + .serial_poll_status = tnt4882_serial_poll_status, + .t1_delay = tnt4882_t1_delay, + .return_to_local = tnt4882_return_to_local, +}; + +static struct gpib_interface ni_nec_isa_accel_interface = { + .name = "ni_nec_isa_accel", + .attach = ni_nec_isa_attach, + .detach = ni_isa_detach, + .read = tnt4882_accel_read, + .write = tnt4882_accel_write, + .command = tnt4882_command_unaccel, + .take_control = tnt4882_take_control, + .go_to_standby = tnt4882_go_to_standby, + .request_system_control = tnt4882_request_system_control, + .interface_clear = tnt4882_interface_clear, + .remote_enable = tnt4882_remote_enable, + .enable_eos = tnt4882_enable_eos, + .disable_eos = tnt4882_disable_eos, + .parallel_poll = tnt4882_parallel_poll, + .parallel_poll_configure = tnt4882_parallel_poll_configure, + .parallel_poll_response = tnt4882_parallel_poll_response, + .local_parallel_poll_mode = NULL, // XXX + .line_status = NULL, + .update_status = tnt4882_update_status, + .primary_address = tnt4882_primary_address, + .secondary_address = tnt4882_secondary_address, + .serial_poll_response = tnt4882_serial_poll_response, + .serial_poll_status = tnt4882_serial_poll_status, + .t1_delay = tnt4882_t1_delay, + .return_to_local = tnt4882_return_to_local, +}; + +static const struct pci_device_id tnt4882_pci_table[] = { + {PCI_DEVICE(PCI_VENDOR_ID_NATINST, PCI_DEVICE_ID_NI_GPIB)}, + {PCI_DEVICE(PCI_VENDOR_ID_NATINST, PCI_DEVICE_ID_NI_GPIB_PLUS)}, + {PCI_DEVICE(PCI_VENDOR_ID_NATINST, PCI_DEVICE_ID_NI_GPIB_PLUS2)}, + {PCI_DEVICE(PCI_VENDOR_ID_NATINST, PCI_DEVICE_ID_NI_PXIGPIB)}, + {PCI_DEVICE(PCI_VENDOR_ID_NATINST, PCI_DEVICE_ID_NI_PMCGPIB)}, + {PCI_DEVICE(PCI_VENDOR_ID_NATINST, PCI_DEVICE_ID_NI_PCIEGPIB)}, + {PCI_DEVICE(PCI_VENDOR_ID_NATINST, PCI_DEVICE_ID_NI_PCIE2GPIB)}, + // support for Measurement Computing PCI-488 + {PCI_DEVICE(PCI_VENDOR_ID_NATINST, PCI_DEVICE_ID_MC_PCI488)}, + {PCI_DEVICE(PCI_VENDOR_ID_NATINST, PCI_DEVICE_ID_CEC_NI_GPIB)}, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, tnt4882_pci_table); + +static struct pci_driver tnt4882_pci_driver = { + .name = DRV_NAME, + .id_table = tnt4882_pci_table, + .probe = &tnt4882_pci_probe +}; + +#if 0 +/* unused, will be needed when the driver is turned into a pnp_driver */ +static const struct pnp_device_id tnt4882_pnp_table[] = { + {.id = "NICC601"}, + {.id = ""} +}; +MODULE_DEVICE_TABLE(pnp, tnt4882_pnp_table); +#endif + +#ifdef CONFIG_GPIB_PCMCIA +static struct gpib_interface ni_pcmcia_interface; +static struct gpib_interface ni_pcmcia_accel_interface; +static int __init init_ni_gpib_cs(void); +static void __exit exit_ni_gpib_cs(void); +#endif + +static int __init tnt4882_init_module(void) +{ + int result; + + result = pci_register_driver(&tnt4882_pci_driver); + if (result) { + pr_err("pci_register_driver failed: error = %d\n", result); + return result; + } + + result = gpib_register_driver(&ni_isa_interface, THIS_MODULE); + if (result) { + pr_err("gpib_register_driver failed: error = %d\n", result); + goto err_isa; + } + + result = gpib_register_driver(&ni_isa_accel_interface, THIS_MODULE); + if (result) { + pr_err("gpib_register_driver failed: error = %d\n", result); + goto err_isa_accel; + } + + result = gpib_register_driver(&ni_nat4882_isa_interface, THIS_MODULE); + if (result) { + pr_err("gpib_register_driver failed: error = %d\n", result); + goto err_nat4882_isa; + } + + result = gpib_register_driver(&ni_nat4882_isa_accel_interface, THIS_MODULE); + if (result) { + pr_err("gpib_register_driver failed: error = %d\n", result); + goto err_nat4882_isa_accel; + } + + result = gpib_register_driver(&ni_nec_isa_interface, THIS_MODULE); + if (result) { + pr_err("gpib_register_driver failed: error = %d\n", result); + goto err_nec_isa; + } + + result = gpib_register_driver(&ni_nec_isa_accel_interface, THIS_MODULE); + if (result) { + pr_err("gpib_register_driver failed: error = %d\n", result); + goto err_nec_isa_accel; + } + + result = gpib_register_driver(&ni_pci_interface, THIS_MODULE); + if (result) { + pr_err("gpib_register_driver failed: error = %d\n", result); + goto err_pci; + } + + result = gpib_register_driver(&ni_pci_accel_interface, THIS_MODULE); + if (result) { + pr_err("gpib_register_driver failed: error = %d\n", result); + goto err_pci_accel; + } + +#ifdef CONFIG_GPIB_PCMCIA + result = gpib_register_driver(&ni_pcmcia_interface, THIS_MODULE); + if (result) { + pr_err("gpib_register_driver failed: error = %d\n", result); + goto err_pcmcia; + } + + result = gpib_register_driver(&ni_pcmcia_accel_interface, THIS_MODULE); + if (result) { + pr_err("gpib_register_driver failed: error = %d\n", result); + goto err_pcmcia_accel; + } + + result = init_ni_gpib_cs(); + if (result) { + pr_err("pcmcia_register_driver failed: error = %d\n", result); + goto err_pcmcia_driver; + } +#endif + + mite_init(); + + return 0; + +#ifdef CONFIG_GPIB_PCMCIA +err_pcmcia_driver: + gpib_unregister_driver(&ni_pcmcia_accel_interface); +err_pcmcia_accel: + gpib_unregister_driver(&ni_pcmcia_interface); +err_pcmcia: +#endif + gpib_unregister_driver(&ni_pci_accel_interface); +err_pci_accel: + gpib_unregister_driver(&ni_pci_interface); +err_pci: + gpib_unregister_driver(&ni_nec_isa_accel_interface); +err_nec_isa_accel: + gpib_unregister_driver(&ni_nec_isa_interface); +err_nec_isa: + gpib_unregister_driver(&ni_nat4882_isa_accel_interface); +err_nat4882_isa_accel: + gpib_unregister_driver(&ni_nat4882_isa_interface); +err_nat4882_isa: + gpib_unregister_driver(&ni_isa_accel_interface); +err_isa_accel: + gpib_unregister_driver(&ni_isa_interface); +err_isa: + pci_unregister_driver(&tnt4882_pci_driver); + + return result; +} + +static void __exit tnt4882_exit_module(void) +{ + gpib_unregister_driver(&ni_isa_interface); + gpib_unregister_driver(&ni_isa_accel_interface); + gpib_unregister_driver(&ni_nat4882_isa_interface); + gpib_unregister_driver(&ni_nat4882_isa_accel_interface); + gpib_unregister_driver(&ni_nec_isa_interface); + gpib_unregister_driver(&ni_nec_isa_accel_interface); + gpib_unregister_driver(&ni_pci_interface); + gpib_unregister_driver(&ni_pci_accel_interface); +#ifdef CONFIG_GPIB_PCMCIA + gpib_unregister_driver(&ni_pcmcia_interface); + gpib_unregister_driver(&ni_pcmcia_accel_interface); + exit_ni_gpib_cs(); +#endif + + mite_cleanup(); + + pci_unregister_driver(&tnt4882_pci_driver); +} + +#ifdef CONFIG_GPIB_PCMCIA + +#include +#include +#include +#include +#include + +#include +#include +#include + +static int ni_gpib_config(struct pcmcia_device *link); +static void ni_gpib_release(struct pcmcia_device *link); +static void ni_pcmcia_detach(struct gpib_board *board); + +/* + * A linked list of "instances" of the dummy device. Each actual + * PCMCIA card corresponds to one device instance, and is described + * by one dev_link_t structure (defined in ds.h). + * + * You may not want to use a linked list for this -- for example, the + * memory card driver uses an array of dev_link_t pointers, where minor + * device numbers are used to derive the corresponding array index. + * + * I think this dev_list is obsolete but the pointer is needed to keep + * the module instance for the ni_pcmcia_attach function. + */ + +static struct pcmcia_device *curr_dev; + +struct local_info_t { + struct pcmcia_device *p_dev; + struct gpib_board *dev; + int stop; + struct bus_operations *bus; +}; + +/* + * ni_gpib_probe() creates an "instance" of the driver, allocating + * local data structures for one device. The device is registered + * with Card Services. + */ + +static int ni_gpib_probe(struct pcmcia_device *link) +{ + struct local_info_t *info; + //struct struct gpib_board *dev; + + /* Allocate space for private device-specific data */ + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->p_dev = link; + link->priv = info; + + /* + * General socket configuration defaults can go here. In this + * client, we assume very little, and rely on the CIS for almost + * everything. In most clients, many details (i.e., number, sizes, + * and attributes of IO windows) are fixed by the nature of the + * device, and can be hard-wired here. + */ + link->config_flags = CONF_ENABLE_IRQ | CONF_AUTO_SET_IO; + + /* Register with Card Services */ + curr_dev = link; + return ni_gpib_config(link); +} + +/* + * This deletes a driver "instance". The device is de-registered + * with Card Services. If it has been released, all local data + * structures are freed. Otherwise, the structures will be freed + * when the device is released. + */ +static void ni_gpib_remove(struct pcmcia_device *link) +{ + struct local_info_t *info = link->priv; + //struct struct gpib_board *dev = info->dev; + + if (info->dev) + ni_pcmcia_detach(info->dev); + ni_gpib_release(link); + + //free_netdev(dev); + kfree(info); +} + +static int ni_gpib_config_iteration(struct pcmcia_device *link, void *priv_data) +{ + int retval; + + retval = pcmcia_request_io(link); + if (retval != 0) + return retval; + + return 0; +} + +/* + * ni_gpib_config() is scheduled to run after a CARD_INSERTION event + * is received, to configure the PCMCIA socket, and to make the + * device available to the system. + */ +static int ni_gpib_config(struct pcmcia_device *link) +{ + //struct local_info_t *info = link->priv; + //struct gpib_board *dev = info->dev; + int last_ret; + + last_ret = pcmcia_loop_config(link, &ni_gpib_config_iteration, NULL); + if (last_ret) { + dev_warn(&link->dev, "no configuration found\n"); + ni_gpib_release(link); + return last_ret; + } + + last_ret = pcmcia_enable_device(link); + if (last_ret) { + ni_gpib_release(link); + return last_ret; + } + return 0; +} /* ni_gpib_config */ + +/* + * After a card is removed, ni_gpib_release() will unregister the + * device, and release the PCMCIA configuration. If the device is + * still open, this will be postponed until it is closed. + */ +static void ni_gpib_release(struct pcmcia_device *link) +{ + pcmcia_disable_device(link); +} /* ni_gpib_release */ + +static int ni_gpib_suspend(struct pcmcia_device *link) +{ + //struct local_info_t *info = link->priv; + //struct struct gpib_board *dev = info->dev; + + if (link->open) + dev_warn(&link->dev, "Device still open\n"); + //netif_device_detach(dev); + + return 0; +} + +static int ni_gpib_resume(struct pcmcia_device *link) +{ + //struct local_info_t *info = link->priv; + //struct struct gpib_board *dev = info->dev; + + /*if (link->open) { + * ni_gpib_probe(dev); / really? + * //netif_device_attach(dev); + *} + */ + return ni_gpib_config(link); +} + +static struct pcmcia_device_id ni_pcmcia_ids[] = { + PCMCIA_DEVICE_MANF_CARD(0x010b, 0x4882), + PCMCIA_DEVICE_MANF_CARD(0x010b, 0x0c71), // NI PCMCIA-GPIB+ + PCMCIA_DEVICE_NULL +}; + +MODULE_DEVICE_TABLE(pcmcia, ni_pcmcia_ids); + +static struct pcmcia_driver ni_gpib_cs_driver = { + .name = "ni_gpib_cs", + .owner = THIS_MODULE, + .drv = { .name = "ni_gpib_cs", }, + .id_table = ni_pcmcia_ids, + .probe = ni_gpib_probe, + .remove = ni_gpib_remove, + .suspend = ni_gpib_suspend, + .resume = ni_gpib_resume, +}; + +static int __init init_ni_gpib_cs(void) +{ + return pcmcia_register_driver(&ni_gpib_cs_driver); +} + +static void __exit exit_ni_gpib_cs(void) +{ + pcmcia_unregister_driver(&ni_gpib_cs_driver); +} + +static const int pcmcia_gpib_iosize = 32; + +static int ni_pcmcia_attach(struct gpib_board *board, const struct gpib_board_config *config) +{ + struct local_info_t *info; + struct tnt4882_priv *tnt_priv; + struct nec7210_priv *nec_priv; + int isr_flags = IRQF_SHARED; + int retval; + + if (!curr_dev) + return -ENODEV; + + info = curr_dev->priv; + info->dev = board; + + board->status = 0; + + if (tnt4882_allocate_private(board)) + return -ENOMEM; + + tnt_priv = board->private_data; + nec_priv = &tnt_priv->nec7210_priv; + nec_priv->type = TNT4882; + nec_priv->read_byte = nec7210_locking_ioport_read_byte; + nec_priv->write_byte = nec7210_locking_ioport_write_byte; + nec_priv->offset = atgpib_reg_offset; + + if (!request_region(curr_dev->resource[0]->start, resource_size(curr_dev->resource[0]), + DRV_NAME)) + return -ENOMEM; + + nec_priv->mmiobase = ioport_map(curr_dev->resource[0]->start, + resource_size(curr_dev->resource[0])); + if (!nec_priv->mmiobase) + return -ENOMEM; + + // get irq + retval = request_irq(curr_dev->irq, tnt4882_interrupt, isr_flags, DRV_NAME, board); + if (retval) { + dev_err(board->gpib_dev, "failed to obtain PCMCIA irq %d\n", curr_dev->irq); + return retval; + } + tnt_priv->irq = curr_dev->irq; + + tnt4882_init(tnt_priv, board); + + return 0; +} + +static void ni_pcmcia_detach(struct gpib_board *board) +{ + struct tnt4882_priv *tnt_priv = board->private_data; + struct nec7210_priv *nec_priv; + + if (tnt_priv) { + nec_priv = &tnt_priv->nec7210_priv; + if (tnt_priv->irq) + free_irq(tnt_priv->irq, board); + if (nec_priv->mmiobase) + ioport_unmap(nec_priv->mmiobase); + if (nec_priv->iobase) { + tnt4882_board_reset(tnt_priv, board); + release_region(nec_priv->iobase, pcmcia_gpib_iosize); + } + } + tnt4882_free_private(board); +} + +static struct gpib_interface ni_pcmcia_interface = { + .name = "ni_pcmcia", + .attach = ni_pcmcia_attach, + .detach = ni_pcmcia_detach, + .read = tnt4882_accel_read, + .write = tnt4882_accel_write, + .command = tnt4882_command, + .take_control = tnt4882_take_control, + .go_to_standby = tnt4882_go_to_standby, + .request_system_control = tnt4882_request_system_control, + .interface_clear = tnt4882_interface_clear, + .remote_enable = tnt4882_remote_enable, + .enable_eos = tnt4882_enable_eos, + .disable_eos = tnt4882_disable_eos, + .parallel_poll = tnt4882_parallel_poll, + .parallel_poll_configure = tnt4882_parallel_poll_configure, + .parallel_poll_response = tnt4882_parallel_poll_response, + .local_parallel_poll_mode = NULL, // XXX + .line_status = tnt4882_line_status, + .update_status = tnt4882_update_status, + .primary_address = tnt4882_primary_address, + .secondary_address = tnt4882_secondary_address, + .serial_poll_response = tnt4882_serial_poll_response, + .serial_poll_status = tnt4882_serial_poll_status, + .t1_delay = tnt4882_t1_delay, + .return_to_local = tnt4882_return_to_local, +}; + +static struct gpib_interface ni_pcmcia_accel_interface = { + .name = "ni_pcmcia_accel", + .attach = ni_pcmcia_attach, + .detach = ni_pcmcia_detach, + .read = tnt4882_accel_read, + .write = tnt4882_accel_write, + .command = tnt4882_command, + .take_control = tnt4882_take_control, + .go_to_standby = tnt4882_go_to_standby, + .request_system_control = tnt4882_request_system_control, + .interface_clear = tnt4882_interface_clear, + .remote_enable = tnt4882_remote_enable, + .enable_eos = tnt4882_enable_eos, + .disable_eos = tnt4882_disable_eos, + .parallel_poll = tnt4882_parallel_poll, + .parallel_poll_configure = tnt4882_parallel_poll_configure, + .parallel_poll_response = tnt4882_parallel_poll_response, + .local_parallel_poll_mode = NULL, // XXX + .line_status = tnt4882_line_status, + .update_status = tnt4882_update_status, + .primary_address = tnt4882_primary_address, + .secondary_address = tnt4882_secondary_address, + .serial_poll_response = tnt4882_serial_poll_response, + .serial_poll_status = tnt4882_serial_poll_status, + .t1_delay = tnt4882_t1_delay, + .return_to_local = tnt4882_return_to_local, +}; + +#endif // CONFIG_GPIB_PCMCIA + +module_init(tnt4882_init_module); +module_exit(tnt4882_exit_module); diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig index 075e775d3868..2f92cd698bef 100644 --- a/drivers/staging/Kconfig +++ b/drivers/staging/Kconfig @@ -48,6 +48,4 @@ source "drivers/staging/axis-fifo/Kconfig" source "drivers/staging/vme_user/Kconfig" -source "drivers/staging/gpib/Kconfig" - endif # STAGING diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile index e681e403509c..f5b8876aa536 100644 --- a/drivers/staging/Makefile +++ b/drivers/staging/Makefile @@ -13,4 +13,3 @@ obj-$(CONFIG_MOST) += most/ obj-$(CONFIG_GREYBUS) += greybus/ obj-$(CONFIG_BCM2835_VCHIQ) += vc04_services/ obj-$(CONFIG_XIL_AXIS_FIFO) += axis-fifo/ -obj-$(CONFIG_GPIB) += gpib/ diff --git a/drivers/staging/gpib/Kconfig b/drivers/staging/gpib/Kconfig deleted file mode 100644 index aa01538d5beb..000000000000 --- a/drivers/staging/gpib/Kconfig +++ /dev/null @@ -1,255 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 -menuconfig GPIB - tristate "Linux GPIB drivers" - help - Enable support for GPIB cards and dongles for Linux. GPIB - is the General Purpose Interface Bus which conforms to the - IEEE488 standard. - - This set of drivers can be used with the corresponding user - space library that can be found on Sourceforge under linux-gpib. - Select the drivers for your hardware from the list. - -if GPIB - -config GPIB_COMMON - tristate "GPIB core" - help - - Core common driver for all GPIB drivers. It provides the - interface for the userland library - - To compile this driver as a module, choose M here: the module will be - called gpib_common - -config GPIB_AGILENT_82350B - tristate "Agilent 8235xx PCI(e) adapters" - depends on PCI - select GPIB_COMMON - select GPIB_TMS9914 - help - Enable support for HP/Agilent/Keysight boards - 82350A - 82350B - 82351A - - To compile this driver as a module, choose M here: the module will be - called agilent_82350b. - -config GPIB_AGILENT_82357A - tristate "Agilent 82357a/b USB dongles" - select GPIB_COMMON - depends on USB - help - Enable support for Agilent/Keysight 82357x USB dongles. - - To compile this driver as a module, choose M here: the module will be - called agilent_82357a. - -config GPIB_CEC_PCI - tristate "CEC PCI board" - depends on PCI - depends on HAS_IOPORT - select GPIB_COMMON - select GPIB_NEC7210 - help - Enable support for Capital Equipment Corporation PCI-488 - and Keithly KPCI-488 boards. - - To compile this driver as a module, choose M here: the module will be - called cec_gpib. - -config GPIB_NI_PCI_ISA - tristate "NI PCI/ISA compatible boards" - depends on ISA_BUS || PCI || PCMCIA - depends on HAS_IOPORT - depends on PCMCIA || !PCMCIA - depends on HAS_IOPORT_MAP - select GPIB_COMMON - select GPIB_NEC7210 - help - Enable support for National Instruments boards based - on TNT4882 chips: - AT-GPIB (with NAT4882 chip) - AT-GPIB (with NEC7210 chip) - AT-GPIB/TNT - PCI-GPIB - PCIe-GPIB - PCI-GPIB+ - PCM-GPIB - PXI-GPIB - PCMCIA-GPIB - and Capital Equipment Corporation CEC-488 board. - - To compile this driver as a module, choose M here: the module will be - called tnt4882. - -config GPIB_CB7210 - tristate "Measurement Computing compatible boards" - depends on HAS_IOPORT - depends on ISA_BUS || PCI || PCMCIA - depends on PCMCIA || !PCMCIA - select GPIB_COMMON - select GPIB_NEC7210 - help - Enable support for Measurement Computing (Computer Boards): - CPCI_GPIB, ISA-GPIB, ISA-GPIB/LC, PCI-GPIB/1M, PCI-GPIB/300K and - PCMCIA-GPIB - Quancom PCIGPIB-1 with MC cb7210 chip - - To compile this driver as a module, choose M here: the module will be - -config GPIB_NI_USB - tristate "NI USB dongles" - select GPIB_COMMON - depends on USB - help - Enable support for National Instruments - GPIB-USB-B - GPIB-USB-HS - GPIB-USB-HS+ - Keithly - KUSB-488 - KUSB-488A - Measurement Computing (Computer Boards) - USB-488 - - To compile this driver as a module, choose M here: the module will be - called ni_usb. - -config GPIB_FLUKE - tristate "Fluke" - depends on OF - select GPIB_COMMON - select GPIB_NEC7210 - help - GPIB driver for Fluke based cda devices. - - To compile this driver as a module, choose M here: the module will be - called fluke_gpib - -config GPIB_FMH - tristate "FMH FPGA based devices" - select GPIB_COMMON - select GPIB_NEC7210 - depends on !PPC - depends on OF && PCI - help - GPIB driver for fmhess FPGA based devices - - To compile this driver as a module, choose M here: the module will be - called fmh_gpib - -config GPIB_GPIO - tristate "RPi GPIO bitbang" - depends on ARCH_BCM2835 || COMPILE_TEST - select GPIB_COMMON - help - GPIB bitbang driver Raspberry Pi GPIO adapters - - To compile this driver as a module, choose M here: the module will be - called gpib_bitbang - -config GPIB_HP82335 - tristate "HP82335/HP27209" - depends on ISA_BUS - select GPIB_COMMON - select GPIB_TMS9914 - help - GPIB driver for HP82335 and HP27209 boards - - To compile this driver as a module, choose M here: the module will be - called hp82335 - - -config GPIB_HP82341 - tristate "HP82341x" - select GPIB_COMMON - select GPIB_TMS9914 - depends on ISA_BUS || EISA - help - GPIB driver for HP82341 A/B/C/D boards - - To compile this driver as a module, choose M here: the module will be - called hp82341 - -config GPIB_INES - tristate "INES" - depends on PCI || ISA_BUS || PCMCIA - depends on PCMCIA || !PCMCIA - depends on HAS_IOPORT - select GPIB_COMMON - select GPIB_NEC7210 - help - GPIB driver for Ines compatible boards - Ines - GPIB-HS-NT - GPIB for Compact PCI - GPIB for PCI - GPIB for PCMCIA - GPIB PC/104 - Hameg - HO80-2 - Quancom - PCIGPIB-1 based on Ines iGPIB 72010 chip - - To compile this driver as a module, choose M here: the module will be - called ines_gpib - called cb7210. - -config GPIB_PCMCIA - def_bool y - depends on PCMCIA && (GPIB_NI_PCI_ISA || GPIB_CB7210 || GPIB_INES) - help - Enable PCMCIA/CArdbus support for National Instruments, - measurement computing boards and Ines boards. - -config GPIB_LPVO - tristate "LPVO DIY USB GPIB" - select GPIB_COMMON - depends on USB - help - Enable support for LPVO Self-made usb-gpib adapter - - To compile this driver as a module, choose M here: the module will be - called lpvo_usb_gpib - -config GPIB_PC2 - tristate "PC2 PC2a" - depends on ISA_BUS - depends on HAS_IOPORT - select GPIB_COMMON - select GPIB_NEC7210 - help - Enable support for pc2 and pc2a compatible adapters - Capital Equipment Corporation PC-488 - CONTEC GP-IB(PC) - Hameg HO80 - Iotech GP488B - Keithly MBC-488 - Measurement Computing ISA-GPIB-PCA2 - National Instruments PCII, PCIIa and PCII/IIa - - To compile this driver as a module, choose M here: the module will be - called pc2_gpib - - -config GPIB_TMS9914 - tristate - select GPIB_COMMON - help - Enable support for TMS 9914 chip. - - To compile this driver as a module, choose M here: the module will be - called tms9914 - -config GPIB_NEC7210 - tristate - select GPIB_COMMON - help - Enable support for NEC 7210 compatible chips. - - To compile this driver as a module, choose M here: the module will be - called nec7210 - -endif # GPIB diff --git a/drivers/staging/gpib/Makefile b/drivers/staging/gpib/Makefile deleted file mode 100644 index d0e88f5c0844..000000000000 --- a/drivers/staging/gpib/Makefile +++ /dev/null @@ -1,20 +0,0 @@ - -subdir-ccflags-y += -I$(src)/include -I$(src)/uapi - -obj-$(CONFIG_GPIB_AGILENT_82350B) += agilent_82350b/ -obj-$(CONFIG_GPIB_AGILENT_82357A) += agilent_82357a/ -obj-$(CONFIG_GPIB_CB7210) += cb7210/ -obj-$(CONFIG_GPIB_CEC_PCI) += cec/ -obj-$(CONFIG_GPIB_COMMON) += common/ -obj-$(CONFIG_GPIB_FLUKE) += eastwood/ -obj-$(CONFIG_GPIB_FMH) += fmh_gpib/ -obj-$(CONFIG_GPIB_GPIO) += gpio/ -obj-$(CONFIG_GPIB_HP82335) += hp_82335/ -obj-$(CONFIG_GPIB_HP82341) += hp_82341/ -obj-$(CONFIG_GPIB_INES) += ines/ -obj-$(CONFIG_GPIB_LPVO) += lpvo_usb_gpib/ -obj-$(CONFIG_GPIB_NEC7210) += nec7210/ -obj-$(CONFIG_GPIB_NI_USB) += ni_usb/ -obj-$(CONFIG_GPIB_PC2) += pc2/ -obj-$(CONFIG_GPIB_TMS9914) += tms9914/ -obj-$(CONFIG_GPIB_NI_PCI_ISA) += tnt4882/ diff --git a/drivers/staging/gpib/TODO b/drivers/staging/gpib/TODO deleted file mode 100644 index ac07dd90b4ef..000000000000 --- a/drivers/staging/gpib/TODO +++ /dev/null @@ -1,10 +0,0 @@ -TODO: -- checkpatch.pl fixes - These checks should be ignored: - CHECK:ALLOC_SIZEOF_STRUCT: Prefer kmalloc(sizeof(*board->private_data)...) over kmalloc(sizeof(struct xxx_priv)...) - ./gpio/gpib_bitbang.c:50: ERROR:COMPLEX_MACRO: Macros with complex values should be enclosed in parenthese - This warning will be addressed later: WARNING:UNDOCUMENTED_DT_STRING: DT compatible string -- resolve XXX notes where possible -- fix FIXME notes -- clean-up commented-out code -- fix typos diff --git a/drivers/staging/gpib/agilent_82350b/Makefile b/drivers/staging/gpib/agilent_82350b/Makefile deleted file mode 100644 index f24e1e713a63..000000000000 --- a/drivers/staging/gpib/agilent_82350b/Makefile +++ /dev/null @@ -1,2 +0,0 @@ - -obj-$(CONFIG_GPIB_AGILENT_82350B) += agilent_82350b.o diff --git a/drivers/staging/gpib/agilent_82350b/agilent_82350b.c b/drivers/staging/gpib/agilent_82350b/agilent_82350b.c deleted file mode 100644 index 01a5bb43cd2d..000000000000 --- a/drivers/staging/gpib/agilent_82350b/agilent_82350b.c +++ /dev/null @@ -1,896 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 - -/*************************************************************************** - * copyright : (C) 2002, 2004 by Frank Mori Hess * - ***************************************************************************/ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -#define dev_fmt pr_fmt -#define DRV_NAME KBUILD_MODNAME - -#include "agilent_82350b.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("GPIB driver for Agilent 82350b"); - -static int read_transfer_counter(struct agilent_82350b_priv *a_priv); -static unsigned short read_and_clear_event_status(struct gpib_board *board); -static void set_transfer_counter(struct agilent_82350b_priv *a_priv, int count); -static int agilent_82350b_write(struct gpib_board *board, u8 *buffer, - size_t length, int send_eoi, size_t *bytes_written); - -static int agilent_82350b_accel_read(struct gpib_board *board, u8 *buffer, - size_t length, int *end, size_t *bytes_read) - -{ - struct agilent_82350b_priv *a_priv = board->private_data; - struct tms9914_priv *tms_priv = &a_priv->tms9914_priv; - int retval = 0; - unsigned short event_status; - int i, num_fifo_bytes; - /* hardware doesn't support checking for end-of-string character when using fifo */ - if (tms_priv->eos_flags & REOS) - return tms9914_read(board, tms_priv, buffer, length, end, bytes_read); - - clear_bit(DEV_CLEAR_BN, &tms_priv->state); - - read_and_clear_event_status(board); - *end = 0; - *bytes_read = 0; - if (length == 0) - return 0; - /* disable fifo for the moment */ - writeb(DIRECTION_GPIB_TO_HOST, a_priv->gpib_base + SRAM_ACCESS_CONTROL_REG); - /* handle corner case of board not in holdoff and one byte might slip in early */ - if (tms_priv->holdoff_active == 0 && length > 1) { - size_t num_bytes; - - retval = tms9914_read(board, tms_priv, buffer, 1, end, &num_bytes); - *bytes_read += num_bytes; - if (retval < 0 || *end) - return retval; - ++buffer; - --length; - } - tms9914_set_holdoff_mode(tms_priv, TMS9914_HOLDOFF_EOI); - tms9914_release_holdoff(tms_priv); - i = 0; - num_fifo_bytes = length - 1; - /* disable BI interrupts */ - write_byte(tms_priv, tms_priv->imr0_bits & ~HR_BIIE, IMR0); - while (i < num_fifo_bytes && *end == 0) { - int block_size; - int j; - int count; - - block_size = min(num_fifo_bytes - i, agilent_82350b_fifo_size); - set_transfer_counter(a_priv, block_size); - writeb(ENABLE_TI_TO_SRAM | DIRECTION_GPIB_TO_HOST, - a_priv->gpib_base + SRAM_ACCESS_CONTROL_REG); - if (agilent_82350b_fifo_is_halted(a_priv)) - writeb(RESTART_STREAM_BIT, a_priv->gpib_base + STREAM_STATUS_REG); - - clear_bit(READ_READY_BN, &tms_priv->state); - - retval = wait_event_interruptible(board->wait, - ((event_status = - read_and_clear_event_status(board)) & - (TERM_COUNT_STATUS_BIT | - BUFFER_END_STATUS_BIT)) || - test_bit(DEV_CLEAR_BN, &tms_priv->state) || - test_bit(TIMO_NUM, &board->status)); - if (retval) { - retval = -ERESTARTSYS; - break; - } - count = block_size - read_transfer_counter(a_priv); - for (j = 0; j < count && i < num_fifo_bytes; ++j) - buffer[i++] = readb(a_priv->sram_base + j); - if (event_status & BUFFER_END_STATUS_BIT) { - clear_bit(RECEIVED_END_BN, &tms_priv->state); - - tms_priv->holdoff_active = 1; - *end = 1; - } - if (test_bit(TIMO_NUM, &board->status)) { - retval = -ETIMEDOUT; - break; - } - if (test_bit(DEV_CLEAR_BN, &tms_priv->state)) { - retval = -EINTR; - break; - } - } - /* re-enable BI interrupts */ - write_byte(tms_priv, tms_priv->imr0_bits, IMR0); - *bytes_read += i; - buffer += i; - length -= i; - writeb(DIRECTION_GPIB_TO_HOST, a_priv->gpib_base + SRAM_ACCESS_CONTROL_REG); - if (retval < 0) - return retval; - /* read last bytes if we havn't received an END yet */ - if (*end == 0) { - size_t num_bytes; - /* try to make sure we holdoff after last byte read */ - retval = tms9914_read(board, tms_priv, buffer, length, end, &num_bytes); - *bytes_read += num_bytes; - if (retval < 0) - return retval; - } - return 0; -} - -static int translate_wait_return_value(struct gpib_board *board, int retval) - -{ - struct agilent_82350b_priv *a_priv = board->private_data; - struct tms9914_priv *tms_priv = &a_priv->tms9914_priv; - - if (retval) - return -ERESTARTSYS; - if (test_bit(TIMO_NUM, &board->status)) - return -ETIMEDOUT; - if (test_bit(DEV_CLEAR_BN, &tms_priv->state)) - return -EINTR; - return 0; -} - -static int agilent_82350b_accel_write(struct gpib_board *board, u8 *buffer, - size_t length, int send_eoi, - size_t *bytes_written) -{ - struct agilent_82350b_priv *a_priv = board->private_data; - struct tms9914_priv *tms_priv = &a_priv->tms9914_priv; - int i, j; - unsigned short event_status; - int retval = 0; - int fifotransferlength = length; - int block_size = 0; - size_t num_bytes; - - *bytes_written = 0; - if (send_eoi) - --fifotransferlength; - - clear_bit(DEV_CLEAR_BN, &tms_priv->state); - - writeb(0, a_priv->gpib_base + SRAM_ACCESS_CONTROL_REG); - - event_status = read_and_clear_event_status(board); - -#ifdef EXPERIMENTAL - /* wait for previous BO to complete if any */ - retval = wait_event_interruptible(board->wait, - test_bit(DEV_CLEAR_BN, &tms_priv->state) || - test_bit(WRITE_READY_BN, &tms_priv->state) || - test_bit(TIMO_NUM, &board->status)); - retval = translate_wait_return_value(board, retval); - - if (retval) - return retval; -#endif - - if (fifotransferlength > 0) { - retval = agilent_82350b_write(board, buffer, 1, 0, &num_bytes); - *bytes_written += num_bytes; - if (retval < 0) - return retval; - } - - write_byte(tms_priv, tms_priv->imr0_bits & ~HR_BOIE, IMR0); - for (i = 1; i < fifotransferlength;) { - clear_bit(WRITE_READY_BN, &tms_priv->state); - - block_size = min(fifotransferlength - i, agilent_82350b_fifo_size); - set_transfer_counter(a_priv, block_size); - for (j = 0; j < block_size; ++j, ++i) { - /* load data into board's sram */ - writeb(buffer[i], a_priv->sram_base + j); - } - writeb(ENABLE_TI_TO_SRAM, a_priv->gpib_base + SRAM_ACCESS_CONTROL_REG); - - if (agilent_82350b_fifo_is_halted(a_priv)) - writeb(RESTART_STREAM_BIT, a_priv->gpib_base + STREAM_STATUS_REG); - - retval = wait_event_interruptible(board->wait, - ((event_status = - read_and_clear_event_status(board)) & - TERM_COUNT_STATUS_BIT) || - test_bit(DEV_CLEAR_BN, &tms_priv->state) || - test_bit(TIMO_NUM, &board->status)); - writeb(0, a_priv->gpib_base + SRAM_ACCESS_CONTROL_REG); - num_bytes = block_size - read_transfer_counter(a_priv); - - *bytes_written += num_bytes; - retval = translate_wait_return_value(board, retval); - if (retval) - break; - } - write_byte(tms_priv, tms_priv->imr0_bits, IMR0); - if (retval < 0) - return retval; - - if (send_eoi) { - retval = agilent_82350b_write(board, buffer + fifotransferlength, 1, send_eoi, - &num_bytes); - *bytes_written += num_bytes; - if (retval < 0) - return retval; - } - return 0; -} - -static unsigned short read_and_clear_event_status(struct gpib_board *board) -{ - struct agilent_82350b_priv *a_priv = board->private_data; - unsigned long flags; - unsigned short status; - - spin_lock_irqsave(&board->spinlock, flags); - status = a_priv->event_status_bits; - a_priv->event_status_bits = 0; - spin_unlock_irqrestore(&board->spinlock, flags); - return status; -} - -static irqreturn_t agilent_82350b_interrupt(int irq, void *arg) - -{ - int tms9914_status1 = 0, tms9914_status2 = 0; - int event_status; - struct gpib_board *board = arg; - struct agilent_82350b_priv *a_priv = board->private_data; - unsigned long flags; - irqreturn_t retval = IRQ_NONE; - - spin_lock_irqsave(&board->spinlock, flags); - event_status = readb(a_priv->gpib_base + EVENT_STATUS_REG); - if (event_status & IRQ_STATUS_BIT) - retval = IRQ_HANDLED; - - if (event_status & TMS9914_IRQ_STATUS_BIT) { - tms9914_status1 = read_byte(&a_priv->tms9914_priv, ISR0); - tms9914_status2 = read_byte(&a_priv->tms9914_priv, ISR1); - tms9914_interrupt_have_status(board, &a_priv->tms9914_priv, tms9914_status1, - tms9914_status2); - } - /* write-clear status bits */ - if (event_status & (BUFFER_END_STATUS_BIT | TERM_COUNT_STATUS_BIT)) { - writeb(event_status & (BUFFER_END_STATUS_BIT | TERM_COUNT_STATUS_BIT), - a_priv->gpib_base + EVENT_STATUS_REG); - a_priv->event_status_bits |= event_status; - wake_up_interruptible(&board->wait); - } - spin_unlock_irqrestore(&board->spinlock, flags); - return retval; -} - -static void agilent_82350b_detach(struct gpib_board *board); - -static int read_transfer_counter(struct agilent_82350b_priv *a_priv) -{ - int lo, mid, value; - - lo = readb(a_priv->gpib_base + XFER_COUNT_LO_REG); - mid = readb(a_priv->gpib_base + XFER_COUNT_MID_REG); - value = (lo & 0xff) | ((mid << 8) & 0x7f00); - value = ~(value - 1) & 0x7fff; - return value; -} - -static void set_transfer_counter(struct agilent_82350b_priv *a_priv, int count) -{ - int complement = -count; - - writeb(complement & 0xff, a_priv->gpib_base + XFER_COUNT_LO_REG); - writeb((complement >> 8) & 0xff, a_priv->gpib_base + XFER_COUNT_MID_REG); - /* I don't think the hi count reg is even used, but oh well */ - writeb((complement >> 16) & 0xf, a_priv->gpib_base + XFER_COUNT_HI_REG); -} - -/* wrappers for interface functions */ -static int agilent_82350b_read(struct gpib_board *board, u8 *buffer, - size_t length, int *end, size_t *bytes_read) -{ - struct agilent_82350b_priv *priv = board->private_data; - - return tms9914_read(board, &priv->tms9914_priv, buffer, length, end, bytes_read); -} - -static int agilent_82350b_write(struct gpib_board *board, u8 *buffer, - size_t length, int send_eoi, size_t *bytes_written) - -{ - struct agilent_82350b_priv *priv = board->private_data; - - return tms9914_write(board, &priv->tms9914_priv, buffer, length, send_eoi, bytes_written); -} - -static int agilent_82350b_command(struct gpib_board *board, u8 *buffer, - size_t length, size_t *bytes_written) - -{ - struct agilent_82350b_priv *priv = board->private_data; - - return tms9914_command(board, &priv->tms9914_priv, buffer, length, bytes_written); -} - -static int agilent_82350b_take_control(struct gpib_board *board, int synchronous) - -{ - struct agilent_82350b_priv *priv = board->private_data; - - return tms9914_take_control_workaround(board, &priv->tms9914_priv, synchronous); -} - -static int agilent_82350b_go_to_standby(struct gpib_board *board) - -{ - struct agilent_82350b_priv *priv = board->private_data; - - return tms9914_go_to_standby(board, &priv->tms9914_priv); -} - -static int agilent_82350b_request_system_control(struct gpib_board *board, int request_control) -{ - struct agilent_82350b_priv *a_priv = board->private_data; - - if (request_control) { - a_priv->card_mode_bits |= CM_SYSTEM_CONTROLLER_BIT; - if (a_priv->model != MODEL_82350A) - writeb(IC_SYSTEM_CONTROLLER_BIT, a_priv->gpib_base + INTERNAL_CONFIG_REG); - } else { - a_priv->card_mode_bits &= ~CM_SYSTEM_CONTROLLER_BIT; - if (a_priv->model != MODEL_82350A) - writeb(0, a_priv->gpib_base + INTERNAL_CONFIG_REG); - } - writeb(a_priv->card_mode_bits, a_priv->gpib_base + CARD_MODE_REG); - return tms9914_request_system_control(board, &a_priv->tms9914_priv, request_control); -} - -static void agilent_82350b_interface_clear(struct gpib_board *board, int assert) - -{ - struct agilent_82350b_priv *priv = board->private_data; - - tms9914_interface_clear(board, &priv->tms9914_priv, assert); -} - -static void agilent_82350b_remote_enable(struct gpib_board *board, int enable) -{ - struct agilent_82350b_priv *priv = board->private_data; - - tms9914_remote_enable(board, &priv->tms9914_priv, enable); -} - -static int agilent_82350b_enable_eos(struct gpib_board *board, u8 eos_byte, - int compare_8_bits) -{ - struct agilent_82350b_priv *priv = board->private_data; - - return tms9914_enable_eos(board, &priv->tms9914_priv, eos_byte, compare_8_bits); -} - -static void agilent_82350b_disable_eos(struct gpib_board *board) -{ - struct agilent_82350b_priv *priv = board->private_data; - - tms9914_disable_eos(board, &priv->tms9914_priv); -} - -static unsigned int agilent_82350b_update_status(struct gpib_board *board, - unsigned int clear_mask) -{ - struct agilent_82350b_priv *priv = board->private_data; - - return tms9914_update_status(board, &priv->tms9914_priv, clear_mask); -} - -static int agilent_82350b_primary_address(struct gpib_board *board, - unsigned int address) -{ - struct agilent_82350b_priv *priv = board->private_data; - - return tms9914_primary_address(board, &priv->tms9914_priv, address); -} - -static int agilent_82350b_secondary_address(struct gpib_board *board, - unsigned int address, int enable) -{ - struct agilent_82350b_priv *priv = board->private_data; - - return tms9914_secondary_address(board, &priv->tms9914_priv, address, enable); -} - -static int agilent_82350b_parallel_poll(struct gpib_board *board, u8 *result) -{ - struct agilent_82350b_priv *priv = board->private_data; - - return tms9914_parallel_poll(board, &priv->tms9914_priv, result); -} - -static void agilent_82350b_parallel_poll_configure(struct gpib_board *board, - u8 config) -{ - struct agilent_82350b_priv *priv = board->private_data; - - tms9914_parallel_poll_configure(board, &priv->tms9914_priv, config); -} - -static void agilent_82350b_parallel_poll_response(struct gpib_board *board, int ist) -{ - struct agilent_82350b_priv *priv = board->private_data; - - tms9914_parallel_poll_response(board, &priv->tms9914_priv, ist); -} - -static void agilent_82350b_serial_poll_response(struct gpib_board *board, u8 status) -{ - struct agilent_82350b_priv *priv = board->private_data; - - tms9914_serial_poll_response(board, &priv->tms9914_priv, status); -} - -static u8 agilent_82350b_serial_poll_status(struct gpib_board *board) -{ - struct agilent_82350b_priv *priv = board->private_data; - - return tms9914_serial_poll_status(board, &priv->tms9914_priv); -} - -static int agilent_82350b_line_status(const struct gpib_board *board) -{ - struct agilent_82350b_priv *priv = board->private_data; - - return tms9914_line_status(board, &priv->tms9914_priv); -} - -static int agilent_82350b_t1_delay(struct gpib_board *board, unsigned int nanosec) -{ - struct agilent_82350b_priv *a_priv = board->private_data; - static const int nanosec_per_clock = 30; - unsigned int value; - - tms9914_t1_delay(board, &a_priv->tms9914_priv, nanosec); - - value = (nanosec + nanosec_per_clock - 1) / nanosec_per_clock; - if (value > 0xff) - value = 0xff; - writeb(value, a_priv->gpib_base + T1_DELAY_REG); - return value * nanosec_per_clock; -} - -static void agilent_82350b_return_to_local(struct gpib_board *board) -{ - struct agilent_82350b_priv *priv = board->private_data; - - tms9914_return_to_local(board, &priv->tms9914_priv); -} - -static int agilent_82350b_allocate_private(struct gpib_board *board) -{ - board->private_data = kzalloc(sizeof(struct agilent_82350b_priv), GFP_KERNEL); - if (!board->private_data) - return -ENOMEM; - return 0; -} - -static void agilent_82350b_free_private(struct gpib_board *board) -{ - kfree(board->private_data); - board->private_data = NULL; -} - -static int init_82350a_hardware(struct gpib_board *board, - const struct gpib_board_config *config) -{ - struct agilent_82350b_priv *a_priv = board->private_data; - static const unsigned int firmware_length = 5302; - unsigned int borg_status; - static const unsigned int timeout = 1000; - int i, j; - const char *firmware_data = config->init_data; - const unsigned int plx_cntrl_static_bits = PLX9050_WAITO_NOT_USER0_SELECT_BIT | - PLX9050_USER0_OUTPUT_BIT | - PLX9050_LLOCK_NOT_USER1_SELECT_BIT | - PLX9050_USER1_OUTPUT_BIT | - PLX9050_USER2_OUTPUT_BIT | - PLX9050_USER3_OUTPUT_BIT | - PLX9050_PCI_READ_MODE_BIT | - PLX9050_PCI_WRITE_MODE_BIT | - PLX9050_PCI_RETRY_DELAY_BITS(64) | - PLX9050_DIRECT_SLAVE_LOCK_ENABLE_BIT; - - /* load borg data */ - borg_status = readb(a_priv->borg_base); - if ((borg_status & BORG_DONE_BIT)) - return 0; - /* need to programme borg */ - if (!config->init_data || config->init_data_length != firmware_length) { - dev_err(board->gpib_dev, "the 82350A board requires firmware after powering on.\n"); - return -EIO; - } - dev_dbg(board->gpib_dev, "Loading firmware...\n"); - - /* tickle the borg */ - writel(plx_cntrl_static_bits | PLX9050_USER3_DATA_BIT, - a_priv->plx_base + PLX9050_CNTRL_REG); - usleep_range(1000, 2000); - writel(plx_cntrl_static_bits, a_priv->plx_base + PLX9050_CNTRL_REG); - usleep_range(1000, 2000); - writel(plx_cntrl_static_bits | PLX9050_USER3_DATA_BIT, - a_priv->plx_base + PLX9050_CNTRL_REG); - usleep_range(1000, 2000); - - for (i = 0; i < config->init_data_length; ++i) { - for (j = 0; j < timeout && (readb(a_priv->borg_base) & BORG_READY_BIT) == 0; ++j) { - if (need_resched()) - schedule(); - usleep_range(10, 20); - } - if (j == timeout) { - dev_err(board->gpib_dev, "timed out loading firmware.\n"); - return -ETIMEDOUT; - } - writeb(firmware_data[i], a_priv->gpib_base + CONFIG_DATA_REG); - } - for (j = 0; j < timeout && (readb(a_priv->borg_base) & BORG_DONE_BIT) == 0; ++j) { - if (need_resched()) - schedule(); - usleep_range(10, 20); - } - if (j == timeout) { - dev_err(board->gpib_dev, "timed out waiting for firmware load to complete.\n"); - return -ETIMEDOUT; - } - dev_dbg(board->gpib_dev, " ...done.\n"); - return 0; -} - -static int test_sram(struct gpib_board *board) - -{ - struct agilent_82350b_priv *a_priv = board->private_data; - unsigned int i; - const unsigned int sram_length = pci_resource_len(a_priv->pci_device, SRAM_82350A_REGION); - /* test SRAM */ - const unsigned int byte_mask = 0xff; - - for (i = 0; i < sram_length; ++i) { - writeb(i & byte_mask, a_priv->sram_base + i); - if (need_resched()) - schedule(); - } - for (i = 0; i < sram_length; ++i) { - unsigned int read_value = readb(a_priv->sram_base + i); - - if ((i & byte_mask) != read_value) { - dev_err(board->gpib_dev, "SRAM test failed at %d wanted %d got %d\n", - i, (i & byte_mask), read_value); - return -EIO; - } - if (need_resched()) - schedule(); - } - dev_dbg(board->gpib_dev, "SRAM test passed 0x%x bytes checked\n", sram_length); - return 0; -} - -static int agilent_82350b_generic_attach(struct gpib_board *board, - const struct gpib_board_config *config, - int use_fifos) - -{ - struct agilent_82350b_priv *a_priv; - struct tms9914_priv *tms_priv; - int retval; - - board->status = 0; - - if (agilent_82350b_allocate_private(board)) - return -ENOMEM; - a_priv = board->private_data; - a_priv->using_fifos = use_fifos; - tms_priv = &a_priv->tms9914_priv; - tms_priv->read_byte = tms9914_iomem_read_byte; - tms_priv->write_byte = tms9914_iomem_write_byte; - tms_priv->offset = 1; - - /* find board */ - a_priv->pci_device = gpib_pci_get_device(config, PCI_VENDOR_ID_AGILENT, - PCI_DEVICE_ID_82350B, NULL); - if (a_priv->pci_device) { - a_priv->model = MODEL_82350B; - dev_dbg(board->gpib_dev, "Agilent 82350B board found\n"); - - } else { - a_priv->pci_device = gpib_pci_get_device(config, PCI_VENDOR_ID_AGILENT, - PCI_DEVICE_ID_82351A, NULL); - if (a_priv->pci_device) { - a_priv->model = MODEL_82351A; - dev_dbg(board->gpib_dev, "Agilent 82351B board found\n"); - - } else { - a_priv->pci_device = gpib_pci_get_subsys(config, PCI_VENDOR_ID_PLX, - PCI_DEVICE_ID_PLX_9050, - PCI_VENDOR_ID_HP, - PCI_SUBDEVICE_ID_82350A, - a_priv->pci_device); - if (a_priv->pci_device) { - a_priv->model = MODEL_82350A; - dev_dbg(board->gpib_dev, "HP/Agilent 82350A board found\n"); - } else { - dev_err(board->gpib_dev, "no 82350/82351 board found\n"); - return -ENODEV; - } - } - } - if (pci_enable_device(a_priv->pci_device)) { - dev_err(board->gpib_dev, "error enabling pci device\n"); - return -EIO; - } - if (pci_request_regions(a_priv->pci_device, DRV_NAME)) - return -ENOMEM; - switch (a_priv->model) { - case MODEL_82350A: - a_priv->plx_base = ioremap(pci_resource_start(a_priv->pci_device, PLX_MEM_REGION), - pci_resource_len(a_priv->pci_device, PLX_MEM_REGION)); - dev_dbg(board->gpib_dev, "plx base address remapped to 0x%p\n", a_priv->plx_base); - a_priv->gpib_base = ioremap(pci_resource_start(a_priv->pci_device, - GPIB_82350A_REGION), - pci_resource_len(a_priv->pci_device, - GPIB_82350A_REGION)); - dev_dbg(board->gpib_dev, "chip base address remapped to 0x%p\n", a_priv->gpib_base); - tms_priv->mmiobase = a_priv->gpib_base + TMS9914_BASE_REG; - a_priv->sram_base = ioremap(pci_resource_start(a_priv->pci_device, - SRAM_82350A_REGION), - pci_resource_len(a_priv->pci_device, - SRAM_82350A_REGION)); - dev_dbg(board->gpib_dev, "sram base address remapped to 0x%p\n", a_priv->sram_base); - a_priv->borg_base = ioremap(pci_resource_start(a_priv->pci_device, - BORG_82350A_REGION), - pci_resource_len(a_priv->pci_device, - BORG_82350A_REGION)); - dev_dbg(board->gpib_dev, "borg base address remapped to 0x%p\n", a_priv->borg_base); - - retval = init_82350a_hardware(board, config); - if (retval < 0) - return retval; - break; - case MODEL_82350B: - case MODEL_82351A: - a_priv->gpib_base = ioremap(pci_resource_start(a_priv->pci_device, GPIB_REGION), - pci_resource_len(a_priv->pci_device, GPIB_REGION)); - dev_dbg(board->gpib_dev, "chip base address remapped to 0x%p\n", a_priv->gpib_base); - tms_priv->mmiobase = a_priv->gpib_base + TMS9914_BASE_REG; - a_priv->sram_base = ioremap(pci_resource_start(a_priv->pci_device, SRAM_REGION), - pci_resource_len(a_priv->pci_device, SRAM_REGION)); - dev_dbg(board->gpib_dev, "sram base address remapped to 0x%p\n", a_priv->sram_base); - a_priv->misc_base = ioremap(pci_resource_start(a_priv->pci_device, MISC_REGION), - pci_resource_len(a_priv->pci_device, MISC_REGION)); - dev_dbg(board->gpib_dev, "misc base address remapped to 0x%p\n", a_priv->misc_base); - break; - default: - dev_err(board->gpib_dev, "invalid board\n"); - return -ENODEV; - } - - retval = test_sram(board); - if (retval < 0) - return retval; - - if (request_irq(a_priv->pci_device->irq, agilent_82350b_interrupt, - IRQF_SHARED, DRV_NAME, board)) { - dev_err(board->gpib_dev, "failed to obtain irq %d\n", a_priv->pci_device->irq); - return -EIO; - } - a_priv->irq = a_priv->pci_device->irq; - dev_dbg(board->gpib_dev, " IRQ %d\n", a_priv->irq); - - writeb(0, a_priv->gpib_base + SRAM_ACCESS_CONTROL_REG); - a_priv->card_mode_bits = ENABLE_PCI_IRQ_BIT; - writeb(a_priv->card_mode_bits, a_priv->gpib_base + CARD_MODE_REG); - - if (a_priv->model == MODEL_82350A) { - /* enable PCI interrupts for 82350a */ - writel(PLX9050_LINTR1_EN_BIT | PLX9050_LINTR2_POLARITY_BIT | - PLX9050_PCI_INTR_EN_BIT, - a_priv->plx_base + PLX9050_INTCSR_REG); - } - - if (use_fifos) { - writeb(ENABLE_BUFFER_END_EVENTS_BIT | ENABLE_TERM_COUNT_EVENTS_BIT, - a_priv->gpib_base + EVENT_ENABLE_REG); - writeb(ENABLE_TERM_COUNT_INTERRUPT_BIT | ENABLE_BUFFER_END_INTERRUPT_BIT | - ENABLE_TMS9914_INTERRUPTS_BIT, a_priv->gpib_base + INTERRUPT_ENABLE_REG); - /* write-clear event status bits */ - writeb(BUFFER_END_STATUS_BIT | TERM_COUNT_STATUS_BIT, - a_priv->gpib_base + EVENT_STATUS_REG); - } else { - writeb(0, a_priv->gpib_base + EVENT_ENABLE_REG); - writeb(ENABLE_TMS9914_INTERRUPTS_BIT, - a_priv->gpib_base + INTERRUPT_ENABLE_REG); - } - board->t1_nano_sec = agilent_82350b_t1_delay(board, 2000); - tms9914_board_reset(tms_priv); - - tms9914_online(board, tms_priv); - - return 0; -} - -static int agilent_82350b_unaccel_attach(struct gpib_board *board, - const struct gpib_board_config *config) -{ - return agilent_82350b_generic_attach(board, config, 0); -} - -static int agilent_82350b_accel_attach(struct gpib_board *board, - const struct gpib_board_config *config) -{ - return agilent_82350b_generic_attach(board, config, 1); -} - -static void agilent_82350b_detach(struct gpib_board *board) -{ - struct agilent_82350b_priv *a_priv = board->private_data; - struct tms9914_priv *tms_priv; - - if (a_priv) { - if (a_priv->plx_base) /* disable interrupts */ - writel(0, a_priv->plx_base + PLX9050_INTCSR_REG); - - tms_priv = &a_priv->tms9914_priv; - if (a_priv->irq) - free_irq(a_priv->irq, board); - if (a_priv->gpib_base) { - tms9914_board_reset(tms_priv); - if (a_priv->misc_base) - iounmap(a_priv->misc_base); - if (a_priv->borg_base) - iounmap(a_priv->borg_base); - if (a_priv->sram_base) - iounmap(a_priv->sram_base); - if (a_priv->gpib_base) - iounmap(a_priv->gpib_base); - if (a_priv->plx_base) - iounmap(a_priv->plx_base); - pci_release_regions(a_priv->pci_device); - } - if (a_priv->pci_device) - pci_dev_put(a_priv->pci_device); - } - agilent_82350b_free_private(board); -} - -static struct gpib_interface agilent_82350b_unaccel_interface = { - .name = "agilent_82350b_unaccel", - .attach = agilent_82350b_unaccel_attach, - .detach = agilent_82350b_detach, - .read = agilent_82350b_read, - .write = agilent_82350b_write, - .command = agilent_82350b_command, - .request_system_control = agilent_82350b_request_system_control, - .take_control = agilent_82350b_take_control, - .go_to_standby = agilent_82350b_go_to_standby, - .interface_clear = agilent_82350b_interface_clear, - .remote_enable = agilent_82350b_remote_enable, - .enable_eos = agilent_82350b_enable_eos, - .disable_eos = agilent_82350b_disable_eos, - .parallel_poll = agilent_82350b_parallel_poll, - .parallel_poll_configure = agilent_82350b_parallel_poll_configure, - .parallel_poll_response = agilent_82350b_parallel_poll_response, - .local_parallel_poll_mode = NULL, /* XXX */ - .line_status = agilent_82350b_line_status, - .update_status = agilent_82350b_update_status, - .primary_address = agilent_82350b_primary_address, - .secondary_address = agilent_82350b_secondary_address, - .serial_poll_response = agilent_82350b_serial_poll_response, - .serial_poll_status = agilent_82350b_serial_poll_status, - .t1_delay = agilent_82350b_t1_delay, - .return_to_local = agilent_82350b_return_to_local, -}; - -static struct gpib_interface agilent_82350b_interface = { - .name = "agilent_82350b", - .attach = agilent_82350b_accel_attach, - .detach = agilent_82350b_detach, - .read = agilent_82350b_accel_read, - .write = agilent_82350b_accel_write, - .command = agilent_82350b_command, - .request_system_control = agilent_82350b_request_system_control, - .take_control = agilent_82350b_take_control, - .go_to_standby = agilent_82350b_go_to_standby, - .interface_clear = agilent_82350b_interface_clear, - .remote_enable = agilent_82350b_remote_enable, - .enable_eos = agilent_82350b_enable_eos, - .disable_eos = agilent_82350b_disable_eos, - .parallel_poll = agilent_82350b_parallel_poll, - .parallel_poll_configure = agilent_82350b_parallel_poll_configure, - .parallel_poll_response = agilent_82350b_parallel_poll_response, - .local_parallel_poll_mode = NULL, /* XXX */ - .line_status = agilent_82350b_line_status, - .update_status = agilent_82350b_update_status, - .primary_address = agilent_82350b_primary_address, - .secondary_address = agilent_82350b_secondary_address, - .serial_poll_response = agilent_82350b_serial_poll_response, - .serial_poll_status = agilent_82350b_serial_poll_status, - .t1_delay = agilent_82350b_t1_delay, - .return_to_local = agilent_82350b_return_to_local, -}; - -static int agilent_82350b_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) - -{ - return 0; -} - -static const struct pci_device_id agilent_82350b_pci_table[] = { - { PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_9050, PCI_VENDOR_ID_HP, - PCI_SUBDEVICE_ID_82350A, 0, 0, 0 }, - { PCI_VENDOR_ID_AGILENT, PCI_DEVICE_ID_82350B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, - { PCI_VENDOR_ID_AGILENT, PCI_DEVICE_ID_82351A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, - { 0 } -}; -MODULE_DEVICE_TABLE(pci, agilent_82350b_pci_table); - -static struct pci_driver agilent_82350b_pci_driver = { - .name = DRV_NAME, - .id_table = agilent_82350b_pci_table, - .probe = &agilent_82350b_pci_probe -}; - -static int __init agilent_82350b_init_module(void) -{ - int result; - - result = pci_register_driver(&agilent_82350b_pci_driver); - if (result) { - pr_err("pci_register_driver failed: error = %d\n", result); - return result; - } - - result = gpib_register_driver(&agilent_82350b_unaccel_interface, THIS_MODULE); - if (result) { - pr_err("gpib_register_driver failed: error = %d\n", result); - goto err_unaccel; - } - - result = gpib_register_driver(&agilent_82350b_interface, THIS_MODULE); - if (result) { - pr_err("gpib_register_driver failed: error = %d\n", result); - goto err_interface; - } - - return 0; - -err_interface: - gpib_unregister_driver(&agilent_82350b_unaccel_interface); -err_unaccel: - pci_unregister_driver(&agilent_82350b_pci_driver); - - return result; -} - -static void __exit agilent_82350b_exit_module(void) -{ - gpib_unregister_driver(&agilent_82350b_interface); - gpib_unregister_driver(&agilent_82350b_unaccel_interface); - - pci_unregister_driver(&agilent_82350b_pci_driver); -} - -module_init(agilent_82350b_init_module); -module_exit(agilent_82350b_exit_module); diff --git a/drivers/staging/gpib/agilent_82350b/agilent_82350b.h b/drivers/staging/gpib/agilent_82350b/agilent_82350b.h deleted file mode 100644 index ef841957297f..000000000000 --- a/drivers/staging/gpib/agilent_82350b/agilent_82350b.h +++ /dev/null @@ -1,157 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -/*************************************************************************** - * copyright : (C) 2002, 2004 by Frank Mori Hess * - ***************************************************************************/ - -#include "gpibP.h" -#include "plx9050.h" -#include "tms9914.h" - -enum pci_vendor_ids { - PCI_VENDOR_ID_AGILENT = 0x15bc, -}; - -enum pci_device_ids { - PCI_DEVICE_ID_82350B = 0x0b01, - PCI_DEVICE_ID_82351A = 0x1218 -}; - -enum pci_subdevice_ids { - PCI_SUBDEVICE_ID_82350A = 0x10b0, -}; - -enum pci_regions_82350a { - PLX_MEM_REGION = 0, - PLX_IO_REGION = 1, - GPIB_82350A_REGION = 2, - SRAM_82350A_REGION = 3, - BORG_82350A_REGION = 4 -}; - -enum pci_regions_82350b { - GPIB_REGION = 0, - SRAM_REGION = 1, - MISC_REGION = 2, -}; - -enum board_model { - MODEL_82350A, - MODEL_82350B, - MODEL_82351A -}; - -/* struct which defines private_data for board */ -struct agilent_82350b_priv { - struct tms9914_priv tms9914_priv; - struct pci_dev *pci_device; - void __iomem *plx_base; /* 82350a only */ - void __iomem *gpib_base; - void __iomem *sram_base; - void __iomem *misc_base; - void __iomem *borg_base; - int irq; - unsigned short card_mode_bits; - unsigned short event_status_bits; - enum board_model model; - bool using_fifos; -}; - -/* registers */ -enum agilent_82350b_gpib_registers - -{ - CARD_MODE_REG = 0x1, - CONFIG_DATA_REG = 0x2, /* 82350A specific */ - INTERRUPT_ENABLE_REG = 0x3, - EVENT_STATUS_REG = 0x4, - EVENT_ENABLE_REG = 0x5, - STREAM_STATUS_REG = 0x7, - DEBUG_RAM0_REG = 0x8, - DEBUG_RAM1_REG = 0x9, - DEBUG_RAM2_REG = 0xa, - DEBUG_RAM3_REG = 0xb, - XFER_COUNT_LO_REG = 0xc, - XFER_COUNT_MID_REG = 0xd, - XFER_COUNT_HI_REG = 0xe, - TMS9914_BASE_REG = 0x10, - INTERNAL_CONFIG_REG = 0x18, - IMR0_READ_REG = 0x19, /* read */ - T1_DELAY_REG = 0x19, /* write */ - IMR1_READ_REG = 0x1a, - ADR_READ_REG = 0x1b, - SPMR_READ_REG = 0x1c, - PPR_READ_REG = 0x1d, - CDOR_READ_REG = 0x1e, - SRAM_ACCESS_CONTROL_REG = 0x1f, -}; - -enum card_mode_bits - -{ - ACTIVE_CONTROLLER_BIT = 0x2, /* read-only */ - CM_SYSTEM_CONTROLLER_BIT = 0x8, - ENABLE_BUS_MONITOR_BIT = 0x10, - ENABLE_PCI_IRQ_BIT = 0x20, -}; - -enum interrupt_enable_bits - -{ - ENABLE_TMS9914_INTERRUPTS_BIT = 0x1, - ENABLE_BUFFER_END_INTERRUPT_BIT = 0x10, - ENABLE_TERM_COUNT_INTERRUPT_BIT = 0x20, -}; - -enum event_enable_bits - -{ - ENABLE_BUFFER_END_EVENTS_BIT = 0x10, - ENABLE_TERM_COUNT_EVENTS_BIT = 0x20, -}; - -enum event_status_bits - -{ - TMS9914_IRQ_STATUS_BIT = 0x1, - IRQ_STATUS_BIT = 0x2, - BUFFER_END_STATUS_BIT = 0x10, /* write-clear */ - TERM_COUNT_STATUS_BIT = 0x20, /* write-clear */ -}; - -enum stream_status_bits - -{ - HALTED_STATUS_BIT = 0x1, /* read */ - RESTART_STREAM_BIT = 0x1, /* write */ -}; - -enum internal_config_bits - -{ - IC_SYSTEM_CONTROLLER_BIT = 0x80, -}; - -enum sram_access_control_bits - -{ - DIRECTION_GPIB_TO_HOST = 0x20, /* transfer direction */ - ENABLE_TI_TO_SRAM = 0x40, /* enable fifo */ - ENABLE_FAST_TALKER = 0x80 /* added for 82350A (not used) */ -}; - -enum borg_bits - -{ - BORG_READY_BIT = 0x40, - BORG_DONE_BIT = 0x80 -}; - -static const int agilent_82350b_fifo_size = 0x8000; - -static inline int agilent_82350b_fifo_is_halted(struct agilent_82350b_priv *a_priv) - -{ - return readb(a_priv->gpib_base + STREAM_STATUS_REG) & HALTED_STATUS_BIT; -} - diff --git a/drivers/staging/gpib/agilent_82357a/Makefile b/drivers/staging/gpib/agilent_82357a/Makefile deleted file mode 100644 index 81a55c257a6e..000000000000 --- a/drivers/staging/gpib/agilent_82357a/Makefile +++ /dev/null @@ -1,4 +0,0 @@ - -obj-$(CONFIG_GPIB_AGILENT_82357A) += agilent_82357a.o - - diff --git a/drivers/staging/gpib/agilent_82357a/agilent_82357a.c b/drivers/staging/gpib/agilent_82357a/agilent_82357a.c deleted file mode 100644 index 77c8e549b208..000000000000 --- a/drivers/staging/gpib/agilent_82357a/agilent_82357a.c +++ /dev/null @@ -1,1691 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 - -/*************************************************************************** - * driver for Agilent 82357A/B usb to gpib adapters * - * copyright : (C) 2004 by Frank Mori Hess * - ***************************************************************************/ - -#define _GNU_SOURCE - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -#define dev_fmt pr_fmt -#define DRV_NAME KBUILD_MODNAME - -#include -#include -#include -#include "agilent_82357a.h" -#include "gpibP.h" -#include "tms9914.h" - -MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("GPIB driver for Agilent 82357A/B usb adapters"); - -#define MAX_NUM_82357A_INTERFACES 128 -static struct usb_interface *agilent_82357a_driver_interfaces[MAX_NUM_82357A_INTERFACES]; -static DEFINE_MUTEX(agilent_82357a_hotplug_lock); // protect board insertion and removal - -static unsigned int agilent_82357a_update_status(struct gpib_board *board, - unsigned int clear_mask); - -static int agilent_82357a_take_control_internal(struct gpib_board *board, int synchronous); - -static void agilent_82357a_bulk_complete(struct urb *urb) -{ - struct agilent_82357a_urb_ctx *context = urb->context; - - complete(&context->complete); -} - -static void agilent_82357a_timeout_handler(struct timer_list *t) -{ - struct agilent_82357a_priv *a_priv = timer_container_of(a_priv, t, - bulk_timer); - struct agilent_82357a_urb_ctx *context = &a_priv->context; - - context->timed_out = 1; - complete(&context->complete); -} - -static int agilent_82357a_send_bulk_msg(struct agilent_82357a_priv *a_priv, void *data, - int data_length, int *actual_data_length, - int timeout_msecs) -{ - struct usb_device *usb_dev; - int retval; - unsigned int out_pipe; - struct agilent_82357a_urb_ctx *context = &a_priv->context; - - *actual_data_length = 0; - retval = mutex_lock_interruptible(&a_priv->bulk_alloc_lock); - if (retval) - return retval; - if (!a_priv->bus_interface) { - mutex_unlock(&a_priv->bulk_alloc_lock); - return -ENODEV; - } - if (a_priv->bulk_urb) { - mutex_unlock(&a_priv->bulk_alloc_lock); - return -EAGAIN; - } - a_priv->bulk_urb = usb_alloc_urb(0, GFP_KERNEL); - if (!a_priv->bulk_urb) { - mutex_unlock(&a_priv->bulk_alloc_lock); - return -ENOMEM; - } - usb_dev = interface_to_usbdev(a_priv->bus_interface); - out_pipe = usb_sndbulkpipe(usb_dev, a_priv->bulk_out_endpoint); - init_completion(&context->complete); - context->timed_out = 0; - usb_fill_bulk_urb(a_priv->bulk_urb, usb_dev, out_pipe, data, data_length, - &agilent_82357a_bulk_complete, context); - - if (timeout_msecs) - mod_timer(&a_priv->bulk_timer, jiffies + msecs_to_jiffies(timeout_msecs)); - - retval = usb_submit_urb(a_priv->bulk_urb, GFP_KERNEL); - if (retval) { - dev_err(&usb_dev->dev, "failed to submit bulk out urb, retval=%i\n", retval); - mutex_unlock(&a_priv->bulk_alloc_lock); - goto cleanup; - } - mutex_unlock(&a_priv->bulk_alloc_lock); - if (wait_for_completion_interruptible(&context->complete)) { - retval = -ERESTARTSYS; - goto cleanup; - } - if (context->timed_out) { - retval = -ETIMEDOUT; - } else { - retval = a_priv->bulk_urb->status; - *actual_data_length = a_priv->bulk_urb->actual_length; - } -cleanup: - if (timeout_msecs) { - if (timer_pending(&a_priv->bulk_timer)) - timer_delete_sync(&a_priv->bulk_timer); - } - mutex_lock(&a_priv->bulk_alloc_lock); - if (a_priv->bulk_urb) { - usb_kill_urb(a_priv->bulk_urb); - usb_free_urb(a_priv->bulk_urb); - a_priv->bulk_urb = NULL; - } - mutex_unlock(&a_priv->bulk_alloc_lock); - return retval; -} - -static int agilent_82357a_receive_bulk_msg(struct agilent_82357a_priv *a_priv, void *data, - int data_length, int *actual_data_length, - int timeout_msecs) -{ - struct usb_device *usb_dev; - int retval; - unsigned int in_pipe; - struct agilent_82357a_urb_ctx *context = &a_priv->context; - - *actual_data_length = 0; - retval = mutex_lock_interruptible(&a_priv->bulk_alloc_lock); - if (retval) - return retval; - if (!a_priv->bus_interface) { - mutex_unlock(&a_priv->bulk_alloc_lock); - return -ENODEV; - } - if (a_priv->bulk_urb) { - mutex_unlock(&a_priv->bulk_alloc_lock); - return -EAGAIN; - } - a_priv->bulk_urb = usb_alloc_urb(0, GFP_KERNEL); - if (!a_priv->bulk_urb) { - mutex_unlock(&a_priv->bulk_alloc_lock); - return -ENOMEM; - } - usb_dev = interface_to_usbdev(a_priv->bus_interface); - in_pipe = usb_rcvbulkpipe(usb_dev, AGILENT_82357_BULK_IN_ENDPOINT); - init_completion(&context->complete); - context->timed_out = 0; - usb_fill_bulk_urb(a_priv->bulk_urb, usb_dev, in_pipe, data, data_length, - &agilent_82357a_bulk_complete, context); - - if (timeout_msecs) - mod_timer(&a_priv->bulk_timer, jiffies + msecs_to_jiffies(timeout_msecs)); - - retval = usb_submit_urb(a_priv->bulk_urb, GFP_KERNEL); - if (retval) { - dev_err(&usb_dev->dev, "failed to submit bulk in urb, retval=%i\n", retval); - mutex_unlock(&a_priv->bulk_alloc_lock); - goto cleanup; - } - mutex_unlock(&a_priv->bulk_alloc_lock); - if (wait_for_completion_interruptible(&context->complete)) { - retval = -ERESTARTSYS; - goto cleanup; - } - if (context->timed_out) { - retval = -ETIMEDOUT; - goto cleanup; - } - retval = a_priv->bulk_urb->status; - *actual_data_length = a_priv->bulk_urb->actual_length; -cleanup: - if (timeout_msecs) - timer_delete_sync(&a_priv->bulk_timer); - - mutex_lock(&a_priv->bulk_alloc_lock); - if (a_priv->bulk_urb) { - usb_kill_urb(a_priv->bulk_urb); - usb_free_urb(a_priv->bulk_urb); - a_priv->bulk_urb = NULL; - } - mutex_unlock(&a_priv->bulk_alloc_lock); - return retval; -} - -static int agilent_82357a_receive_control_msg(struct agilent_82357a_priv *a_priv, __u8 request, - __u8 requesttype, __u16 value, __u16 index, - void *data, __u16 size, int timeout_msecs) -{ - struct usb_device *usb_dev; - int retval; - unsigned int in_pipe; - - retval = mutex_lock_interruptible(&a_priv->control_alloc_lock); - if (retval) - return retval; - if (!a_priv->bus_interface) { - mutex_unlock(&a_priv->control_alloc_lock); - return -ENODEV; - } - usb_dev = interface_to_usbdev(a_priv->bus_interface); - in_pipe = usb_rcvctrlpipe(usb_dev, AGILENT_82357_CONTROL_ENDPOINT); - retval = usb_control_msg(usb_dev, in_pipe, request, requesttype, value, index, data, - size, timeout_msecs); - mutex_unlock(&a_priv->control_alloc_lock); - return retval; -} - -static void agilent_82357a_dump_raw_block(const u8 *raw_data, int length) -{ - print_hex_dump(KERN_INFO, "", DUMP_PREFIX_NONE, 8, 1, raw_data, length, true); -} - -static int agilent_82357a_write_registers(struct agilent_82357a_priv *a_priv, - const struct agilent_82357a_register_pairlet *writes, - int num_writes) -{ - struct usb_device *usb_dev = interface_to_usbdev(a_priv->bus_interface); - int retval; - u8 *out_data, *in_data; - int out_data_length, in_data_length; - int bytes_written, bytes_read; - int i = 0; - int j; - static const int bytes_per_write = 2; - static const int header_length = 2; - static const int max_writes = 31; - - if (num_writes > max_writes) { - dev_err(&usb_dev->dev, "bug! num_writes=%i too large\n", num_writes); - return -EIO; - } - out_data_length = num_writes * bytes_per_write + header_length; - out_data = kmalloc(out_data_length, GFP_KERNEL); - if (!out_data) - return -ENOMEM; - - out_data[i++] = DATA_PIPE_CMD_WR_REGS; - out_data[i++] = num_writes; - for (j = 0; j < num_writes; j++) { - out_data[i++] = writes[j].address; - out_data[i++] = writes[j].value; - } - - retval = mutex_lock_interruptible(&a_priv->bulk_transfer_lock); - if (retval) { - kfree(out_data); - return retval; - } - retval = agilent_82357a_send_bulk_msg(a_priv, out_data, i, &bytes_written, 1000); - kfree(out_data); - if (retval) { - dev_err(&usb_dev->dev, "send_bulk_msg returned %i, bytes_written=%i, i=%i\n", - retval, bytes_written, i); - mutex_unlock(&a_priv->bulk_transfer_lock); - return retval; - } - in_data_length = 0x20; - in_data = kmalloc(in_data_length, GFP_KERNEL); - if (!in_data) { - mutex_unlock(&a_priv->bulk_transfer_lock); - return -ENOMEM; - } - retval = agilent_82357a_receive_bulk_msg(a_priv, in_data, in_data_length, - &bytes_read, 1000); - mutex_unlock(&a_priv->bulk_transfer_lock); - - if (retval) { - dev_err(&usb_dev->dev, "receive_bulk_msg returned %i, bytes_read=%i\n", - retval, bytes_read); - agilent_82357a_dump_raw_block(in_data, bytes_read); - kfree(in_data); - return -EIO; - } - if (in_data[0] != (0xff & ~DATA_PIPE_CMD_WR_REGS)) { - dev_err(&usb_dev->dev, "bulk command=0x%x != ~DATA_PIPE_CMD_WR_REGS\n", in_data[0]); - return -EIO; - } - if (in_data[1]) { - dev_err(&usb_dev->dev, "nonzero error code 0x%x in DATA_PIPE_CMD_WR_REGS response\n", - in_data[1]); - return -EIO; - } - kfree(in_data); - return 0; -} - -static int agilent_82357a_read_registers(struct agilent_82357a_priv *a_priv, - struct agilent_82357a_register_pairlet *reads, - int num_reads, int blocking) -{ - struct usb_device *usb_dev = interface_to_usbdev(a_priv->bus_interface); - int retval; - u8 *out_data, *in_data; - int out_data_length, in_data_length; - int bytes_written, bytes_read; - int i = 0; - int j; - static const int header_length = 2; - static const int max_reads = 62; - - if (num_reads > max_reads) { - dev_err(&usb_dev->dev, "bug! num_reads=%i too large\n", num_reads); - return -EIO; - } - out_data_length = num_reads + header_length; - out_data = kmalloc(out_data_length, GFP_KERNEL); - if (!out_data) - return -ENOMEM; - - out_data[i++] = DATA_PIPE_CMD_RD_REGS; - out_data[i++] = num_reads; - for (j = 0; j < num_reads; j++) - out_data[i++] = reads[j].address; - - if (blocking) { - retval = mutex_lock_interruptible(&a_priv->bulk_transfer_lock); - if (retval) { - kfree(out_data); - return retval; - } - } else { - retval = mutex_trylock(&a_priv->bulk_transfer_lock); - if (retval == 0) { - kfree(out_data); - return -EAGAIN; - } - } - retval = agilent_82357a_send_bulk_msg(a_priv, out_data, i, &bytes_written, 1000); - kfree(out_data); - if (retval) { - dev_err(&usb_dev->dev, "send_bulk_msg returned %i, bytes_written=%i, i=%i\n", - retval, bytes_written, i); - mutex_unlock(&a_priv->bulk_transfer_lock); - return retval; - } - in_data_length = 0x20; - in_data = kmalloc(in_data_length, GFP_KERNEL); - if (!in_data) { - mutex_unlock(&a_priv->bulk_transfer_lock); - return -ENOMEM; - } - retval = agilent_82357a_receive_bulk_msg(a_priv, in_data, in_data_length, - &bytes_read, 10000); - mutex_unlock(&a_priv->bulk_transfer_lock); - - if (retval) { - dev_err(&usb_dev->dev, "receive_bulk_msg returned %i, bytes_read=%i\n", - retval, bytes_read); - agilent_82357a_dump_raw_block(in_data, bytes_read); - kfree(in_data); - return -EIO; - } - i = 0; - if (in_data[i++] != (0xff & ~DATA_PIPE_CMD_RD_REGS)) { - dev_err(&usb_dev->dev, "bulk command=0x%x != ~DATA_PIPE_CMD_RD_REGS\n", in_data[0]); - return -EIO; - } - if (in_data[i++]) { - dev_err(&usb_dev->dev, "nonzero error code 0x%x in DATA_PIPE_CMD_RD_REGS response\n", - in_data[1]); - return -EIO; - } - for (j = 0; j < num_reads; j++) - reads[j].value = in_data[i++]; - kfree(in_data); - return 0; -} - -static int agilent_82357a_abort(struct agilent_82357a_priv *a_priv, int flush) -{ - struct usb_device *usb_dev = interface_to_usbdev(a_priv->bus_interface); - int retval = 0; - int receive_control_retval; - u16 wIndex = 0; - u8 *status_data; - static const unsigned int status_data_len = 2; - - status_data = kmalloc(status_data_len, GFP_KERNEL); - if (!status_data) - return -ENOMEM; - - if (flush) - wIndex |= XA_FLUSH; - receive_control_retval = agilent_82357a_receive_control_msg(a_priv, - agilent_82357a_control_request, - USB_DIR_IN | USB_TYPE_VENDOR | - USB_RECIP_DEVICE, XFER_ABORT, - wIndex, status_data, - status_data_len, 100); - if (receive_control_retval < 0) { - dev_err(&usb_dev->dev, "82357a_receive_control_msg() returned %i\n", - receive_control_retval); - retval = -EIO; - goto cleanup; - } - if (status_data[0] != (~XFER_ABORT & 0xff)) { - dev_err(&usb_dev->dev, "major code=0x%x != ~XFER_ABORT\n", status_data[0]); - retval = -EIO; - goto cleanup; - } - switch (status_data[1]) { - case UGP_SUCCESS: - retval = 0; - break; - case UGP_ERR_FLUSHING: - if (flush) { - retval = 0; - break; - } - fallthrough; - case UGP_ERR_FLUSHING_ALREADY: - default: - dev_err(&usb_dev->dev, "abort returned error code=0x%x\n", status_data[1]); - retval = -EIO; - break; - } - -cleanup: - kfree(status_data); - return retval; -} - -// interface functions -int agilent_82357a_command(struct gpib_board *board, u8 *buffer, size_t length, - size_t *bytes_written); - -static int agilent_82357a_read(struct gpib_board *board, u8 *buffer, size_t length, int *end, - size_t *nbytes) -{ - int retval; - struct agilent_82357a_priv *a_priv = board->private_data; - struct usb_device *usb_dev; - u8 *out_data, *in_data; - int out_data_length, in_data_length; - int bytes_written, bytes_read; - int i = 0; - u8 trailing_flags; - unsigned long start_jiffies = jiffies; - int msec_timeout; - - *nbytes = 0; - *end = 0; - - if (!a_priv->bus_interface) - return -ENODEV; - usb_dev = interface_to_usbdev(a_priv->bus_interface); - out_data_length = 0x9; - out_data = kmalloc(out_data_length, GFP_KERNEL); - if (!out_data) - return -ENOMEM; - out_data[i++] = DATA_PIPE_CMD_READ; - out_data[i++] = 0; // primary address when ARF_NO_ADDR is not set - out_data[i++] = 0; // secondary address when ARF_NO_ADDR is not set - out_data[i] = ARF_NO_ADDRESS | ARF_END_ON_EOI; - if (a_priv->eos_mode & REOS) - out_data[i] |= ARF_END_ON_EOS_CHAR; - ++i; - out_data[i++] = length & 0xff; - out_data[i++] = (length >> 8) & 0xff; - out_data[i++] = (length >> 16) & 0xff; - out_data[i++] = (length >> 24) & 0xff; - out_data[i++] = a_priv->eos_char; - msec_timeout = (board->usec_timeout + 999) / 1000; - retval = mutex_lock_interruptible(&a_priv->bulk_transfer_lock); - if (retval) { - kfree(out_data); - return retval; - } - retval = agilent_82357a_send_bulk_msg(a_priv, out_data, i, &bytes_written, msec_timeout); - kfree(out_data); - if (retval || bytes_written != i) { - dev_err(&usb_dev->dev, "send_bulk_msg returned %i, bytes_written=%i, i=%i\n", - retval, bytes_written, i); - mutex_unlock(&a_priv->bulk_transfer_lock); - if (retval < 0) - return retval; - return -EIO; - } - in_data_length = length + 1; - in_data = kmalloc(in_data_length, GFP_KERNEL); - if (!in_data) { - mutex_unlock(&a_priv->bulk_transfer_lock); - return -ENOMEM; - } - if (board->usec_timeout != 0) - msec_timeout -= jiffies_to_msecs(jiffies - start_jiffies) - 1; - if (msec_timeout >= 0) { - retval = agilent_82357a_receive_bulk_msg(a_priv, in_data, in_data_length, - &bytes_read, msec_timeout); - } else { - retval = -ETIMEDOUT; - bytes_read = 0; - } - if (retval == -ETIMEDOUT) { - int extra_bytes_read; - int extra_bytes_retval; - - agilent_82357a_abort(a_priv, 1); - extra_bytes_retval = agilent_82357a_receive_bulk_msg(a_priv, in_data + bytes_read, - in_data_length - bytes_read, - &extra_bytes_read, 100); - bytes_read += extra_bytes_read; - if (extra_bytes_retval) { - dev_err(&usb_dev->dev, "extra_bytes_retval=%i, bytes_read=%i\n", - extra_bytes_retval, bytes_read); - agilent_82357a_abort(a_priv, 0); - } - } else if (retval) { - dev_err(&usb_dev->dev, "receive_bulk_msg returned %i, bytes_read=%i\n", - retval, bytes_read); - agilent_82357a_abort(a_priv, 0); - } - mutex_unlock(&a_priv->bulk_transfer_lock); - if (bytes_read > length + 1) { - bytes_read = length + 1; - dev_warn(&usb_dev->dev, "bytes_read > length? truncating"); - } - - if (bytes_read >= 1) { - memcpy(buffer, in_data, bytes_read - 1); - trailing_flags = in_data[bytes_read - 1]; - *nbytes = bytes_read - 1; - if (trailing_flags & (ATRF_EOI | ATRF_EOS)) - *end = 1; - } - kfree(in_data); - - /* - * Fix for a bug in 9914A that does not return the contents of ADSR - * when the board is in listener active state and ATN is not asserted. - * Set ATN here to obtain a valid board level ibsta - */ - agilent_82357a_take_control_internal(board, 0); - - // FIXME check trailing flags for error - return retval; -} - -static ssize_t agilent_82357a_generic_write(struct gpib_board *board, - u8 *buffer, size_t length, - int send_commands, int send_eoi, - size_t *bytes_written) -{ - int retval; - struct agilent_82357a_priv *a_priv = board->private_data; - struct usb_device *usb_dev; - u8 *out_data = NULL; - u8 *status_data = NULL; - int out_data_length; - int raw_bytes_written; - int i = 0, j; - int msec_timeout; - unsigned short bsr, adsr; - struct agilent_82357a_register_pairlet read_reg; - - *bytes_written = 0; - if (!a_priv->bus_interface) - return -ENODEV; - - usb_dev = interface_to_usbdev(a_priv->bus_interface); - out_data_length = length + 0x8; - out_data = kmalloc(out_data_length, GFP_KERNEL); - if (!out_data) - return -ENOMEM; - out_data[i++] = DATA_PIPE_CMD_WRITE; - out_data[i++] = 0; // primary address when AWF_NO_ADDRESS is not set - out_data[i++] = 0; // secondary address when AWF_NO_ADDRESS is not set - out_data[i] = AWF_NO_ADDRESS | AWF_NO_FAST_TALKER_FIRST_BYTE; - if (send_commands) - out_data[i] |= AWF_ATN | AWF_NO_FAST_TALKER; - if (send_eoi) - out_data[i] |= AWF_SEND_EOI; - ++i; - out_data[i++] = length & 0xff; - out_data[i++] = (length >> 8) & 0xff; - out_data[i++] = (length >> 16) & 0xff; - out_data[i++] = (length >> 24) & 0xff; - for (j = 0; j < length; j++) - out_data[i++] = buffer[j]; - - clear_bit(AIF_WRITE_COMPLETE_BN, &a_priv->interrupt_flags); - - msec_timeout = (board->usec_timeout + 999) / 1000; - retval = mutex_lock_interruptible(&a_priv->bulk_transfer_lock); - if (retval) { - kfree(out_data); - return retval; - } - retval = agilent_82357a_send_bulk_msg(a_priv, out_data, i, &raw_bytes_written, - msec_timeout); - kfree(out_data); - if (retval || raw_bytes_written != i) { - agilent_82357a_abort(a_priv, 0); - dev_err(&usb_dev->dev, "send_bulk_msg returned %i, raw_bytes_written=%i, i=%i\n", - retval, raw_bytes_written, i); - mutex_unlock(&a_priv->bulk_transfer_lock); - if (retval < 0) - return retval; - return -EIO; - } - - retval = wait_event_interruptible(board->wait, - test_bit(AIF_WRITE_COMPLETE_BN, - &a_priv->interrupt_flags) || - test_bit(TIMO_NUM, &board->status)); - if (retval) { - dev_dbg(&usb_dev->dev, "wait write complete interrupted\n"); - agilent_82357a_abort(a_priv, 0); - mutex_unlock(&a_priv->bulk_transfer_lock); - return -ERESTARTSYS; - } - - if (test_bit(AIF_WRITE_COMPLETE_BN, &a_priv->interrupt_flags) == 0) { - dev_dbg(&usb_dev->dev, "write timed out ibs %i, tmo %i\n", - test_bit(TIMO_NUM, &board->status), msec_timeout); - - agilent_82357a_abort(a_priv, 0); - - mutex_unlock(&a_priv->bulk_transfer_lock); - - read_reg.address = BSR; - retval = agilent_82357a_read_registers(a_priv, &read_reg, 1, 1); - if (retval) { - dev_err(&usb_dev->dev, "read_registers() returned error\n"); - return -ETIMEDOUT; - } - - bsr = read_reg.value; - dev_dbg(&usb_dev->dev, "write aborted bsr 0x%x\n", bsr); - - if (send_commands) {/* check for no listeners */ - if ((bsr & BSR_ATN_BIT) && !(bsr & (BSR_NDAC_BIT | BSR_NRFD_BIT))) { - dev_dbg(&usb_dev->dev, "No listener on command\n"); - clear_bit(TIMO_NUM, &board->status); - return -ENOTCONN; // no listener on bus - } - } else { - read_reg.address = ADSR; - retval = agilent_82357a_read_registers(a_priv, &read_reg, 1, 1); - if (retval) { - dev_err(&usb_dev->dev, "read_registers() returned error\n"); - return -ETIMEDOUT; - } - adsr = read_reg.value; - if ((adsr & HR_TA) && !(bsr & (BSR_NDAC_BIT | BSR_NRFD_BIT))) { - dev_dbg(&usb_dev->dev, "No listener on write\n"); - clear_bit(TIMO_NUM, &board->status); - return -ECOMM; - } - } - - return -ETIMEDOUT; - } - - status_data = kmalloc(STATUS_DATA_LEN, GFP_KERNEL); - if (!status_data) { - mutex_unlock(&a_priv->bulk_transfer_lock); - return -ENOMEM; - } - - retval = agilent_82357a_receive_control_msg(a_priv, agilent_82357a_control_request, - USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, - XFER_STATUS, 0, status_data, STATUS_DATA_LEN, - 100); - mutex_unlock(&a_priv->bulk_transfer_lock); - if (retval < 0) { - dev_err(&usb_dev->dev, "receive_control_msg() returned %i\n", retval); - kfree(status_data); - return -EIO; - } - *bytes_written = (u32)status_data[2]; - *bytes_written |= (u32)status_data[3] << 8; - *bytes_written |= (u32)status_data[4] << 16; - *bytes_written |= (u32)status_data[5] << 24; - - kfree(status_data); - return 0; -} - -static int agilent_82357a_write(struct gpib_board *board, u8 *buffer, - size_t length, int send_eoi, size_t *bytes_written) -{ - return agilent_82357a_generic_write(board, buffer, length, 0, send_eoi, bytes_written); -} - -int agilent_82357a_command(struct gpib_board *board, u8 *buffer, size_t length, - size_t *bytes_written) -{ - return agilent_82357a_generic_write(board, buffer, length, 1, 0, bytes_written); -} - -int agilent_82357a_take_control_internal(struct gpib_board *board, int synchronous) -{ - struct agilent_82357a_priv *a_priv = board->private_data; - struct usb_device *usb_dev = interface_to_usbdev(a_priv->bus_interface); - struct agilent_82357a_register_pairlet write; - int retval; - - write.address = AUXCR; - if (synchronous) - write.value = AUX_TCS; - else - write.value = AUX_TCA; - retval = agilent_82357a_write_registers(a_priv, &write, 1); - if (retval) - dev_err(&usb_dev->dev, "write_registers() returned error\n"); - - return retval; -} - -static int agilent_82357a_take_control(struct gpib_board *board, int synchronous) -{ - struct agilent_82357a_priv *a_priv = board->private_data; - const int timeout = 10; - int i; - - if (!a_priv->bus_interface) - return -ENODEV; - -/* - * It looks like the 9914 does not handle tcs properly. - * See comment above tms9914_take_control_workaround() in - * drivers/gpib/tms9914/tms9914_aux.c - */ - if (synchronous) - return -ETIMEDOUT; - - agilent_82357a_take_control_internal(board, synchronous); - // busy wait until ATN is asserted - for (i = 0; i < timeout; ++i) { - agilent_82357a_update_status(board, 0); - if (test_bit(ATN_NUM, &board->status)) - break; - udelay(1); - } - if (i == timeout) - return -ETIMEDOUT; - return 0; -} - -static int agilent_82357a_go_to_standby(struct gpib_board *board) -{ - struct agilent_82357a_priv *a_priv = board->private_data; - struct usb_device *usb_dev; - struct agilent_82357a_register_pairlet write; - int retval; - - if (!a_priv->bus_interface) - return -ENODEV; - - usb_dev = interface_to_usbdev(a_priv->bus_interface); - write.address = AUXCR; - write.value = AUX_GTS; - retval = agilent_82357a_write_registers(a_priv, &write, 1); - if (retval) - dev_err(&usb_dev->dev, "write_registers() returned error\n"); - return 0; -} - -static int agilent_82357a_request_system_control(struct gpib_board *board, int request_control) -{ - struct agilent_82357a_priv *a_priv = board->private_data; - struct usb_device *usb_dev; - struct agilent_82357a_register_pairlet writes[2]; - int retval; - int i = 0; - - if (!a_priv->bus_interface) - return -ENODEV; - - usb_dev = interface_to_usbdev(a_priv->bus_interface); - /* 82357B needs bit to be set in 9914 AUXCR register */ - writes[i].address = AUXCR; - if (request_control) { - writes[i].value = AUX_RQC; - a_priv->hw_control_bits |= SYSTEM_CONTROLLER; - } else { - return -EINVAL; - } - ++i; - writes[i].address = HW_CONTROL; - writes[i].value = a_priv->hw_control_bits; - ++i; - retval = agilent_82357a_write_registers(a_priv, writes, i); - if (retval) - dev_err(&usb_dev->dev, "write_registers() returned error\n"); - return retval; -} - -static void agilent_82357a_interface_clear(struct gpib_board *board, int assert) -{ - struct agilent_82357a_priv *a_priv = board->private_data; - struct usb_device *usb_dev; - struct agilent_82357a_register_pairlet write; - int retval; - - if (!a_priv->bus_interface) - return; // -ENODEV; - - usb_dev = interface_to_usbdev(a_priv->bus_interface); - write.address = AUXCR; - write.value = AUX_SIC; - if (assert) { - write.value |= AUX_CS; - a_priv->is_cic = 1; - } - retval = agilent_82357a_write_registers(a_priv, &write, 1); - if (retval) - dev_err(&usb_dev->dev, "write_registers() returned error\n"); -} - -static void agilent_82357a_remote_enable(struct gpib_board *board, int enable) -{ - struct agilent_82357a_priv *a_priv = board->private_data; - struct usb_device *usb_dev; - struct agilent_82357a_register_pairlet write; - int retval; - - if (!a_priv->bus_interface) - return; //-ENODEV; - - usb_dev = interface_to_usbdev(a_priv->bus_interface); - write.address = AUXCR; - write.value = AUX_SRE; - if (enable) - write.value |= AUX_CS; - retval = agilent_82357a_write_registers(a_priv, &write, 1); - if (retval) - dev_err(&usb_dev->dev, "write_registers() returned error\n"); - a_priv->ren_state = enable; - return;// 0; -} - -static int agilent_82357a_enable_eos(struct gpib_board *board, u8 eos_byte, - int compare_8_bits) -{ - struct agilent_82357a_priv *a_priv = board->private_data; - - if (!a_priv->bus_interface) - return -ENODEV; - if (compare_8_bits == 0) - return -EOPNOTSUPP; - - a_priv->eos_char = eos_byte; - a_priv->eos_mode = REOS | BIN; - return 0; -} - -static void agilent_82357a_disable_eos(struct gpib_board *board) -{ - struct agilent_82357a_priv *a_priv = board->private_data; - - a_priv->eos_mode &= ~REOS; -} - -static unsigned int agilent_82357a_update_status(struct gpib_board *board, - unsigned int clear_mask) -{ - struct agilent_82357a_priv *a_priv = board->private_data; - struct usb_device *usb_dev; - struct agilent_82357a_register_pairlet address_status, bus_status; - int retval; - - if (!a_priv->bus_interface) - return -ENODEV; - usb_dev = interface_to_usbdev(a_priv->bus_interface); - board->status &= ~clear_mask; - if (a_priv->is_cic) - set_bit(CIC_NUM, &board->status); - else - clear_bit(CIC_NUM, &board->status); - address_status.address = ADSR; - retval = agilent_82357a_read_registers(a_priv, &address_status, 1, 0); - if (retval) { - if (retval != -EAGAIN) - dev_err(&usb_dev->dev, "read_registers() returned error\n"); - return board->status; - } - // check for remote/local - if (address_status.value & HR_REM) - set_bit(REM_NUM, &board->status); - else - clear_bit(REM_NUM, &board->status); - // check for lockout - if (address_status.value & HR_LLO) - set_bit(LOK_NUM, &board->status); - else - clear_bit(LOK_NUM, &board->status); - // check for ATN - if (address_status.value & HR_ATN) - set_bit(ATN_NUM, &board->status); - else - clear_bit(ATN_NUM, &board->status); - // check for talker/listener addressed - if (address_status.value & HR_TA) - set_bit(TACS_NUM, &board->status); - else - clear_bit(TACS_NUM, &board->status); - if (address_status.value & HR_LA) - set_bit(LACS_NUM, &board->status); - else - clear_bit(LACS_NUM, &board->status); - - bus_status.address = BSR; - retval = agilent_82357a_read_registers(a_priv, &bus_status, 1, 0); - if (retval) { - if (retval != -EAGAIN) - dev_err(&usb_dev->dev, "read_registers() returned error\n"); - return board->status; - } - if (bus_status.value & BSR_SRQ_BIT) - set_bit(SRQI_NUM, &board->status); - else - clear_bit(SRQI_NUM, &board->status); - - return board->status; -} - -static int agilent_82357a_primary_address(struct gpib_board *board, unsigned int address) -{ - struct agilent_82357a_priv *a_priv = board->private_data; - struct usb_device *usb_dev = interface_to_usbdev(a_priv->bus_interface); - struct agilent_82357a_register_pairlet write; - int retval; - - if (!a_priv->bus_interface) - return -ENODEV; - usb_dev = interface_to_usbdev(a_priv->bus_interface); - // put primary address in address0 - write.address = ADR; - write.value = address & ADDRESS_MASK; - retval = agilent_82357a_write_registers(a_priv, &write, 1); - if (retval) { - dev_err(&usb_dev->dev, "write_registers() returned error\n"); - return retval; - } - return retval; -} - -static int agilent_82357a_secondary_address(struct gpib_board *board, - unsigned int address, int enable) -{ - if (enable) - return -EOPNOTSUPP; - return 0; -} - -static int agilent_82357a_parallel_poll(struct gpib_board *board, u8 *result) -{ - struct agilent_82357a_priv *a_priv = board->private_data; - struct usb_device *usb_dev; - struct agilent_82357a_register_pairlet writes[2]; - struct agilent_82357a_register_pairlet read; - int retval; - - if (!a_priv->bus_interface) - return -ENODEV; - usb_dev = interface_to_usbdev(a_priv->bus_interface); - // execute parallel poll - writes[0].address = AUXCR; - writes[0].value = AUX_CS | AUX_RPP; - writes[1].address = HW_CONTROL; - writes[1].value = a_priv->hw_control_bits & ~NOT_PARALLEL_POLL; - retval = agilent_82357a_write_registers(a_priv, writes, 2); - if (retval) { - dev_err(&usb_dev->dev, "write_registers() returned error\n"); - return retval; - } - udelay(2); // silly, since usb write will take way longer - read.address = CPTR; - retval = agilent_82357a_read_registers(a_priv, &read, 1, 1); - if (retval) { - dev_err(&usb_dev->dev, "read_registers() returned error\n"); - return retval; - } - *result = read.value; - // clear parallel poll state - writes[0].address = HW_CONTROL; - writes[0].value = a_priv->hw_control_bits | NOT_PARALLEL_POLL; - writes[1].address = AUXCR; - writes[1].value = AUX_RPP; - retval = agilent_82357a_write_registers(a_priv, writes, 2); - if (retval) { - dev_err(&usb_dev->dev, "write_registers() returned error\n"); - return retval; - } - return 0; -} - -static void agilent_82357a_parallel_poll_configure(struct gpib_board *board, u8 config) -{ - // board can only be system controller - return;// 0; -} - -static void agilent_82357a_parallel_poll_response(struct gpib_board *board, int ist) -{ - // board can only be system controller - return;// 0; -} - -static void agilent_82357a_serial_poll_response(struct gpib_board *board, u8 status) -{ - // board can only be system controller - return;// 0; -} - -static u8 agilent_82357a_serial_poll_status(struct gpib_board *board) -{ - // board can only be system controller - return 0; -} - -static void agilent_82357a_return_to_local(struct gpib_board *board) -{ - // board can only be system controller - return;// 0; -} - -static int agilent_82357a_line_status(const struct gpib_board *board) -{ - struct agilent_82357a_priv *a_priv = board->private_data; - struct usb_device *usb_dev; - struct agilent_82357a_register_pairlet bus_status; - int retval; - int status = VALID_ALL; - - if (!a_priv->bus_interface) - return -ENODEV; - usb_dev = interface_to_usbdev(a_priv->bus_interface); - bus_status.address = BSR; - retval = agilent_82357a_read_registers(a_priv, &bus_status, 1, 0); - if (retval) { - if (retval != -EAGAIN) - dev_err(&usb_dev->dev, "read_registers() returned error\n"); - return retval; - } - if (bus_status.value & BSR_REN_BIT) - status |= BUS_REN; - if (bus_status.value & BSR_IFC_BIT) - status |= BUS_IFC; - if (bus_status.value & BSR_SRQ_BIT) - status |= BUS_SRQ; - if (bus_status.value & BSR_EOI_BIT) - status |= BUS_EOI; - if (bus_status.value & BSR_NRFD_BIT) - status |= BUS_NRFD; - if (bus_status.value & BSR_NDAC_BIT) - status |= BUS_NDAC; - if (bus_status.value & BSR_DAV_BIT) - status |= BUS_DAV; - if (bus_status.value & BSR_ATN_BIT) - status |= BUS_ATN; - return status; -} - -static unsigned short nanosec_to_fast_talker_bits(unsigned int *nanosec) -{ - static const int nanosec_per_bit = 21; - static const int max_value = 0x72; - static const int min_value = 0x11; - unsigned short bits; - - bits = (*nanosec + nanosec_per_bit / 2) / nanosec_per_bit; - if (bits < min_value) - bits = min_value; - if (bits > max_value) - bits = max_value; - *nanosec = bits * nanosec_per_bit; - return bits; -} - -static int agilent_82357a_t1_delay(struct gpib_board *board, unsigned int nanosec) -{ - struct agilent_82357a_priv *a_priv = board->private_data; - struct usb_device *usb_dev; - struct agilent_82357a_register_pairlet write; - int retval; - - if (!a_priv->bus_interface) - return -ENODEV; - usb_dev = interface_to_usbdev(a_priv->bus_interface); - write.address = FAST_TALKER_T1; - write.value = nanosec_to_fast_talker_bits(&nanosec); - retval = agilent_82357a_write_registers(a_priv, &write, 1); - if (retval) - dev_err(&usb_dev->dev, "write_registers() returned error\n"); - return nanosec; -} - -static void agilent_82357a_interrupt_complete(struct urb *urb) -{ - struct gpib_board *board = urb->context; - struct agilent_82357a_priv *a_priv = board->private_data; - struct usb_device *usb_dev = interface_to_usbdev(a_priv->bus_interface); - int retval; - u8 *transfer_buffer = urb->transfer_buffer; - unsigned long interrupt_flags; - - switch (urb->status) { - /* success */ - case 0: - break; - /* unlinked, don't resubmit */ - case -ECONNRESET: - case -ENOENT: - case -ESHUTDOWN: - return; - default: /* other error, resubmit */ - retval = usb_submit_urb(a_priv->interrupt_urb, GFP_ATOMIC); - if (retval) - dev_err(&usb_dev->dev, "failed to resubmit interrupt urb\n"); - return; - } - - interrupt_flags = transfer_buffer[0]; - if (test_bit(AIF_READ_COMPLETE_BN, &interrupt_flags)) - set_bit(AIF_READ_COMPLETE_BN, &a_priv->interrupt_flags); - if (test_bit(AIF_WRITE_COMPLETE_BN, &interrupt_flags)) - set_bit(AIF_WRITE_COMPLETE_BN, &a_priv->interrupt_flags); - if (test_bit(AIF_SRQ_BN, &interrupt_flags)) - set_bit(SRQI_NUM, &board->status); - - wake_up_interruptible(&board->wait); - - retval = usb_submit_urb(a_priv->interrupt_urb, GFP_ATOMIC); - if (retval) - dev_err(&usb_dev->dev, "failed to resubmit interrupt urb\n"); -} - -static int agilent_82357a_setup_urbs(struct gpib_board *board) -{ - struct agilent_82357a_priv *a_priv = board->private_data; - struct usb_device *usb_dev; - int int_pipe; - int retval; - - retval = mutex_lock_interruptible(&a_priv->interrupt_alloc_lock); - if (retval) - return retval; - if (!a_priv->bus_interface) { - retval = -ENODEV; - goto setup_exit; - } - - a_priv->interrupt_buffer = kmalloc(INTERRUPT_BUF_LEN, GFP_KERNEL); - if (!a_priv->interrupt_buffer) { - retval = -ENOMEM; - goto setup_exit; - } - a_priv->interrupt_urb = usb_alloc_urb(0, GFP_KERNEL); - if (!a_priv->interrupt_urb) { - retval = -ENOMEM; - goto setup_exit; - } - usb_dev = interface_to_usbdev(a_priv->bus_interface); - int_pipe = usb_rcvintpipe(usb_dev, a_priv->interrupt_in_endpoint); - usb_fill_int_urb(a_priv->interrupt_urb, usb_dev, int_pipe, a_priv->interrupt_buffer, - INTERRUPT_BUF_LEN, &agilent_82357a_interrupt_complete, board, 1); - retval = usb_submit_urb(a_priv->interrupt_urb, GFP_KERNEL); - if (retval) { - usb_free_urb(a_priv->interrupt_urb); - a_priv->interrupt_urb = NULL; - dev_err(&usb_dev->dev, "failed to submit first interrupt urb, retval=%i\n", retval); - goto setup_exit; - } - mutex_unlock(&a_priv->interrupt_alloc_lock); - return 0; - -setup_exit: - kfree(a_priv->interrupt_buffer); - mutex_unlock(&a_priv->interrupt_alloc_lock); - return retval; -} - -static void agilent_82357a_cleanup_urbs(struct agilent_82357a_priv *a_priv) -{ - if (a_priv && a_priv->bus_interface) { - if (a_priv->interrupt_urb) - usb_kill_urb(a_priv->interrupt_urb); - if (a_priv->bulk_urb) - usb_kill_urb(a_priv->bulk_urb); - } -}; - -static void agilent_82357a_release_urbs(struct agilent_82357a_priv *a_priv) -{ - if (a_priv) { - usb_free_urb(a_priv->interrupt_urb); - a_priv->interrupt_urb = NULL; - kfree(a_priv->interrupt_buffer); - } -} - -static int agilent_82357a_allocate_private(struct gpib_board *board) -{ - struct agilent_82357a_priv *a_priv; - - board->private_data = kzalloc(sizeof(struct agilent_82357a_priv), GFP_KERNEL); - if (!board->private_data) - return -ENOMEM; - a_priv = board->private_data; - mutex_init(&a_priv->bulk_transfer_lock); - mutex_init(&a_priv->bulk_alloc_lock); - mutex_init(&a_priv->control_alloc_lock); - mutex_init(&a_priv->interrupt_alloc_lock); - return 0; -} - -static void agilent_82357a_free_private(struct gpib_board *board) -{ - kfree(board->private_data); - board->private_data = NULL; -} - -#define INIT_NUM_REG_WRITES 18 -static int agilent_82357a_init(struct gpib_board *board) -{ - struct agilent_82357a_priv *a_priv = board->private_data; - struct usb_device *usb_dev = interface_to_usbdev(a_priv->bus_interface); - struct agilent_82357a_register_pairlet hw_control; - struct agilent_82357a_register_pairlet writes[INIT_NUM_REG_WRITES]; - int retval; - unsigned int nanosec; - - writes[0].address = LED_CONTROL; - writes[0].value = FAIL_LED_ON; - writes[1].address = RESET_TO_POWERUP; - writes[1].value = RESET_SPACEBALL; - retval = agilent_82357a_write_registers(a_priv, writes, 2); - if (retval) { - dev_err(&usb_dev->dev, "write_registers() returned error\n"); - return -EIO; - } - set_current_state(TASK_INTERRUPTIBLE); - if (schedule_timeout(usec_to_jiffies(2000))) - return -ERESTARTSYS; - writes[0].address = AUXCR; - writes[0].value = AUX_NBAF; - writes[1].address = AUXCR; - writes[1].value = AUX_HLDE; - writes[2].address = AUXCR; - writes[2].value = AUX_TON; - writes[3].address = AUXCR; - writes[3].value = AUX_LON; - writes[4].address = AUXCR; - writes[4].value = AUX_RSV2; - writes[5].address = AUXCR; - writes[5].value = AUX_INVAL; - writes[6].address = AUXCR; - writes[6].value = AUX_RPP; - writes[7].address = AUXCR; - writes[7].value = AUX_STDL; - writes[8].address = AUXCR; - writes[8].value = AUX_VSTDL; - writes[9].address = FAST_TALKER_T1; - nanosec = board->t1_nano_sec; - writes[9].value = nanosec_to_fast_talker_bits(&nanosec); - board->t1_nano_sec = nanosec; - writes[10].address = ADR; - writes[10].value = board->pad & ADDRESS_MASK; - writes[11].address = PPR; - writes[11].value = 0; - writes[12].address = SPMR; - writes[12].value = 0; - writes[13].address = PROTOCOL_CONTROL; - writes[13].value = WRITE_COMPLETE_INTERRUPT_EN; - writes[14].address = IMR0; - writes[14].value = HR_BOIE | HR_BIIE; - writes[15].address = IMR1; - writes[15].value = HR_SRQIE; - // turn off reset state - writes[16].address = AUXCR; - writes[16].value = AUX_CHIP_RESET; - writes[17].address = LED_CONTROL; - writes[17].value = FIRMWARE_LED_CONTROL; - retval = agilent_82357a_write_registers(a_priv, writes, INIT_NUM_REG_WRITES); - if (retval) { - dev_err(&usb_dev->dev, "write_registers() returned error\n"); - return -EIO; - } - hw_control.address = HW_CONTROL; - retval = agilent_82357a_read_registers(a_priv, &hw_control, 1, 1); - if (retval) { - dev_err(&usb_dev->dev, "read_registers() returned error\n"); - return -EIO; - } - a_priv->hw_control_bits = (hw_control.value & ~0x7) | NOT_TI_RESET | NOT_PARALLEL_POLL; - - return 0; -} - -static inline int agilent_82357a_device_match(struct usb_interface *interface, - const struct gpib_board_config *config) -{ - struct usb_device * const usbdev = interface_to_usbdev(interface); - - if (gpib_match_device_path(&interface->dev, config->device_path) == 0) - return 0; - if (config->serial_number && - strcmp(usbdev->serial, config->serial_number) != 0) - return 0; - - return 1; -} - -static int agilent_82357a_attach(struct gpib_board *board, const struct gpib_board_config *config) -{ - int retval; - int i; - unsigned int product_id; - struct agilent_82357a_priv *a_priv; - struct usb_device *usb_dev; - - if (mutex_lock_interruptible(&agilent_82357a_hotplug_lock)) - return -ERESTARTSYS; - - retval = agilent_82357a_allocate_private(board); - if (retval < 0) { - mutex_unlock(&agilent_82357a_hotplug_lock); - return retval; - } - a_priv = board->private_data; - for (i = 0; i < MAX_NUM_82357A_INTERFACES; ++i) { - if (agilent_82357a_driver_interfaces[i] && - !usb_get_intfdata(agilent_82357a_driver_interfaces[i]) && - agilent_82357a_device_match(agilent_82357a_driver_interfaces[i], config)) { - a_priv->bus_interface = agilent_82357a_driver_interfaces[i]; - usb_set_intfdata(agilent_82357a_driver_interfaces[i], board); - usb_dev = interface_to_usbdev(a_priv->bus_interface); - break; - } - } - if (i == MAX_NUM_82357A_INTERFACES) { - dev_err(board->gpib_dev, - "No supported adapters found, have you loaded its firmware?\n"); - retval = -ENODEV; - goto attach_fail; - } - product_id = le16_to_cpu(interface_to_usbdev(a_priv->bus_interface)->descriptor.idProduct); - switch (product_id) { - case USB_DEVICE_ID_AGILENT_82357A: - a_priv->bulk_out_endpoint = AGILENT_82357A_BULK_OUT_ENDPOINT; - a_priv->interrupt_in_endpoint = AGILENT_82357A_INTERRUPT_IN_ENDPOINT; - break; - case USB_DEVICE_ID_AGILENT_82357B: - a_priv->bulk_out_endpoint = AGILENT_82357B_BULK_OUT_ENDPOINT; - a_priv->interrupt_in_endpoint = AGILENT_82357B_INTERRUPT_IN_ENDPOINT; - break; - default: - dev_err(&usb_dev->dev, "bug, unhandled product_id in switch?\n"); - retval = -EIO; - goto attach_fail; - } - - retval = agilent_82357a_setup_urbs(board); - if (retval < 0) - goto attach_fail; - - timer_setup(&a_priv->bulk_timer, agilent_82357a_timeout_handler, 0); - - board->t1_nano_sec = 800; - - retval = agilent_82357a_init(board); - - if (retval < 0) { - agilent_82357a_cleanup_urbs(a_priv); - agilent_82357a_release_urbs(a_priv); - goto attach_fail; - } - - dev_info(&usb_dev->dev, "bus %d dev num %d attached to gpib%d, interface %i\n", - usb_dev->bus->busnum, usb_dev->devnum, board->minor, i); - mutex_unlock(&agilent_82357a_hotplug_lock); - return retval; - -attach_fail: - agilent_82357a_free_private(board); - mutex_unlock(&agilent_82357a_hotplug_lock); - return retval; -} - -static int agilent_82357a_go_idle(struct gpib_board *board) -{ - struct agilent_82357a_priv *a_priv = board->private_data; - struct usb_device *usb_dev = interface_to_usbdev(a_priv->bus_interface); - struct agilent_82357a_register_pairlet writes[0x20]; - int retval; - - // turn on tms9914 reset state - writes[0].address = AUXCR; - writes[0].value = AUX_CS | AUX_CHIP_RESET; - a_priv->hw_control_bits &= ~NOT_TI_RESET; - writes[1].address = HW_CONTROL; - writes[1].value = a_priv->hw_control_bits; - writes[2].address = PROTOCOL_CONTROL; - writes[2].value = 0; - writes[3].address = IMR0; - writes[3].value = 0; - writes[4].address = IMR1; - writes[4].value = 0; - writes[5].address = LED_CONTROL; - writes[5].value = 0; - retval = agilent_82357a_write_registers(a_priv, writes, 6); - if (retval) { - dev_err(&usb_dev->dev, "write_registers() returned error\n"); - return -EIO; - } - return 0; -} - -static void agilent_82357a_detach(struct gpib_board *board) -{ - struct agilent_82357a_priv *a_priv; - - mutex_lock(&agilent_82357a_hotplug_lock); - - a_priv = board->private_data; - if (a_priv) { - if (a_priv->bus_interface) { - agilent_82357a_go_idle(board); - usb_set_intfdata(a_priv->bus_interface, NULL); - } - mutex_lock(&a_priv->control_alloc_lock); - mutex_lock(&a_priv->bulk_alloc_lock); - mutex_lock(&a_priv->interrupt_alloc_lock); - agilent_82357a_cleanup_urbs(a_priv); - agilent_82357a_release_urbs(a_priv); - agilent_82357a_free_private(board); - } - mutex_unlock(&agilent_82357a_hotplug_lock); -} - -static struct gpib_interface agilent_82357a_gpib_interface = { - .name = "agilent_82357a", - .attach = agilent_82357a_attach, - .detach = agilent_82357a_detach, - .read = agilent_82357a_read, - .write = agilent_82357a_write, - .command = agilent_82357a_command, - .take_control = agilent_82357a_take_control, - .go_to_standby = agilent_82357a_go_to_standby, - .request_system_control = agilent_82357a_request_system_control, - .interface_clear = agilent_82357a_interface_clear, - .remote_enable = agilent_82357a_remote_enable, - .enable_eos = agilent_82357a_enable_eos, - .disable_eos = agilent_82357a_disable_eos, - .parallel_poll = agilent_82357a_parallel_poll, - .parallel_poll_configure = agilent_82357a_parallel_poll_configure, - .parallel_poll_response = agilent_82357a_parallel_poll_response, - .local_parallel_poll_mode = NULL, // XXX - .line_status = agilent_82357a_line_status, - .update_status = agilent_82357a_update_status, - .primary_address = agilent_82357a_primary_address, - .secondary_address = agilent_82357a_secondary_address, - .serial_poll_response = agilent_82357a_serial_poll_response, - .serial_poll_status = agilent_82357a_serial_poll_status, - .t1_delay = agilent_82357a_t1_delay, - .return_to_local = agilent_82357a_return_to_local, - .no_7_bit_eos = 1, - .skip_check_for_command_acceptors = 1 -}; - -// Table with the USB-devices: just now only testing IDs -static struct usb_device_id agilent_82357a_driver_device_table[] = { - {USB_DEVICE(USB_VENDOR_ID_AGILENT, USB_DEVICE_ID_AGILENT_82357A)}, - {USB_DEVICE(USB_VENDOR_ID_AGILENT, USB_DEVICE_ID_AGILENT_82357B)}, - {} /* Terminating entry */ -}; -MODULE_DEVICE_TABLE(usb, agilent_82357a_driver_device_table); - -static int agilent_82357a_driver_probe(struct usb_interface *interface, - const struct usb_device_id *id) -{ - int i; - char *path; - static const int path_length = 1024; - struct usb_device *usb_dev; - - if (mutex_lock_interruptible(&agilent_82357a_hotplug_lock)) - return -ERESTARTSYS; - usb_dev = usb_get_dev(interface_to_usbdev(interface)); - for (i = 0; i < MAX_NUM_82357A_INTERFACES; ++i) { - if (!agilent_82357a_driver_interfaces[i]) { - agilent_82357a_driver_interfaces[i] = interface; - usb_set_intfdata(interface, NULL); - dev_dbg(&usb_dev->dev, "set bus interface %i to address 0x%p\n", - i, interface); - break; - } - } - if (i == MAX_NUM_82357A_INTERFACES) { - usb_put_dev(usb_dev); - mutex_unlock(&agilent_82357a_hotplug_lock); - dev_err(&usb_dev->dev, "out of space in agilent_82357a_driver_interfaces[]\n"); - return -1; - } - path = kmalloc(path_length, GFP_KERNEL); - if (!path) { - usb_put_dev(usb_dev); - mutex_unlock(&agilent_82357a_hotplug_lock); - return -ENOMEM; - } - usb_make_path(usb_dev, path, path_length); - dev_info(&usb_dev->dev, "probe succeeded for path: %s\n", path); - kfree(path); - mutex_unlock(&agilent_82357a_hotplug_lock); - return 0; -} - -static void agilent_82357a_driver_disconnect(struct usb_interface *interface) -{ - int i; - struct usb_device *usb_dev = interface_to_usbdev(interface); - - mutex_lock(&agilent_82357a_hotplug_lock); - - for (i = 0; i < MAX_NUM_82357A_INTERFACES; ++i) { - if (agilent_82357a_driver_interfaces[i] == interface) { - struct gpib_board *board = usb_get_intfdata(interface); - - if (board) { - struct agilent_82357a_priv *a_priv = board->private_data; - - if (a_priv) { - mutex_lock(&a_priv->control_alloc_lock); - mutex_lock(&a_priv->bulk_alloc_lock); - mutex_lock(&a_priv->interrupt_alloc_lock); - agilent_82357a_cleanup_urbs(a_priv); - a_priv->bus_interface = NULL; - mutex_unlock(&a_priv->interrupt_alloc_lock); - mutex_unlock(&a_priv->bulk_alloc_lock); - mutex_unlock(&a_priv->control_alloc_lock); - } - } - agilent_82357a_driver_interfaces[i] = NULL; - break; - } - } - if (i == MAX_NUM_82357A_INTERFACES) - dev_err(&usb_dev->dev, "unable to find interface - bug?\n"); - usb_put_dev(usb_dev); - - mutex_unlock(&agilent_82357a_hotplug_lock); -} - -static int agilent_82357a_driver_suspend(struct usb_interface *interface, pm_message_t message) -{ - int i, retval; - struct usb_device *usb_dev = interface_to_usbdev(interface); - - mutex_lock(&agilent_82357a_hotplug_lock); - - for (i = 0; i < MAX_NUM_82357A_INTERFACES; ++i) { - if (agilent_82357a_driver_interfaces[i] == interface) { - struct gpib_board *board = usb_get_intfdata(interface); - - if (board) { - struct agilent_82357a_priv *a_priv = board->private_data; - - if (a_priv) { - agilent_82357a_abort(a_priv, 0); - agilent_82357a_abort(a_priv, 0); - retval = agilent_82357a_go_idle(board); - if (retval) { - dev_err(&usb_dev->dev, "failed to go idle, retval=%i\n", - retval); - mutex_unlock(&agilent_82357a_hotplug_lock); - return retval; - } - mutex_lock(&a_priv->interrupt_alloc_lock); - agilent_82357a_cleanup_urbs(a_priv); - mutex_unlock(&a_priv->interrupt_alloc_lock); - dev_dbg(&usb_dev->dev, - "bus %d dev num %d gpib %d, interface %i suspended\n", - usb_dev->bus->busnum, usb_dev->devnum, - board->minor, i); - } - } - break; - } - } - - mutex_unlock(&agilent_82357a_hotplug_lock); - - return 0; -} - -static int agilent_82357a_driver_resume(struct usb_interface *interface) -{ - struct usb_device *usb_dev = interface_to_usbdev(interface); - struct gpib_board *board; - int i, retval = 0; - - mutex_lock(&agilent_82357a_hotplug_lock); - - for (i = 0; i < MAX_NUM_82357A_INTERFACES; ++i) { - if (agilent_82357a_driver_interfaces[i] == interface) { - board = usb_get_intfdata(interface); - if (board) - break; - } - } - if (i == MAX_NUM_82357A_INTERFACES) { - retval = -ENOENT; - goto resume_exit; - } - - struct agilent_82357a_priv *a_priv = board->private_data; - - if (a_priv) { - if (a_priv->interrupt_urb) { - mutex_lock(&a_priv->interrupt_alloc_lock); - retval = usb_submit_urb(a_priv->interrupt_urb, GFP_KERNEL); - if (retval) { - dev_err(&usb_dev->dev, "failed to resubmit interrupt urb in resume, retval=%i\n", - retval); - mutex_unlock(&a_priv->interrupt_alloc_lock); - mutex_unlock(&agilent_82357a_hotplug_lock); - return retval; - } - mutex_unlock(&a_priv->interrupt_alloc_lock); - } - retval = agilent_82357a_init(board); - if (retval < 0) { - mutex_unlock(&agilent_82357a_hotplug_lock); - return retval; - } - // set/unset system controller - retval = agilent_82357a_request_system_control(board, board->master); - // toggle ifc if master - if (board->master) { - agilent_82357a_interface_clear(board, 1); - usleep_range(200, 250); - agilent_82357a_interface_clear(board, 0); - } - // assert/unassert REN - agilent_82357a_remote_enable(board, a_priv->ren_state); - - dev_dbg(&usb_dev->dev, - "bus %d dev num %d gpib%d, interface %i resumed\n", - usb_dev->bus->busnum, usb_dev->devnum, board->minor, i); - } - -resume_exit: - mutex_unlock(&agilent_82357a_hotplug_lock); - - return retval; -} - -static struct usb_driver agilent_82357a_bus_driver = { - .name = DRV_NAME, - .probe = agilent_82357a_driver_probe, - .disconnect = agilent_82357a_driver_disconnect, - .suspend = agilent_82357a_driver_suspend, - .resume = agilent_82357a_driver_resume, - .id_table = agilent_82357a_driver_device_table, -}; - -static int __init agilent_82357a_init_module(void) -{ - int i; - int ret; - - for (i = 0; i < MAX_NUM_82357A_INTERFACES; ++i) - agilent_82357a_driver_interfaces[i] = NULL; - - ret = usb_register(&agilent_82357a_bus_driver); - if (ret) { - pr_err("usb_register failed: error = %d\n", ret); - return ret; - } - - ret = gpib_register_driver(&agilent_82357a_gpib_interface, THIS_MODULE); - if (ret) { - pr_err("gpib_register_driver failed: error = %d\n", ret); - usb_deregister(&agilent_82357a_bus_driver); - return ret; - } - - return 0; -} - -static void __exit agilent_82357a_exit_module(void) -{ - gpib_unregister_driver(&agilent_82357a_gpib_interface); - usb_deregister(&agilent_82357a_bus_driver); -} - -module_init(agilent_82357a_init_module); -module_exit(agilent_82357a_exit_module); diff --git a/drivers/staging/gpib/agilent_82357a/agilent_82357a.h b/drivers/staging/gpib/agilent_82357a/agilent_82357a.h deleted file mode 100644 index 33ac558e5552..000000000000 --- a/drivers/staging/gpib/agilent_82357a/agilent_82357a.h +++ /dev/null @@ -1,182 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -/*************************************************************************** - * copyright : (C) 2004 by Frank Mori Hess * - ***************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include "gpibP.h" -#include "tms9914.h" - -enum usb_vendor_ids { - USB_VENDOR_ID_AGILENT = 0x0957 -}; - -enum usb_device_ids { - USB_DEVICE_ID_AGILENT_82357A = 0x0107, - USB_DEVICE_ID_AGILENT_82357A_PREINIT = 0x0007, // device id before firmware is loaded - USB_DEVICE_ID_AGILENT_82357B = 0x0718, // device id before firmware is loaded - USB_DEVICE_ID_AGILENT_82357B_PREINIT = 0x0518, // device id before firmware is loaded -}; - -enum endpoint_addresses { - AGILENT_82357_CONTROL_ENDPOINT = 0x0, - AGILENT_82357_BULK_IN_ENDPOINT = 0x2, - AGILENT_82357A_BULK_OUT_ENDPOINT = 0x4, - AGILENT_82357A_INTERRUPT_IN_ENDPOINT = 0x6, - AGILENT_82357B_BULK_OUT_ENDPOINT = 0x6, - AGILENT_82357B_INTERRUPT_IN_ENDPOINT = 0x8, -}; - -enum bulk_commands { - DATA_PIPE_CMD_WRITE = 0x1, - DATA_PIPE_CMD_READ = 0x3, - DATA_PIPE_CMD_WR_REGS = 0x4, - DATA_PIPE_CMD_RD_REGS = 0x5 -}; - -enum agilent_82357a_read_flags { - ARF_END_ON_EOI = 0x1, - ARF_NO_ADDRESS = 0x2, - ARF_END_ON_EOS_CHAR = 0x4, - ARF_SPOLL = 0x8 -}; - -enum agilent_82357a_trailing_read_flags { - ATRF_EOI = 0x1, - ATRF_ATN = 0x2, - ATRF_IFC = 0x4, - ATRF_EOS = 0x8, - ATRF_ABORT = 0x10, - ATRF_COUNT = 0x20, - ATRF_DEAD_BUS = 0x40, - ATRF_UNADDRESSED = 0x80 -}; - -enum agilent_82357a_write_flags { - AWF_SEND_EOI = 0x1, - AWF_NO_FAST_TALKER_FIRST_BYTE = 0x2, - AWF_NO_FAST_TALKER = 0x4, - AWF_NO_ADDRESS = 0x8, - AWF_ATN = 0x10, - AWF_SEPARATE_HEADER = 0x80 -}; - -enum agilent_82357a_interrupt_flag_bit_numbers { - AIF_SRQ_BN = 0, - AIF_WRITE_COMPLETE_BN = 1, - AIF_READ_COMPLETE_BN = 2, -}; - -enum agilent_82357_error_codes { - UGP_SUCCESS = 0, - UGP_ERR_INVALID_CMD = 1, - UGP_ERR_INVALID_PARAM = 2, - UGP_ERR_INVALID_REG = 3, - UGP_ERR_GPIB_READ = 4, - UGP_ERR_GPIB_WRITE = 5, - UGP_ERR_FLUSHING = 6, - UGP_ERR_FLUSHING_ALREADY = 7, - UGP_ERR_UNSUPPORTED = 8, - UGP_ERR_OTHER = 9 -}; - -enum agilent_82357_control_values { - XFER_ABORT = 0xa0, - XFER_STATUS = 0xb0, -}; - -enum xfer_status_bits { - XS_COMPLETED = 0x1, - XS_READ = 0x2, -}; - -enum xfer_status_completion_bits { - XSC_EOI = 0x1, - XSC_ATN = 0x2, - XSC_IFC = 0x4, - XSC_EOS = 0x8, - XSC_ABORT = 0x10, - XSC_COUNT = 0x20, - XSC_DEAD_BUS = 0x40, - XSC_BUS_NOT_ADDRESSED = 0x80 -}; - -enum xfer_abort_type { - XA_FLUSH = 0x1 -}; - -#define STATUS_DATA_LEN 8 -#define INTERRUPT_BUF_LEN 8 - -struct agilent_82357a_urb_ctx { - struct completion complete; - unsigned timed_out : 1; -}; - -// struct which defines local data for each 82357 device -struct agilent_82357a_priv { - struct usb_interface *bus_interface; - unsigned short eos_char; - unsigned short eos_mode; - unsigned short hw_control_bits; - unsigned long interrupt_flags; - struct urb *bulk_urb; - struct urb *interrupt_urb; - u8 *interrupt_buffer; - struct mutex bulk_transfer_lock; // bulk transfer lock - struct mutex bulk_alloc_lock; // bulk transfer allocation lock - struct mutex interrupt_alloc_lock; // interrupt allocation lock - struct mutex control_alloc_lock; // control message allocation lock - struct timer_list bulk_timer; - struct agilent_82357a_urb_ctx context; - unsigned int bulk_out_endpoint; - unsigned int interrupt_in_endpoint; - unsigned is_cic : 1; - unsigned ren_state : 1; -}; - -struct agilent_82357a_register_pairlet { - short address; - unsigned short value; -}; - -enum firmware_registers { - HW_CONTROL = 0xa, - LED_CONTROL = 0xb, - RESET_TO_POWERUP = 0xc, - PROTOCOL_CONTROL = 0xd, - FAST_TALKER_T1 = 0xe -}; - -enum hardware_control_bits { - NOT_TI_RESET = 0x1, - SYSTEM_CONTROLLER = 0x2, - NOT_PARALLEL_POLL = 0x4, - OSCILLATOR_5V_ON = 0x8, - OUTPUT_5V_ON = 0x20, - CPLD_3V_ON = 0x80, -}; - -enum led_control_bits { - FIRMWARE_LED_CONTROL = 0x1, - FAIL_LED_ON = 0x20, - READY_LED_ON = 0x40, - ACCESS_LED_ON = 0x80 -}; - -enum reset_to_powerup_bits { - RESET_SPACEBALL = 0x1, // wait 2 millisec after sending -}; - -enum protocol_control_bits { - WRITE_COMPLETE_INTERRUPT_EN = 0x1, -}; - -static const int agilent_82357a_control_request = 0x4; - diff --git a/drivers/staging/gpib/cb7210/Makefile b/drivers/staging/gpib/cb7210/Makefile deleted file mode 100644 index d239ae80b415..000000000000 --- a/drivers/staging/gpib/cb7210/Makefile +++ /dev/null @@ -1,3 +0,0 @@ -obj-$(CONFIG_GPIB_CB7210) += cb7210.o - - diff --git a/drivers/staging/gpib/cb7210/cb7210.c b/drivers/staging/gpib/cb7210/cb7210.c deleted file mode 100644 index 3e2397898a9b..000000000000 --- a/drivers/staging/gpib/cb7210/cb7210.c +++ /dev/null @@ -1,1598 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 - -/*************************************************************************** - * Measurement Computing boards using cb7210.2 and cbi488.2 chips - * copyright : (C) 2001, 2002 by Frank Mori Hess - ***************************************************************************/ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -#define dev_fmt pr_fmt -#define DRV_NAME KBUILD_MODNAME - -#include "cb7210.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "gpib_pci_ids.h" -#include "quancom_pci.h" - -MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("GPIB driver Measurement Computing boards using cb7210.2 and cbi488.2"); - -static int cb7210_read(struct gpib_board *board, u8 *buffer, size_t length, - int *end, size_t *bytes_read); - - static inline int have_fifo_word(const struct cb7210_priv *cb_priv) -{ - if (((cb7210_read_byte(cb_priv, HS_STATUS)) & - (HS_RX_MSB_NOT_EMPTY | HS_RX_LSB_NOT_EMPTY)) == - (HS_RX_MSB_NOT_EMPTY | HS_RX_LSB_NOT_EMPTY)) - return 1; - else - return 0; -} - -static inline void input_fifo_enable(struct gpib_board *board, int enable) -{ - struct cb7210_priv *cb_priv = board->private_data; - struct nec7210_priv *nec_priv = &cb_priv->nec7210_priv; - unsigned long flags; - - spin_lock_irqsave(&board->spinlock, flags); - - if (enable) { - cb_priv->in_fifo_half_full = 0; - nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAI, 0); - - cb7210_write_byte(cb_priv, HS_RX_ENABLE | HS_TX_ENABLE | HS_CLR_SRQ_INT | - HS_CLR_EOI_EMPTY_INT | HS_CLR_HF_INT | cb_priv->hs_mode_bits, - HS_MODE); - - cb_priv->hs_mode_bits &= ~HS_ENABLE_MASK; - cb7210_write_byte(cb_priv, cb_priv->hs_mode_bits, HS_MODE); - - cb7210_write_byte(cb_priv, irq_bits(cb_priv->irq), HS_INT_LEVEL); - - cb_priv->hs_mode_bits |= HS_RX_ENABLE; - cb7210_write_byte(cb_priv, cb_priv->hs_mode_bits, HS_MODE); - } else { - nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAI, 0); - - cb_priv->hs_mode_bits &= ~HS_ENABLE_MASK; - cb7210_write_byte(cb_priv, cb_priv->hs_mode_bits, nec7210_iobase(cb_priv) + - HS_MODE); - - clear_bit(READ_READY_BN, &nec_priv->state); - } - - spin_unlock_irqrestore(&board->spinlock, flags); -} - -static int fifo_read(struct gpib_board *board, struct cb7210_priv *cb_priv, u8 *buffer, - size_t length, int *end, size_t *bytes_read) -{ - ssize_t retval = 0; - struct nec7210_priv *nec_priv = &cb_priv->nec7210_priv; - int hs_status; - u16 word; - unsigned long flags; - - *bytes_read = 0; - if (cb_priv->fifo_iobase == 0) { - dev_err(board->gpib_dev, "fifo iobase is zero!\n"); - return -EIO; - } - *end = 0; - if (length <= cb7210_fifo_size) { - dev_err(board->gpib_dev, " bug! fifo read length < fifo size\n"); - return -EINVAL; - } - - input_fifo_enable(board, 1); - - while (*bytes_read + cb7210_fifo_size < length) { - nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAI, HR_DMAI); - - if (wait_event_interruptible(board->wait, - (cb_priv->in_fifo_half_full && - have_fifo_word(cb_priv)) || - test_bit(RECEIVED_END_BN, &nec_priv->state) || - test_bit(DEV_CLEAR_BN, &nec_priv->state) || - test_bit(TIMO_NUM, &board->status))) { - retval = -ERESTARTSYS; - nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAI, 0); - break; - } - - spin_lock_irqsave(&board->spinlock, flags); - - nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAI, 0); - - while (have_fifo_word(cb_priv)) { - word = inw(cb_priv->fifo_iobase + DIR); - buffer[(*bytes_read)++] = word & 0xff; - buffer[(*bytes_read)++] = (word >> 8) & 0xff; - } - - cb_priv->in_fifo_half_full = 0; - - hs_status = cb7210_read_byte(cb_priv, HS_STATUS); - - spin_unlock_irqrestore(&board->spinlock, flags); - - if (test_and_clear_bit(RECEIVED_END_BN, &nec_priv->state)) { - *end = 1; - break; - } - if (hs_status & HS_FIFO_FULL) - break; - if (test_bit(TIMO_NUM, &board->status)) { - retval = -ETIMEDOUT; - break; - } - if (test_bit(DEV_CLEAR_BN, &nec_priv->state)) { - retval = -EINTR; - break; - } - } - hs_status = cb7210_read_byte(cb_priv, HS_STATUS); - if (hs_status & HS_RX_LSB_NOT_EMPTY) { - word = inw(cb_priv->fifo_iobase + DIR); - buffer[(*bytes_read)++] = word & 0xff; - } - - input_fifo_enable(board, 0); - - if (wait_event_interruptible(board->wait, - test_bit(READ_READY_BN, &nec_priv->state) || - test_bit(RECEIVED_END_BN, &nec_priv->state) || - test_bit(DEV_CLEAR_BN, &nec_priv->state) || - test_bit(TIMO_NUM, &board->status))) { - retval = -ERESTARTSYS; - } - if (test_bit(TIMO_NUM, &board->status)) - retval = -ETIMEDOUT; - if (test_bit(DEV_CLEAR_BN, &nec_priv->state)) - retval = -EINTR; - if (test_bit(READ_READY_BN, &nec_priv->state)) { - nec7210_set_handshake_mode(board, nec_priv, HR_HLDA); - buffer[(*bytes_read)++] = nec7210_read_data_in(board, nec_priv, end); - } - - return retval; -} - -static int cb7210_accel_read(struct gpib_board *board, u8 *buffer, - size_t length, int *end, size_t *bytes_read) -{ - ssize_t retval; - struct cb7210_priv *cb_priv = board->private_data; - struct nec7210_priv *nec_priv = &cb_priv->nec7210_priv; - size_t num_bytes; - - *bytes_read = 0; - // deal with limitations of fifo - if (length < cb7210_fifo_size + 3 || (nec_priv->auxa_bits & HR_REOS)) - return cb7210_read(board, buffer, length, end, bytes_read); - *end = 0; - - nec7210_release_rfd_holdoff(board, nec_priv); - - if (wait_event_interruptible(board->wait, - test_bit(READ_READY_BN, &nec_priv->state) || - test_bit(DEV_CLEAR_BN, &nec_priv->state) || - test_bit(TIMO_NUM, &board->status))) { - return -ERESTARTSYS; - } - if (test_bit(TIMO_NUM, &board->status)) - return -ETIMEDOUT; - if (test_bit(DEV_CLEAR_BN, &nec_priv->state)) - return -EINTR; - - nec7210_set_handshake_mode(board, nec_priv, HR_HLDE); - buffer[(*bytes_read)++] = nec7210_read_data_in(board, nec_priv, end); - if (*end) - return 0; - - nec7210_release_rfd_holdoff(board, nec_priv); - - retval = fifo_read(board, cb_priv, &buffer[*bytes_read], length - *bytes_read - 1, - end, &num_bytes); - *bytes_read += num_bytes; - if (retval < 0) - return retval; - if (*end) - return 0; - - retval = cb7210_read(board, &buffer[*bytes_read], 1, end, &num_bytes); - *bytes_read += num_bytes; - if (retval < 0) - return retval; - - return 0; -} - -static int output_fifo_empty(const struct cb7210_priv *cb_priv) -{ - if ((cb7210_read_byte(cb_priv, HS_STATUS) & (HS_TX_MSB_NOT_EMPTY | HS_TX_LSB_NOT_EMPTY)) - == 0) - return 1; - else - return 0; -} - -static inline void output_fifo_enable(struct gpib_board *board, int enable) -{ - struct cb7210_priv *cb_priv = board->private_data; - struct nec7210_priv *nec_priv = &cb_priv->nec7210_priv; - unsigned long flags; - - spin_lock_irqsave(&board->spinlock, flags); - - if (enable) { - nec7210_set_reg_bits(nec_priv, IMR1, HR_DOIE, 0); - nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAO, HR_DMAO); - - cb7210_write_byte(cb_priv, HS_RX_ENABLE | HS_TX_ENABLE | HS_CLR_SRQ_INT | - HS_CLR_EOI_EMPTY_INT | HS_CLR_HF_INT | cb_priv->hs_mode_bits, - HS_MODE); - - cb_priv->hs_mode_bits &= ~HS_ENABLE_MASK; - cb_priv->hs_mode_bits |= HS_TX_ENABLE; - cb7210_write_byte(cb_priv, cb_priv->hs_mode_bits, HS_MODE); - - cb7210_write_byte(cb_priv, irq_bits(cb_priv->irq), HS_INT_LEVEL); - - clear_bit(WRITE_READY_BN, &nec_priv->state); - - } else { - cb_priv->hs_mode_bits &= ~HS_ENABLE_MASK; - cb7210_write_byte(cb_priv, cb_priv->hs_mode_bits, HS_MODE); - - nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAO, 0); - nec7210_set_reg_bits(nec_priv, IMR1, HR_DOIE, HR_DOIE); - } - - spin_unlock_irqrestore(&board->spinlock, flags); -} - -static int fifo_write(struct gpib_board *board, u8 *buffer, size_t length, - size_t *bytes_written) -{ - size_t count = 0; - ssize_t retval = 0; - struct cb7210_priv *cb_priv = board->private_data; - struct nec7210_priv *nec_priv = &cb_priv->nec7210_priv; - unsigned int num_bytes, i; - unsigned long flags; - - *bytes_written = 0; - if (cb_priv->fifo_iobase == 0) { - dev_err(board->gpib_dev, "fifo iobase is zero!\n"); - return -EINVAL; - } - if (length == 0) - return 0; - - clear_bit(DEV_CLEAR_BN, &nec_priv->state); - clear_bit(BUS_ERROR_BN, &nec_priv->state); - - output_fifo_enable(board, 1); - - while (count < length) { - // wait until byte is ready to be sent - if (wait_event_interruptible(board->wait, - cb_priv->out_fifo_half_empty || - output_fifo_empty(cb_priv) || - test_bit(DEV_CLEAR_BN, &nec_priv->state) || - test_bit(BUS_ERROR_BN, &nec_priv->state) || - test_bit(TIMO_NUM, &board->status))) { - retval = -ERESTARTSYS; - break; - } - if (test_bit(TIMO_NUM, &board->status) || - test_bit(DEV_CLEAR_BN, &nec_priv->state) || - test_bit(BUS_ERROR_BN, &nec_priv->state)) - break; - - if (output_fifo_empty(cb_priv)) - num_bytes = cb7210_fifo_size - cb7210_fifo_width; - else - num_bytes = cb7210_fifo_size / 2; - if (num_bytes + count > length) - num_bytes = length - count; - if (num_bytes % cb7210_fifo_width) { - dev_err(board->gpib_dev, " bug! fifo write with odd number of bytes\n"); - retval = -EINVAL; - break; - } - - spin_lock_irqsave(&board->spinlock, flags); - for (i = 0; i < num_bytes / cb7210_fifo_width; i++) { - u16 word; - - word = buffer[count++] & 0xff; - word |= (buffer[count++] << 8) & 0xff00; - outw(word, cb_priv->fifo_iobase + CDOR); - } - cb_priv->out_fifo_half_empty = 0; - cb7210_write_byte(cb_priv, cb_priv->hs_mode_bits | - HS_CLR_EOI_EMPTY_INT | HS_CLR_HF_INT, HS_MODE); - cb7210_write_byte(cb_priv, cb_priv->hs_mode_bits, HS_MODE); - spin_unlock_irqrestore(&board->spinlock, flags); - } - // wait last byte has been sent - if (wait_event_interruptible(board->wait, - output_fifo_empty(cb_priv) || - test_bit(DEV_CLEAR_BN, &nec_priv->state) || - test_bit(BUS_ERROR_BN, &nec_priv->state) || - test_bit(TIMO_NUM, &board->status))) { - retval = -ERESTARTSYS; - } - if (test_bit(TIMO_NUM, &board->status)) - retval = -ETIMEDOUT; - if (test_bit(BUS_ERROR_BN, &nec_priv->state)) - retval = -EIO; - if (test_bit(DEV_CLEAR_BN, &nec_priv->state)) - retval = -EINTR; - - output_fifo_enable(board, 0); - - *bytes_written = count; - return retval; -} - -static int cb7210_accel_write(struct gpib_board *board, u8 *buffer, - size_t length, int send_eoi, size_t *bytes_written) -{ - struct cb7210_priv *cb_priv = board->private_data; - struct nec7210_priv *nec_priv = &cb_priv->nec7210_priv; - unsigned long fast_chunk_size, leftover; - int retval; - size_t num_bytes; - - *bytes_written = 0; - if (length > cb7210_fifo_width) - fast_chunk_size = length - 1; - else - fast_chunk_size = 0; - fast_chunk_size -= fast_chunk_size % cb7210_fifo_width; - leftover = length - fast_chunk_size; - - retval = fifo_write(board, buffer, fast_chunk_size, &num_bytes); - *bytes_written += num_bytes; - if (retval < 0) - return retval; - - retval = nec7210_write(board, nec_priv, buffer + fast_chunk_size, leftover, - send_eoi, &num_bytes); - *bytes_written += num_bytes; - return retval; -} - -static int cb7210_line_status(const struct gpib_board *board) -{ - int status = VALID_ALL; - int bsr_bits; - struct cb7210_priv *cb_priv; - - cb_priv = board->private_data; - - bsr_bits = cb7210_paged_read_byte(cb_priv, BUS_STATUS, BUS_STATUS_PAGE); - - if ((bsr_bits & BSR_REN_BIT) == 0) - status |= BUS_REN; - if ((bsr_bits & BSR_IFC_BIT) == 0) - status |= BUS_IFC; - if ((bsr_bits & BSR_SRQ_BIT) == 0) - status |= BUS_SRQ; - if ((bsr_bits & BSR_EOI_BIT) == 0) - status |= BUS_EOI; - if ((bsr_bits & BSR_NRFD_BIT) == 0) - status |= BUS_NRFD; - if ((bsr_bits & BSR_NDAC_BIT) == 0) - status |= BUS_NDAC; - if ((bsr_bits & BSR_DAV_BIT) == 0) - status |= BUS_DAV; - if ((bsr_bits & BSR_ATN_BIT) == 0) - status |= BUS_ATN; - - return status; -} - -static int cb7210_t1_delay(struct gpib_board *board, unsigned int nano_sec) -{ - struct cb7210_priv *cb_priv = board->private_data; - struct nec7210_priv *nec_priv = &cb_priv->nec7210_priv; - unsigned int retval; - - retval = nec7210_t1_delay(board, nec_priv, nano_sec); - - if (nano_sec <= 350) { - write_byte(nec_priv, AUX_HI_SPEED, AUXMR); - retval = 350; - } else { - write_byte(nec_priv, AUX_LO_SPEED, AUXMR); - } - return retval; -} - -static irqreturn_t cb7210_locked_internal_interrupt(struct gpib_board *board); - -/* - * GPIB interrupt service routines - */ - -static irqreturn_t cb_pci_interrupt(int irq, void *arg) -{ - int bits; - struct gpib_board *board = arg; - struct cb7210_priv *priv = board->private_data; - - // first task check if this is really our interrupt in a shared irq environment - switch (priv->pci_chip) { - case PCI_CHIP_AMCC_S5933: - if ((inl(priv->amcc_iobase + INTCSR_REG) & - (INBOX_INTR_CS_BIT | INTR_ASSERTED_BIT)) == 0) - return IRQ_NONE; - - // read incoming mailbox to clear mailbox full flag - inl(priv->amcc_iobase + INCOMING_MAILBOX_REG(3)); - // clear amccs5933 interrupt - bits = INBOX_FULL_INTR_BIT | INBOX_BYTE_BITS(3) | - INBOX_SELECT_BITS(3) | INBOX_INTR_CS_BIT; - outl(bits, priv->amcc_iobase + INTCSR_REG); - break; - case PCI_CHIP_QUANCOM: - if ((inb(nec7210_iobase(priv) + QUANCOM_IRQ_CONTROL_STATUS_REG) & - QUANCOM_IRQ_ASSERTED_BIT)) - outb(QUANCOM_IRQ_ENABLE_BIT, nec7210_iobase(priv) + - QUANCOM_IRQ_CONTROL_STATUS_REG); - break; - default: - break; - } - return cb7210_locked_internal_interrupt(arg); -} - -static irqreturn_t cb7210_internal_interrupt(struct gpib_board *board) -{ - int hs_status, status1, status2; - struct cb7210_priv *priv = board->private_data; - struct nec7210_priv *nec_priv = &priv->nec7210_priv; - int clear_bits; - - if ((priv->hs_mode_bits & HS_ENABLE_MASK)) { - status1 = 0; - hs_status = cb7210_read_byte(priv, HS_STATUS); - } else { - hs_status = 0; - status1 = read_byte(nec_priv, ISR1); - } - status2 = read_byte(nec_priv, ISR2); - nec7210_interrupt_have_status(board, nec_priv, status1, status2); - - dev_dbg(board->gpib_dev, "status 0x%x, mode 0x%x\n", hs_status, priv->hs_mode_bits); - - clear_bits = 0; - - if (hs_status & HS_HALF_FULL) { - if (priv->hs_mode_bits & HS_TX_ENABLE) - priv->out_fifo_half_empty = 1; - else if (priv->hs_mode_bits & HS_RX_ENABLE) - priv->in_fifo_half_full = 1; - clear_bits |= HS_CLR_HF_INT; - } - - if (hs_status & HS_SRQ_INT) { - set_bit(SRQI_NUM, &board->status); - clear_bits |= HS_CLR_SRQ_INT; - } - - if ((hs_status & HS_EOI_INT)) { - clear_bits |= HS_CLR_EOI_EMPTY_INT; - set_bit(RECEIVED_END_BN, &nec_priv->state); - if ((nec_priv->auxa_bits & HR_HANDSHAKE_MASK) == HR_HLDE) - set_bit(RFD_HOLDOFF_BN, &nec_priv->state); - } - - if ((priv->hs_mode_bits & HS_TX_ENABLE) && - (hs_status & (HS_TX_MSB_NOT_EMPTY | HS_TX_LSB_NOT_EMPTY)) == 0) - clear_bits |= HS_CLR_EOI_EMPTY_INT; - - if (clear_bits) { - cb7210_write_byte(priv, priv->hs_mode_bits | clear_bits, HS_MODE); - cb7210_write_byte(priv, priv->hs_mode_bits, HS_MODE); - wake_up_interruptible(&board->wait); - } - - return IRQ_HANDLED; -} - -static irqreturn_t cb7210_locked_internal_interrupt(struct gpib_board *board) -{ - unsigned long flags; - irqreturn_t retval; - - spin_lock_irqsave(&board->spinlock, flags); - retval = cb7210_internal_interrupt(board); - spin_unlock_irqrestore(&board->spinlock, flags); - return retval; -} - -static irqreturn_t cb7210_interrupt(int irq, void *arg) -{ - return cb7210_internal_interrupt(arg); -} - -static int cb_pci_attach(struct gpib_board *board, const struct gpib_board_config *config); -static int cb_isa_attach(struct gpib_board *board, const struct gpib_board_config *config); - -static void cb_pci_detach(struct gpib_board *board); -static void cb_isa_detach(struct gpib_board *board); - -// wrappers for interface functions -static int cb7210_read(struct gpib_board *board, u8 *buffer, size_t length, - int *end, size_t *bytes_read) -{ - struct cb7210_priv *priv = board->private_data; - - return nec7210_read(board, &priv->nec7210_priv, buffer, length, end, bytes_read); -} - -static int cb7210_write(struct gpib_board *board, u8 *buffer, size_t length, - int send_eoi, size_t *bytes_written) -{ - struct cb7210_priv *priv = board->private_data; - - return nec7210_write(board, &priv->nec7210_priv, buffer, length, send_eoi, bytes_written); -} - -static int cb7210_command(struct gpib_board *board, u8 *buffer, size_t length, - size_t *bytes_written) -{ - struct cb7210_priv *priv = board->private_data; - - return nec7210_command(board, &priv->nec7210_priv, buffer, length, bytes_written); -} - -static int cb7210_take_control(struct gpib_board *board, int synchronous) -{ - struct cb7210_priv *priv = board->private_data; - - return nec7210_take_control(board, &priv->nec7210_priv, synchronous); -} - -static int cb7210_go_to_standby(struct gpib_board *board) -{ - struct cb7210_priv *priv = board->private_data; - - return nec7210_go_to_standby(board, &priv->nec7210_priv); -} - -static int cb7210_request_system_control(struct gpib_board *board, int request_control) -{ - struct cb7210_priv *priv = board->private_data; - struct nec7210_priv *nec_priv = &priv->nec7210_priv; - - if (request_control) - priv->hs_mode_bits |= HS_SYS_CONTROL; - else - priv->hs_mode_bits &= ~HS_SYS_CONTROL; - - cb7210_write_byte(priv, priv->hs_mode_bits, HS_MODE); - return nec7210_request_system_control(board, nec_priv, request_control); -} - -static void cb7210_interface_clear(struct gpib_board *board, int assert) -{ - struct cb7210_priv *priv = board->private_data; - - nec7210_interface_clear(board, &priv->nec7210_priv, assert); -} - -static void cb7210_remote_enable(struct gpib_board *board, int enable) -{ - struct cb7210_priv *priv = board->private_data; - - nec7210_remote_enable(board, &priv->nec7210_priv, enable); -} - -static int cb7210_enable_eos(struct gpib_board *board, u8 eos_byte, int compare_8_bits) -{ - struct cb7210_priv *priv = board->private_data; - - return nec7210_enable_eos(board, &priv->nec7210_priv, eos_byte, compare_8_bits); -} - -static void cb7210_disable_eos(struct gpib_board *board) -{ - struct cb7210_priv *priv = board->private_data; - - nec7210_disable_eos(board, &priv->nec7210_priv); -} - -static unsigned int cb7210_update_status(struct gpib_board *board, unsigned int clear_mask) -{ - struct cb7210_priv *priv = board->private_data; - - return nec7210_update_status(board, &priv->nec7210_priv, clear_mask); -} - -static int cb7210_primary_address(struct gpib_board *board, unsigned int address) -{ - struct cb7210_priv *priv = board->private_data; - - return nec7210_primary_address(board, &priv->nec7210_priv, address); -} - -static int cb7210_secondary_address(struct gpib_board *board, unsigned int address, int enable) -{ - struct cb7210_priv *priv = board->private_data; - - return nec7210_secondary_address(board, &priv->nec7210_priv, address, enable); -} - -static int cb7210_parallel_poll(struct gpib_board *board, u8 *result) -{ - struct cb7210_priv *priv = board->private_data; - - return nec7210_parallel_poll(board, &priv->nec7210_priv, result); -} - -static void cb7210_parallel_poll_configure(struct gpib_board *board, u8 configuration) -{ - struct cb7210_priv *priv = board->private_data; - - nec7210_parallel_poll_configure(board, &priv->nec7210_priv, configuration); -} - -static void cb7210_parallel_poll_response(struct gpib_board *board, int ist) -{ - struct cb7210_priv *priv = board->private_data; - - nec7210_parallel_poll_response(board, &priv->nec7210_priv, ist); -} - -static void cb7210_serial_poll_response(struct gpib_board *board, u8 status) -{ - struct cb7210_priv *priv = board->private_data; - - nec7210_serial_poll_response(board, &priv->nec7210_priv, status); -} - -static u8 cb7210_serial_poll_status(struct gpib_board *board) -{ - struct cb7210_priv *priv = board->private_data; - - return nec7210_serial_poll_status(board, &priv->nec7210_priv); -} - -static void cb7210_return_to_local(struct gpib_board *board) -{ - struct cb7210_priv *priv = board->private_data; - struct nec7210_priv *nec_priv = &priv->nec7210_priv; - - write_byte(nec_priv, AUX_RTL2, AUXMR); - udelay(1); - write_byte(nec_priv, AUX_RTL, AUXMR); -} - -static struct gpib_interface cb_pci_unaccel_interface = { - .name = "cbi_pci_unaccel", - .attach = cb_pci_attach, - .detach = cb_pci_detach, - .read = cb7210_read, - .write = cb7210_write, - .command = cb7210_command, - .take_control = cb7210_take_control, - .go_to_standby = cb7210_go_to_standby, - .request_system_control = cb7210_request_system_control, - .interface_clear = cb7210_interface_clear, - .remote_enable = cb7210_remote_enable, - .enable_eos = cb7210_enable_eos, - .disable_eos = cb7210_disable_eos, - .parallel_poll = cb7210_parallel_poll, - .parallel_poll_configure = cb7210_parallel_poll_configure, - .parallel_poll_response = cb7210_parallel_poll_response, - .local_parallel_poll_mode = NULL, // XXX - .line_status = cb7210_line_status, - .update_status = cb7210_update_status, - .primary_address = cb7210_primary_address, - .secondary_address = cb7210_secondary_address, - .serial_poll_response = cb7210_serial_poll_response, - .serial_poll_status = cb7210_serial_poll_status, - .t1_delay = cb7210_t1_delay, - .return_to_local = cb7210_return_to_local, -}; - -static struct gpib_interface cb_pci_accel_interface = { - .name = "cbi_pci_accel", - .attach = cb_pci_attach, - .detach = cb_pci_detach, - .read = cb7210_accel_read, - .write = cb7210_accel_write, - .command = cb7210_command, - .take_control = cb7210_take_control, - .go_to_standby = cb7210_go_to_standby, - .request_system_control = cb7210_request_system_control, - .interface_clear = cb7210_interface_clear, - .remote_enable = cb7210_remote_enable, - .enable_eos = cb7210_enable_eos, - .disable_eos = cb7210_disable_eos, - .parallel_poll = cb7210_parallel_poll, - .parallel_poll_configure = cb7210_parallel_poll_configure, - .parallel_poll_response = cb7210_parallel_poll_response, - .local_parallel_poll_mode = NULL, // XXX - .line_status = cb7210_line_status, - .update_status = cb7210_update_status, - .primary_address = cb7210_primary_address, - .secondary_address = cb7210_secondary_address, - .serial_poll_response = cb7210_serial_poll_response, - .serial_poll_status = cb7210_serial_poll_status, - .t1_delay = cb7210_t1_delay, - .return_to_local = cb7210_return_to_local, -}; - -static struct gpib_interface cb_pci_interface = { - .name = "cbi_pci", - .attach = cb_pci_attach, - .detach = cb_pci_detach, - .read = cb7210_accel_read, - .write = cb7210_accel_write, - .command = cb7210_command, - .take_control = cb7210_take_control, - .go_to_standby = cb7210_go_to_standby, - .request_system_control = cb7210_request_system_control, - .interface_clear = cb7210_interface_clear, - .remote_enable = cb7210_remote_enable, - .enable_eos = cb7210_enable_eos, - .disable_eos = cb7210_disable_eos, - .parallel_poll = cb7210_parallel_poll, - .parallel_poll_configure = cb7210_parallel_poll_configure, - .parallel_poll_response = cb7210_parallel_poll_response, - .line_status = cb7210_line_status, - .update_status = cb7210_update_status, - .primary_address = cb7210_primary_address, - .secondary_address = cb7210_secondary_address, - .serial_poll_response = cb7210_serial_poll_response, - .serial_poll_status = cb7210_serial_poll_status, - .t1_delay = cb7210_t1_delay, - .return_to_local = cb7210_return_to_local, -}; - -static struct gpib_interface cb_isa_unaccel_interface = { - .name = "cbi_isa_unaccel", - .attach = cb_isa_attach, - .detach = cb_isa_detach, - .read = cb7210_read, - .write = cb7210_write, - .command = cb7210_command, - .take_control = cb7210_take_control, - .go_to_standby = cb7210_go_to_standby, - .request_system_control = cb7210_request_system_control, - .interface_clear = cb7210_interface_clear, - .remote_enable = cb7210_remote_enable, - .enable_eos = cb7210_enable_eos, - .disable_eos = cb7210_disable_eos, - .parallel_poll = cb7210_parallel_poll, - .parallel_poll_configure = cb7210_parallel_poll_configure, - .parallel_poll_response = cb7210_parallel_poll_response, - .local_parallel_poll_mode = NULL, // XXX - .line_status = cb7210_line_status, - .update_status = cb7210_update_status, - .primary_address = cb7210_primary_address, - .secondary_address = cb7210_secondary_address, - .serial_poll_response = cb7210_serial_poll_response, - .serial_poll_status = cb7210_serial_poll_status, - .t1_delay = cb7210_t1_delay, - .return_to_local = cb7210_return_to_local, -}; - -static struct gpib_interface cb_isa_interface = { - .name = "cbi_isa", - .attach = cb_isa_attach, - .detach = cb_isa_detach, - .read = cb7210_accel_read, - .write = cb7210_accel_write, - .command = cb7210_command, - .take_control = cb7210_take_control, - .go_to_standby = cb7210_go_to_standby, - .request_system_control = cb7210_request_system_control, - .interface_clear = cb7210_interface_clear, - .remote_enable = cb7210_remote_enable, - .enable_eos = cb7210_enable_eos, - .disable_eos = cb7210_disable_eos, - .parallel_poll = cb7210_parallel_poll, - .parallel_poll_configure = cb7210_parallel_poll_configure, - .parallel_poll_response = cb7210_parallel_poll_response, - .line_status = cb7210_line_status, - .update_status = cb7210_update_status, - .primary_address = cb7210_primary_address, - .secondary_address = cb7210_secondary_address, - .serial_poll_response = cb7210_serial_poll_response, - .serial_poll_status = cb7210_serial_poll_status, - .t1_delay = cb7210_t1_delay, - .return_to_local = cb7210_return_to_local, -}; - -static struct gpib_interface cb_isa_accel_interface = { - .name = "cbi_isa_accel", - .attach = cb_isa_attach, - .detach = cb_isa_detach, - .read = cb7210_accel_read, - .write = cb7210_accel_write, - .command = cb7210_command, - .take_control = cb7210_take_control, - .go_to_standby = cb7210_go_to_standby, - .request_system_control = cb7210_request_system_control, - .interface_clear = cb7210_interface_clear, - .remote_enable = cb7210_remote_enable, - .enable_eos = cb7210_enable_eos, - .disable_eos = cb7210_disable_eos, - .parallel_poll = cb7210_parallel_poll, - .parallel_poll_configure = cb7210_parallel_poll_configure, - .parallel_poll_response = cb7210_parallel_poll_response, - .local_parallel_poll_mode = NULL, // XXX - .line_status = cb7210_line_status, - .update_status = cb7210_update_status, - .primary_address = cb7210_primary_address, - .secondary_address = cb7210_secondary_address, - .serial_poll_response = cb7210_serial_poll_response, - .serial_poll_status = cb7210_serial_poll_status, - .t1_delay = cb7210_t1_delay, - .return_to_local = cb7210_return_to_local, -}; - -static int cb7210_allocate_private(struct gpib_board *board) -{ - struct cb7210_priv *priv; - - board->private_data = kmalloc(sizeof(struct cb7210_priv), GFP_KERNEL); - if (!board->private_data) - return -ENOMEM; - priv = board->private_data; - memset(priv, 0, sizeof(struct cb7210_priv)); - init_nec7210_private(&priv->nec7210_priv); - return 0; -} - -static void cb7210_generic_detach(struct gpib_board *board) -{ - kfree(board->private_data); - board->private_data = NULL; -} - -// generic part of attach functions shared by all cb7210 boards -static int cb7210_generic_attach(struct gpib_board *board) -{ - struct cb7210_priv *cb_priv; - struct nec7210_priv *nec_priv; - - board->status = 0; - - if (cb7210_allocate_private(board)) - return -ENOMEM; - cb_priv = board->private_data; - nec_priv = &cb_priv->nec7210_priv; - nec_priv->read_byte = nec7210_locking_ioport_read_byte; - nec_priv->write_byte = nec7210_locking_ioport_write_byte; - nec_priv->offset = cb7210_reg_offset; - nec_priv->type = CB7210; - return 0; -} - -static int cb7210_init(struct cb7210_priv *cb_priv, struct gpib_board *board) -{ - struct nec7210_priv *nec_priv = &cb_priv->nec7210_priv; - - cb7210_write_byte(cb_priv, HS_RESET7210, HS_INT_LEVEL); - cb7210_write_byte(cb_priv, irq_bits(cb_priv->irq), HS_INT_LEVEL); - - nec7210_board_reset(nec_priv, board); - cb7210_write_byte(cb_priv, HS_TX_ENABLE | HS_RX_ENABLE | HS_CLR_SRQ_INT | - HS_CLR_EOI_EMPTY_INT | HS_CLR_HF_INT, HS_MODE); - - cb_priv->hs_mode_bits = HS_HF_INT_EN; - cb7210_write_byte(cb_priv, cb_priv->hs_mode_bits, HS_MODE); - - write_byte(nec_priv, AUX_LO_SPEED, AUXMR); - /* - * set clock register for maximum (20 MHz) driving frequency - * ICR should be set to clock in megahertz (1-15) and to zero - * for clocks faster than 15 MHz (max 20MHz) - */ - write_byte(nec_priv, ICR | 0, AUXMR); - - if (cb_priv->pci_chip == PCI_CHIP_QUANCOM) { - /* change interrupt polarity */ - nec_priv->auxb_bits |= HR_INV; - write_byte(nec_priv, nec_priv->auxb_bits, AUXMR); - } - nec7210_board_online(nec_priv, board); - - /* poll so we can detect assertion of ATN */ - if (gpib_request_pseudo_irq(board, cb_pci_interrupt)) { - pr_err("failed to allocate pseudo_irq\n"); - return -1; - } - return 0; -} - -static int cb_pci_attach(struct gpib_board *board, const struct gpib_board_config *config) -{ - struct cb7210_priv *cb_priv; - struct nec7210_priv *nec_priv; - int isr_flags = 0; - int bits; - int retval; - - retval = cb7210_generic_attach(board); - if (retval) - return retval; - - cb_priv = board->private_data; - nec_priv = &cb_priv->nec7210_priv; - - cb_priv->pci_device = gpib_pci_get_device(config, PCI_VENDOR_ID_CBOARDS, - PCI_DEVICE_ID_CBOARDS_PCI_GPIB, NULL); - if (cb_priv->pci_device) - cb_priv->pci_chip = PCI_CHIP_AMCC_S5933; - if (!cb_priv->pci_device) { - cb_priv->pci_device = gpib_pci_get_device(config, PCI_VENDOR_ID_CBOARDS, - PCI_DEVICE_ID_CBOARDS_CPCI_GPIB, NULL); - if (cb_priv->pci_device) - cb_priv->pci_chip = PCI_CHIP_AMCC_S5933; - } - if (!cb_priv->pci_device) { - cb_priv->pci_device = gpib_pci_get_device(config, PCI_VENDOR_ID_QUANCOM, - PCI_DEVICE_ID_QUANCOM_GPIB, NULL); - if (cb_priv->pci_device) { - cb_priv->pci_chip = PCI_CHIP_QUANCOM; - nec_priv->offset = 4; - } - } - if (!cb_priv->pci_device) { - dev_err(board->gpib_dev, "no supported boards found.\n"); - return -ENODEV; - } - - if (pci_enable_device(cb_priv->pci_device)) { - dev_err(board->gpib_dev, "error enabling pci device\n"); - return -EIO; - } - - if (pci_request_regions(cb_priv->pci_device, DRV_NAME)) - return -EBUSY; - switch (cb_priv->pci_chip) { - case PCI_CHIP_AMCC_S5933: - cb_priv->amcc_iobase = pci_resource_start(cb_priv->pci_device, 0); - nec_priv->iobase = pci_resource_start(cb_priv->pci_device, 1); - cb_priv->fifo_iobase = pci_resource_start(cb_priv->pci_device, 2); - break; - case PCI_CHIP_QUANCOM: - nec_priv->iobase = pci_resource_start(cb_priv->pci_device, 0); - cb_priv->fifo_iobase = nec_priv->iobase; - break; - default: - dev_err(board->gpib_dev, "bug! unhandled pci_chip=%i\n", cb_priv->pci_chip); - return -EIO; - } - isr_flags |= IRQF_SHARED; - if (request_irq(cb_priv->pci_device->irq, cb_pci_interrupt, isr_flags, DRV_NAME, board)) { - dev_err(board->gpib_dev, "can't request IRQ %d\n", - cb_priv->pci_device->irq); - return -EBUSY; - } - cb_priv->irq = cb_priv->pci_device->irq; - - switch (cb_priv->pci_chip) { - case PCI_CHIP_AMCC_S5933: - // make sure mailbox flags are clear - inl(cb_priv->amcc_iobase + INCOMING_MAILBOX_REG(3)); - // enable interrupts on amccs5933 chip - bits = INBOX_FULL_INTR_BIT | INBOX_BYTE_BITS(3) | INBOX_SELECT_BITS(3) | - INBOX_INTR_CS_BIT; - outl(bits, cb_priv->amcc_iobase + INTCSR_REG); - break; - default: - break; - } - return cb7210_init(cb_priv, board); -} - -static void cb_pci_detach(struct gpib_board *board) -{ - struct cb7210_priv *cb_priv = board->private_data; - struct nec7210_priv *nec_priv; - - if (cb_priv) { - gpib_free_pseudo_irq(board); - nec_priv = &cb_priv->nec7210_priv; - if (cb_priv->irq) { - // disable amcc interrupts - outl(0, cb_priv->amcc_iobase + INTCSR_REG); - free_irq(cb_priv->irq, board); - } - if (nec_priv->iobase) { - nec7210_board_reset(nec_priv, board); - pci_release_regions(cb_priv->pci_device); - } - if (cb_priv->pci_device) - pci_dev_put(cb_priv->pci_device); - } - cb7210_generic_detach(board); -} - -static int cb_isa_attach(struct gpib_board *board, const struct gpib_board_config *config) -{ - int isr_flags = 0; - struct cb7210_priv *cb_priv; - struct nec7210_priv *nec_priv; - unsigned int bits; - int retval; - - retval = cb7210_generic_attach(board); - if (retval) - return retval; - cb_priv = board->private_data; - nec_priv = &cb_priv->nec7210_priv; - if (!request_region(config->ibbase, cb7210_iosize, DRV_NAME)) { - dev_err(board->gpib_dev, "ioports starting at 0x%x are already in use\n", - config->ibbase); - return -EBUSY; - } - nec_priv->iobase = config->ibbase; - cb_priv->fifo_iobase = nec7210_iobase(cb_priv); - - bits = irq_bits(config->ibirq); - if (bits == 0) - dev_err(board->gpib_dev, "board incapable of using irq %i, try 2-5, 7, 10, or 11\n", - config->ibirq); - - // install interrupt handler - if (request_irq(config->ibirq, cb7210_interrupt, isr_flags, DRV_NAME, board)) { - dev_err(board->gpib_dev, "failed to obtain IRQ %d\n", config->ibirq); - return -EBUSY; - } - cb_priv->irq = config->ibirq; - - return cb7210_init(cb_priv, board); -} - -static void cb_isa_detach(struct gpib_board *board) -{ - struct cb7210_priv *cb_priv = board->private_data; - struct nec7210_priv *nec_priv; - - if (cb_priv) { - gpib_free_pseudo_irq(board); - nec_priv = &cb_priv->nec7210_priv; - if (cb_priv->irq) - free_irq(cb_priv->irq, board); - if (nec_priv->iobase) { - nec7210_board_reset(nec_priv, board); - release_region(nec7210_iobase(cb_priv), cb7210_iosize); - } - } - cb7210_generic_detach(board); -} - -static int cb7210_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) -{ - return 0; -} - -static const struct pci_device_id cb7210_pci_table[] = { - {PCI_VENDOR_ID_CBOARDS, PCI_DEVICE_ID_CBOARDS_PCI_GPIB, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, - {PCI_VENDOR_ID_CBOARDS, PCI_DEVICE_ID_CBOARDS_CPCI_GPIB, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, - {PCI_VENDOR_ID_QUANCOM, PCI_DEVICE_ID_QUANCOM_GPIB, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, - { 0 } -}; -MODULE_DEVICE_TABLE(pci, cb7210_pci_table); - -static struct pci_driver cb7210_pci_driver = { - .name = DRV_NAME, - .id_table = cb7210_pci_table, - .probe = &cb7210_pci_probe -}; - -/*************************************************************************** - * Support for computer boards pcmcia-gpib card - * - * Based on gpib PCMCIA client driver written by Claus Schroeter - * (clausi@chemie.fu-berlin.de), which was adapted from the - * pcmcia skeleton example (presumably David Hinds) - ***************************************************************************/ - -#ifdef CONFIG_GPIB_PCMCIA - -#include -#include -#include -#include - -#include -#include - -/* - * The event() function is this driver's Card Services event handler. - * It will be called by Card Services when an appropriate card status - * event is received. The config() and release() entry points are - * used to configure or release a socket, in response to card insertion - * and ejection events. They are invoked from the gpib event - * handler. - */ - -static int cb_gpib_config(struct pcmcia_device *link); -static void cb_gpib_release(struct pcmcia_device *link); -static int cb_pcmcia_attach(struct gpib_board *board, const struct gpib_board_config *config); -static void cb_pcmcia_detach(struct gpib_board *board); - -/* - * A linked list of "instances" of the gpib device. Each actual - * PCMCIA card corresponds to one device instance, and is described - * by one dev_link_t structure (defined in ds.h). - * - * You may not want to use a linked list for this -- for example, the - * memory card driver uses an array of dev_link_t pointers, where minor - * device numbers are used to derive the corresponding array index. - */ - -static struct pcmcia_device *curr_dev; - -/* - * A dev_link_t structure has fields for most things that are needed - * to keep track of a socket, but there will usually be some device - * specific information that also needs to be kept track of. The - * 'priv' pointer in a dev_link_t structure can be used to point to - * a device-specific private data structure, like this. - * - * A driver needs to provide a dev_node_t structure for each device - * on a card. In some cases, there is only one device per card (for - * example, ethernet cards, modems). In other cases, there may be - * many actual or logical devices (SCSI adapters, memory cards with - * multiple partitions). The dev_node_t structures need to be kept - * in a linked list starting at the 'dev' field of a dev_link_t - * structure. We allocate them in the card's private data structure, - * because they generally can't be allocated dynamically. - */ - -struct local_info { - struct pcmcia_device *p_dev; - struct gpib_board *dev; -}; - -/* - * gpib_attach() creates an "instance" of the driver, allocating - * local data structures for one device. The device is registered - * with Card Services. - * - * The dev_link structure is initialized, but we don't actually - * configure the card at this point -- we wait until we receive a - * card insertion event. - */ - -static int cb_gpib_probe(struct pcmcia_device *link) -{ - struct local_info *info; - int ret; - - /* Allocate space for private device-specific data */ - info = kzalloc(sizeof(*info), GFP_KERNEL); - if (!info) - return -ENOMEM; - - info->p_dev = link; - link->priv = info; - - /* The io structure describes IO port mapping */ - link->resource[0]->end = 16; - link->resource[0]->flags &= ~IO_DATA_PATH_WIDTH; - link->resource[0]->flags |= IO_DATA_PATH_WIDTH_AUTO; - link->resource[1]->end = 16; - link->resource[1]->flags &= ~IO_DATA_PATH_WIDTH; - link->resource[1]->flags |= IO_DATA_PATH_WIDTH_16; - link->io_lines = 10; - - /* General socket configuration */ - link->config_flags = CONF_ENABLE_IRQ | CONF_AUTO_SET_IO; - link->config_index = 1; - link->config_regs = PRESENT_OPTION; - - /* Register with Card Services */ - curr_dev = link; - ret = cb_gpib_config(link); - if (ret) - goto free_info; - - return 0; - -free_info: - kfree(info); - return ret; -} - -/* - * This deletes a driver "instance". The device is de-registered - * with Card Services. If it has been released, all local data - * structures are freed. Otherwise, the structures will be freed - * when the device is released. - */ - -static void cb_gpib_remove(struct pcmcia_device *link) -{ - struct local_info *info = link->priv; - //struct struct gpib_board *dev = info->dev; - - if (info->dev) - cb_pcmcia_detach(info->dev); - cb_gpib_release(link); - - //free_netdev(dev); - kfree(info); -} - -static int cb_gpib_config_iteration(struct pcmcia_device *link, void *priv_data) -{ - return pcmcia_request_io(link); -} - -/* - * gpib_config() is scheduled to run after a CARD_INSERTION event - * is received, to configure the PCMCIA socket, and to make the - * ethernet device available to the system. - */ - -static int cb_gpib_config(struct pcmcia_device *link) -{ - int retval; - - retval = pcmcia_loop_config(link, &cb_gpib_config_iteration, NULL); - if (retval) { - dev_warn(&link->dev, "no configuration found\n"); - cb_gpib_release(link); - return -ENODEV; - } - - /* - * This actually configures the PCMCIA socket -- setting up - * the I/O windows and the interrupt mapping. - */ - retval = pcmcia_enable_device(link); - if (retval) { - dev_warn(&link->dev, "pcmcia_enable_device failed\n"); - cb_gpib_release(link); - return -ENODEV; - } - - return 0; -} /* gpib_config */ - -/* - * After a card is removed, gpib_release() will unregister the net - * device, and release the PCMCIA configuration. If the device is - * still open, this will be postponed until it is closed. - */ - -static void cb_gpib_release(struct pcmcia_device *link) -{ - pcmcia_disable_device(link); -} - -static int cb_gpib_suspend(struct pcmcia_device *link) -{ - //struct local_info *info = link->priv; - //struct struct gpib_board *dev = info->dev; - - if (link->open) - dev_warn(&link->dev, "Device still open\n"); - //netif_device_detach(dev); - - return 0; -} - -static int cb_gpib_resume(struct pcmcia_device *link) -{ - //struct local_info *info = link->priv; - //struct struct gpib_board *dev = info->dev; - - /*if (link->open) { - * ni_gpib_probe(dev); / really? - * //netif_device_attach(dev); - * - */ - return cb_gpib_config(link); -} - -/*====================================================================*/ - -static struct pcmcia_device_id cb_pcmcia_ids[] = { - PCMCIA_DEVICE_MANF_CARD(0x01c5, 0x0005), - PCMCIA_DEVICE_NULL -}; -MODULE_DEVICE_TABLE(pcmcia, cb_pcmcia_ids); - -static struct pcmcia_driver cb_gpib_cs_driver = { - .name = "cb_gpib_cs", - .owner = THIS_MODULE, - .id_table = cb_pcmcia_ids, - .probe = cb_gpib_probe, - .remove = cb_gpib_remove, - .suspend = cb_gpib_suspend, - .resume = cb_gpib_resume, -}; - -static void cb_pcmcia_cleanup_module(void) -{ - pcmcia_unregister_driver(&cb_gpib_cs_driver); -} - -static struct gpib_interface cb_pcmcia_unaccel_interface = { - .name = "cbi_pcmcia_unaccel", - .attach = cb_pcmcia_attach, - .detach = cb_pcmcia_detach, - .read = cb7210_read, - .write = cb7210_write, - .command = cb7210_command, - .take_control = cb7210_take_control, - .go_to_standby = cb7210_go_to_standby, - .request_system_control = cb7210_request_system_control, - .interface_clear = cb7210_interface_clear, - .remote_enable = cb7210_remote_enable, - .enable_eos = cb7210_enable_eos, - .disable_eos = cb7210_disable_eos, - .parallel_poll = cb7210_parallel_poll, - .parallel_poll_configure = cb7210_parallel_poll_configure, - .parallel_poll_response = cb7210_parallel_poll_response, - .local_parallel_poll_mode = NULL, // XXX - .line_status = cb7210_line_status, - .update_status = cb7210_update_status, - .primary_address = cb7210_primary_address, - .secondary_address = cb7210_secondary_address, - .serial_poll_response = cb7210_serial_poll_response, - .serial_poll_status = cb7210_serial_poll_status, - .t1_delay = cb7210_t1_delay, - .return_to_local = cb7210_return_to_local, -}; - -static struct gpib_interface cb_pcmcia_interface = { - .name = "cbi_pcmcia", - .attach = cb_pcmcia_attach, - .detach = cb_pcmcia_detach, - .read = cb7210_accel_read, - .write = cb7210_accel_write, - .command = cb7210_command, - .take_control = cb7210_take_control, - .go_to_standby = cb7210_go_to_standby, - .request_system_control = cb7210_request_system_control, - .interface_clear = cb7210_interface_clear, - .remote_enable = cb7210_remote_enable, - .enable_eos = cb7210_enable_eos, - .disable_eos = cb7210_disable_eos, - .parallel_poll = cb7210_parallel_poll, - .parallel_poll_configure = cb7210_parallel_poll_configure, - .parallel_poll_response = cb7210_parallel_poll_response, - .local_parallel_poll_mode = NULL, // XXX - .line_status = cb7210_line_status, - .update_status = cb7210_update_status, - .primary_address = cb7210_primary_address, - .secondary_address = cb7210_secondary_address, - .serial_poll_response = cb7210_serial_poll_response, - .serial_poll_status = cb7210_serial_poll_status, - .t1_delay = cb7210_t1_delay, - .return_to_local = cb7210_return_to_local, -}; - -static struct gpib_interface cb_pcmcia_accel_interface = { - .name = "cbi_pcmcia_accel", - .attach = cb_pcmcia_attach, - .detach = cb_pcmcia_detach, - .read = cb7210_accel_read, - .write = cb7210_accel_write, - .command = cb7210_command, - .take_control = cb7210_take_control, - .go_to_standby = cb7210_go_to_standby, - .request_system_control = cb7210_request_system_control, - .interface_clear = cb7210_interface_clear, - .remote_enable = cb7210_remote_enable, - .enable_eos = cb7210_enable_eos, - .disable_eos = cb7210_disable_eos, - .parallel_poll = cb7210_parallel_poll, - .parallel_poll_configure = cb7210_parallel_poll_configure, - .parallel_poll_response = cb7210_parallel_poll_response, - .local_parallel_poll_mode = NULL, // XXX - .line_status = cb7210_line_status, - .update_status = cb7210_update_status, - .primary_address = cb7210_primary_address, - .secondary_address = cb7210_secondary_address, - .serial_poll_response = cb7210_serial_poll_response, - .serial_poll_status = cb7210_serial_poll_status, - .t1_delay = cb7210_t1_delay, - .return_to_local = cb7210_return_to_local, -}; - -static int cb_pcmcia_attach(struct gpib_board *board, const struct gpib_board_config *config) -{ - struct cb7210_priv *cb_priv; - struct nec7210_priv *nec_priv; - int retval; - - if (!curr_dev) { - dev_err(board->gpib_dev, "no cb pcmcia cards found\n"); - return -ENODEV; - } - - retval = cb7210_generic_attach(board); - if (retval) - return retval; - - cb_priv = board->private_data; - nec_priv = &cb_priv->nec7210_priv; - - if (!request_region(curr_dev->resource[0]->start, resource_size(curr_dev->resource[0]), - DRV_NAME)) { - dev_err(board->gpib_dev, "ioports starting at 0x%lx are already in use\n", - (unsigned long)curr_dev->resource[0]->start); - return -EBUSY; - } - nec_priv->iobase = curr_dev->resource[0]->start; - cb_priv->fifo_iobase = curr_dev->resource[0]->start; - - if (request_irq(curr_dev->irq, cb7210_interrupt, IRQF_SHARED, DRV_NAME, board)) { - dev_err(board->gpib_dev, "failed to request IRQ %d\n", curr_dev->irq); - return -EBUSY; - } - cb_priv->irq = curr_dev->irq; - - return cb7210_init(cb_priv, board); -} - -static void cb_pcmcia_detach(struct gpib_board *board) -{ - struct cb7210_priv *cb_priv = board->private_data; - struct nec7210_priv *nec_priv; - - if (cb_priv) { - nec_priv = &cb_priv->nec7210_priv; - gpib_free_pseudo_irq(board); - if (cb_priv->irq) - free_irq(cb_priv->irq, board); - if (nec_priv->iobase) { - nec7210_board_reset(nec_priv, board); - release_region(nec7210_iobase(cb_priv), cb7210_iosize); - } - } - cb7210_generic_detach(board); -} - -#endif /* CONFIG_GPIB_PCMCIA */ - -static int __init cb7210_init_module(void) -{ - int ret; - - ret = pci_register_driver(&cb7210_pci_driver); - if (ret) { - pr_err("pci_register_driver failed: error = %d\n", ret); - return ret; - } - - ret = gpib_register_driver(&cb_pci_interface, THIS_MODULE); - if (ret) { - pr_err("gpib_register_driver failed: error = %d\n", ret); - goto err_pci; - } - - ret = gpib_register_driver(&cb_isa_interface, THIS_MODULE); - if (ret) { - pr_err("gpib_register_driver failed: error = %d\n", ret); - goto err_isa; - } - - ret = gpib_register_driver(&cb_pci_accel_interface, THIS_MODULE); - if (ret) { - pr_err("gpib_register_driver failed: error = %d\n", ret); - goto err_pci_accel; - } - - ret = gpib_register_driver(&cb_pci_unaccel_interface, THIS_MODULE); - if (ret) { - pr_err("gpib_register_driver failed: error = %d\n", ret); - goto err_pci_unaccel; - } - - ret = gpib_register_driver(&cb_isa_accel_interface, THIS_MODULE); - if (ret) { - pr_err("gpib_register_driver failed: error = %d\n", ret); - goto err_isa_accel; - } - - ret = gpib_register_driver(&cb_isa_unaccel_interface, THIS_MODULE); - if (ret) { - pr_err("gpib_register_driver failed: error = %d\n", ret); - goto err_isa_unaccel; - } - -#ifdef CONFIG_GPIB_PCMCIA - ret = gpib_register_driver(&cb_pcmcia_interface, THIS_MODULE); - if (ret) { - pr_err("gpib_register_driver failed: error = %d\n", ret); - goto err_pcmcia; - } - - ret = gpib_register_driver(&cb_pcmcia_accel_interface, THIS_MODULE); - if (ret) { - pr_err("gpib_register_driver failed: error = %d\n", ret); - goto err_pcmcia_accel; - } - - ret = gpib_register_driver(&cb_pcmcia_unaccel_interface, THIS_MODULE); - if (ret) { - pr_err("gpib_register_driver failed: error = %d\n", ret); - goto err_pcmcia_unaccel; - } - - ret = pcmcia_register_driver(&cb_gpib_cs_driver); - if (ret) { - pr_err("pcmcia_register_driver failed: error = %d\n", ret); - goto err_pcmcia_driver; - } -#endif - - return 0; - -#ifdef CONFIG_GPIB_PCMCIA -err_pcmcia_driver: - gpib_unregister_driver(&cb_pcmcia_unaccel_interface); -err_pcmcia_unaccel: - gpib_unregister_driver(&cb_pcmcia_accel_interface); -err_pcmcia_accel: - gpib_unregister_driver(&cb_pcmcia_interface); -err_pcmcia: -#endif - gpib_unregister_driver(&cb_isa_unaccel_interface); -err_isa_unaccel: - gpib_unregister_driver(&cb_isa_accel_interface); -err_isa_accel: - gpib_unregister_driver(&cb_pci_unaccel_interface); -err_pci_unaccel: - gpib_unregister_driver(&cb_pci_accel_interface); -err_pci_accel: - gpib_unregister_driver(&cb_isa_interface); -err_isa: - gpib_unregister_driver(&cb_pci_interface); -err_pci: - pci_unregister_driver(&cb7210_pci_driver); - - return ret; -} - -static void __exit cb7210_exit_module(void) -{ - gpib_unregister_driver(&cb_pci_interface); - gpib_unregister_driver(&cb_isa_interface); - gpib_unregister_driver(&cb_pci_accel_interface); - gpib_unregister_driver(&cb_pci_unaccel_interface); - gpib_unregister_driver(&cb_isa_accel_interface); - gpib_unregister_driver(&cb_isa_unaccel_interface); -#ifdef CONFIG_GPIB_PCMCIA - gpib_unregister_driver(&cb_pcmcia_interface); - gpib_unregister_driver(&cb_pcmcia_accel_interface); - gpib_unregister_driver(&cb_pcmcia_unaccel_interface); - cb_pcmcia_cleanup_module(); -#endif - - pci_unregister_driver(&cb7210_pci_driver); -} - -module_init(cb7210_init_module); -module_exit(cb7210_exit_module); diff --git a/drivers/staging/gpib/cb7210/cb7210.h b/drivers/staging/gpib/cb7210/cb7210.h deleted file mode 100644 index ddc841ff87ae..000000000000 --- a/drivers/staging/gpib/cb7210/cb7210.h +++ /dev/null @@ -1,203 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -/*************************************************************************** - * copyright : (C) 2002 by Frank Mori Hess - ***************************************************************************/ - -#include "nec7210.h" -#include "gpibP.h" -#include "amccs5933.h" - -#include -#include - -enum { - PCI_DEVICE_ID_CBOARDS_PCI_GPIB = 0x6, - PCI_DEVICE_ID_CBOARDS_CPCI_GPIB = 0xe, -}; - -enum pci_chip { - PCI_CHIP_NONE = 0, - PCI_CHIP_AMCC_S5933, - PCI_CHIP_QUANCOM -}; - -// struct which defines private_data for cb7210 boards -struct cb7210_priv { - struct nec7210_priv nec7210_priv; - struct pci_dev *pci_device; - // base address of amccs5933 pci chip - unsigned long amcc_iobase; - unsigned long fifo_iobase; - unsigned int irq; - enum pci_chip pci_chip; - u8 hs_mode_bits; - unsigned out_fifo_half_empty : 1; - unsigned in_fifo_half_full : 1; -}; - -// pci-gpib register offset -static const int cb7210_reg_offset = 1; - -// uses 10 ioports -static const int cb7210_iosize = 10; - -// fifo size in bytes -static const int cb7210_fifo_size = 2048; -static const int cb7210_fifo_width = 2; - -// cb7210 specific registers and bits -enum cb7210_regs { - BUS_STATUS = 0x7, -}; - -enum cb7210_page_in { - BUS_STATUS_PAGE = 1, -}; - -enum hs_regs { - // write registers - HS_MODE = 0x8, /* HS_MODE register */ - HS_INT_LEVEL = 0x9, /* HS_INT_LEVEL register */ - // read registers - HS_STATUS = 0x8, /* HS_STATUS register */ -}; - -static inline u32 nec7210_iobase(const struct cb7210_priv *cb_priv) -{ - return cb_priv->nec7210_priv.iobase; -} - -static inline int cb7210_page_in_bits(unsigned int page) -{ - return 0x50 | (page & 0xf); -} - -static inline u8 cb7210_paged_read_byte(struct cb7210_priv *cb_priv, - unsigned int register_num, unsigned int page) -{ - struct nec7210_priv *nec_priv = &cb_priv->nec7210_priv; - u8 retval; - unsigned long flags; - - spin_lock_irqsave(&nec_priv->register_page_lock, flags); - outb(cb7210_page_in_bits(page), nec7210_iobase(cb_priv) + AUXMR * nec_priv->offset); - udelay(1); - retval = inb(nec7210_iobase(cb_priv) + register_num * nec_priv->offset); - spin_unlock_irqrestore(&nec_priv->register_page_lock, flags); - return retval; -} - -// don't use for register_num < 8, since it doesn't lock -static inline u8 cb7210_read_byte(const struct cb7210_priv *cb_priv, - enum hs_regs register_num) -{ - const struct nec7210_priv *nec_priv = &cb_priv->nec7210_priv; - u8 retval; - - retval = inb(nec7210_iobase(cb_priv) + register_num * nec_priv->offset); - return retval; -} - -static inline void cb7210_paged_write_byte(struct cb7210_priv *cb_priv, u8 data, - unsigned int register_num, unsigned int page) -{ - struct nec7210_priv *nec_priv = &cb_priv->nec7210_priv; - unsigned long flags; - - spin_lock_irqsave(&nec_priv->register_page_lock, flags); - outb(cb7210_page_in_bits(page), nec7210_iobase(cb_priv) + AUXMR * nec_priv->offset); - udelay(1); - outb(data, nec7210_iobase(cb_priv) + register_num * nec_priv->offset); - spin_unlock_irqrestore(&nec_priv->register_page_lock, flags); -} - -// don't use for register_num < 8, since it doesn't lock -static inline void cb7210_write_byte(const struct cb7210_priv *cb_priv, u8 data, - enum hs_regs register_num) -{ - const struct nec7210_priv *nec_priv = &cb_priv->nec7210_priv; - - outb(data, nec7210_iobase(cb_priv) + register_num * nec_priv->offset); -} - -enum bus_status_bits { - BSR_ATN_BIT = 0x1, - BSR_EOI_BIT = 0x2, - BSR_SRQ_BIT = 0x4, - BSR_IFC_BIT = 0x8, - BSR_REN_BIT = 0x10, - BSR_DAV_BIT = 0x20, - BSR_NRFD_BIT = 0x40, - BSR_NDAC_BIT = 0x80, -}; - -/* CBI 488.2 HS control */ - -/* - * when both bit 0 and 1 are set, it - * 1 clears the transmit state machine to an initial condition - * 2 clears any residual interrupts left latched on cbi488.2 - * 3 resets all control bits in HS_MODE to zero - * 4 enables TX empty interrupts - * when both bit 0 and 1 are zero, then the high speed mode is disabled - */ -enum hs_mode_bits { - HS_ENABLE_MASK = 0x3, - HS_TX_ENABLE = (1 << 0), - HS_RX_ENABLE = (1 << 1), - HS_HF_INT_EN = (1 << 3), - HS_CLR_SRQ_INT = (1 << 4), - HS_CLR_EOI_EMPTY_INT = (1 << 5), - HS_CLR_HF_INT = (1 << 6), - HS_SYS_CONTROL = (1 << 7), -}; - -/* CBI 488.2 status */ -enum hs_status_bits { - HS_FIFO_FULL = (1 << 0), - HS_HALF_FULL = (1 << 1), - HS_SRQ_INT = (1 << 2), - HS_EOI_INT = (1 << 3), - HS_TX_MSB_NOT_EMPTY = (1 << 4), - HS_RX_MSB_NOT_EMPTY = (1 << 5), - HS_TX_LSB_NOT_EMPTY = (1 << 6), - HS_RX_LSB_NOT_EMPTY = (1 << 7), -}; - -/* CBI488.2 hs_int_level register */ -enum hs_int_level_bits { - HS_RESET7210 = (1 << 7), -}; - -static inline unsigned int irq_bits(unsigned int irq) -{ - switch (irq) { - case 2: - case 3: - case 4: - case 5: - return irq - 1; - case 7: - return 0x5; - case 10: - return 0x6; - case 11: - return 0x7; - default: - return 0; - } -} - -enum cb7210_aux_cmds { -/* - * AUX_RTL2 is an undocumented aux command which causes cb7210 to assert - * (and keep asserted) local rtl message. This is used in conjunction - * with the (stupid) cb7210 implementation - * of the normal nec7210 AUX_RTL aux command, which - * causes the rtl message to toggle between on and off. - */ - AUX_RTL2 = 0xd, - AUX_LO_SPEED = 0x40, - AUX_HI_SPEED = 0x41, -}; diff --git a/drivers/staging/gpib/cec/Makefile b/drivers/staging/gpib/cec/Makefile deleted file mode 100644 index b7141e23d4e0..000000000000 --- a/drivers/staging/gpib/cec/Makefile +++ /dev/null @@ -1,3 +0,0 @@ - -obj-$(CONFIG_GPIB_CEC_PCI) += cec_gpib.o - diff --git a/drivers/staging/gpib/cec/cec.h b/drivers/staging/gpib/cec/cec.h deleted file mode 100644 index 3ce2869c7429..000000000000 --- a/drivers/staging/gpib/cec/cec.h +++ /dev/null @@ -1,20 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -/*************************************************************************** - * copyright : (C) 2002 by Frank Mori Hess - ***************************************************************************/ - -#include "nec7210.h" -#include "gpibP.h" -#include "plx9050.h" - -struct cec_priv { - struct nec7210_priv nec7210_priv; - struct pci_dev *pci_device; - // base address for plx9052 pci chip - unsigned long plx_iobase; - unsigned int irq; -}; - -// offset between consecutive nec7210 registers -static const int cec_reg_offset = 1; diff --git a/drivers/staging/gpib/cec/cec_gpib.c b/drivers/staging/gpib/cec/cec_gpib.c deleted file mode 100644 index dbf9b95baabc..000000000000 --- a/drivers/staging/gpib/cec/cec_gpib.c +++ /dev/null @@ -1,393 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 - -/*************************************************************************** - * copyright : (C) 2002 by Frank Mori Hess - ***************************************************************************/ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -#define dev_fmt pr_fmt -#define DRV_NAME KBUILD_MODNAME - -#include "cec.h" -#include -#include -#include -#include -#include -#include - -MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("GPIB driver for CEC PCI and PCMCIA boards"); - -/* - * GPIB interrupt service routines - */ - -static irqreturn_t cec_interrupt(int irq, void *arg) -{ - struct gpib_board *board = arg; - struct cec_priv *priv = board->private_data; - unsigned long flags; - irqreturn_t retval; - - spin_lock_irqsave(&board->spinlock, flags); - retval = nec7210_interrupt(board, &priv->nec7210_priv); - spin_unlock_irqrestore(&board->spinlock, flags); - return retval; -} - -#define CEC_VENDOR_ID 0x12fc -#define CEC_DEV_ID 0x5cec -#define CEC_SUBID 0x9050 - -static int cec_pci_attach(struct gpib_board *board, const struct gpib_board_config *config); - -static void cec_pci_detach(struct gpib_board *board); - -// wrappers for interface functions -static int cec_read(struct gpib_board *board, u8 *buffer, size_t length, int *end, - size_t *bytes_read) -{ - struct cec_priv *priv = board->private_data; - - return nec7210_read(board, &priv->nec7210_priv, buffer, length, end, bytes_read); -} - -static int cec_write(struct gpib_board *board, u8 *buffer, size_t length, int send_eoi, - size_t *bytes_written) -{ - struct cec_priv *priv = board->private_data; - - return nec7210_write(board, &priv->nec7210_priv, buffer, length, send_eoi, bytes_written); -} - -static int cec_command(struct gpib_board *board, u8 *buffer, - size_t length, size_t *bytes_written) -{ - struct cec_priv *priv = board->private_data; - - return nec7210_command(board, &priv->nec7210_priv, buffer, length, bytes_written); -} - -static int cec_take_control(struct gpib_board *board, int synchronous) -{ - struct cec_priv *priv = board->private_data; - - return nec7210_take_control(board, &priv->nec7210_priv, synchronous); -} - -static int cec_go_to_standby(struct gpib_board *board) -{ - struct cec_priv *priv = board->private_data; - - return nec7210_go_to_standby(board, &priv->nec7210_priv); -} - -static int cec_request_system_control(struct gpib_board *board, int request_control) -{ - struct cec_priv *priv = board->private_data; - - return nec7210_request_system_control(board, &priv->nec7210_priv, request_control); -} - -static void cec_interface_clear(struct gpib_board *board, int assert) -{ - struct cec_priv *priv = board->private_data; - - nec7210_interface_clear(board, &priv->nec7210_priv, assert); -} - -static void cec_remote_enable(struct gpib_board *board, int enable) -{ - struct cec_priv *priv = board->private_data; - - nec7210_remote_enable(board, &priv->nec7210_priv, enable); -} - -static int cec_enable_eos(struct gpib_board *board, u8 eos_byte, int compare_8_bits) -{ - struct cec_priv *priv = board->private_data; - - return nec7210_enable_eos(board, &priv->nec7210_priv, eos_byte, compare_8_bits); -} - -static void cec_disable_eos(struct gpib_board *board) -{ - struct cec_priv *priv = board->private_data; - - nec7210_disable_eos(board, &priv->nec7210_priv); -} - -static unsigned int cec_update_status(struct gpib_board *board, unsigned int clear_mask) -{ - struct cec_priv *priv = board->private_data; - - return nec7210_update_status(board, &priv->nec7210_priv, clear_mask); -} - -static int cec_primary_address(struct gpib_board *board, unsigned int address) -{ - struct cec_priv *priv = board->private_data; - - return nec7210_primary_address(board, &priv->nec7210_priv, address); -} - -static int cec_secondary_address(struct gpib_board *board, unsigned int address, int enable) -{ - struct cec_priv *priv = board->private_data; - - return nec7210_secondary_address(board, &priv->nec7210_priv, address, enable); -} - -static int cec_parallel_poll(struct gpib_board *board, u8 *result) -{ - struct cec_priv *priv = board->private_data; - - return nec7210_parallel_poll(board, &priv->nec7210_priv, result); -} - -static void cec_parallel_poll_configure(struct gpib_board *board, u8 config) -{ - struct cec_priv *priv = board->private_data; - - nec7210_parallel_poll_configure(board, &priv->nec7210_priv, config); -} - -static void cec_parallel_poll_response(struct gpib_board *board, int ist) -{ - struct cec_priv *priv = board->private_data; - - nec7210_parallel_poll_response(board, &priv->nec7210_priv, ist); -} - -static void cec_serial_poll_response(struct gpib_board *board, u8 status) -{ - struct cec_priv *priv = board->private_data; - - nec7210_serial_poll_response(board, &priv->nec7210_priv, status); -} - -static u8 cec_serial_poll_status(struct gpib_board *board) -{ - struct cec_priv *priv = board->private_data; - - return nec7210_serial_poll_status(board, &priv->nec7210_priv); -} - -static int cec_t1_delay(struct gpib_board *board, unsigned int nano_sec) -{ - struct cec_priv *priv = board->private_data; - - return nec7210_t1_delay(board, &priv->nec7210_priv, nano_sec); -} - -static void cec_return_to_local(struct gpib_board *board) -{ - struct cec_priv *priv = board->private_data; - - nec7210_return_to_local(board, &priv->nec7210_priv); -} - -static struct gpib_interface cec_pci_interface = { - .name = "cec_pci", - .attach = cec_pci_attach, - .detach = cec_pci_detach, - .read = cec_read, - .write = cec_write, - .command = cec_command, - .take_control = cec_take_control, - .go_to_standby = cec_go_to_standby, - .request_system_control = cec_request_system_control, - .interface_clear = cec_interface_clear, - .remote_enable = cec_remote_enable, - .enable_eos = cec_enable_eos, - .disable_eos = cec_disable_eos, - .parallel_poll = cec_parallel_poll, - .parallel_poll_configure = cec_parallel_poll_configure, - .parallel_poll_response = cec_parallel_poll_response, - .local_parallel_poll_mode = NULL, // XXX - .line_status = NULL, // XXX - .update_status = cec_update_status, - .primary_address = cec_primary_address, - .secondary_address = cec_secondary_address, - .serial_poll_response = cec_serial_poll_response, - .serial_poll_status = cec_serial_poll_status, - .t1_delay = cec_t1_delay, - .return_to_local = cec_return_to_local, -}; - -static int cec_allocate_private(struct gpib_board *board) -{ - struct cec_priv *priv; - - board->private_data = kmalloc(sizeof(struct cec_priv), GFP_KERNEL); - if (!board->private_data) - return -1; - priv = board->private_data; - memset(priv, 0, sizeof(struct cec_priv)); - init_nec7210_private(&priv->nec7210_priv); - return 0; -} - -static void cec_free_private(struct gpib_board *board) -{ - kfree(board->private_data); - board->private_data = NULL; -} - -static int cec_generic_attach(struct gpib_board *board) -{ - struct cec_priv *cec_priv; - struct nec7210_priv *nec_priv; - - board->status = 0; - - if (cec_allocate_private(board)) - return -ENOMEM; - cec_priv = board->private_data; - nec_priv = &cec_priv->nec7210_priv; - nec_priv->read_byte = nec7210_ioport_read_byte; - nec_priv->write_byte = nec7210_ioport_write_byte; - nec_priv->offset = cec_reg_offset; - nec_priv->type = NEC7210; // guess - return 0; -} - -static void cec_init(struct cec_priv *cec_priv, const struct gpib_board *board) -{ - struct nec7210_priv *nec_priv = &cec_priv->nec7210_priv; - - nec7210_board_reset(nec_priv, board); - - /* set internal counter register for 8 MHz input clock */ - write_byte(nec_priv, ICR | 8, AUXMR); - - nec7210_board_online(nec_priv, board); -} - -static int cec_pci_attach(struct gpib_board *board, const struct gpib_board_config *config) -{ - struct cec_priv *cec_priv; - struct nec7210_priv *nec_priv; - int isr_flags = 0; - int retval; - - retval = cec_generic_attach(board); - if (retval) - return retval; - - cec_priv = board->private_data; - nec_priv = &cec_priv->nec7210_priv; - - // find board - cec_priv->pci_device = NULL; - while ((cec_priv->pci_device = - gpib_pci_get_device(config, CEC_VENDOR_ID, - CEC_DEV_ID, cec_priv->pci_device))) { - // check for board with plx9050 controller - if (cec_priv->pci_device->subsystem_device == CEC_SUBID) - break; - } - if (!cec_priv->pci_device) { - dev_err(board->gpib_dev, "no cec PCI board found\n"); - return -ENODEV; - } - - if (pci_enable_device(cec_priv->pci_device)) { - dev_err(board->gpib_dev, "error enabling pci device\n"); - return -EIO; - } - - if (pci_request_regions(cec_priv->pci_device, "cec-gpib")) - return -EBUSY; - - cec_priv->plx_iobase = pci_resource_start(cec_priv->pci_device, 1); - nec_priv->iobase = pci_resource_start(cec_priv->pci_device, 3); - - isr_flags |= IRQF_SHARED; - if (request_irq(cec_priv->pci_device->irq, cec_interrupt, isr_flags, DRV_NAME, board)) { - dev_err(board->gpib_dev, "failed to obtain IRQ %d\n", cec_priv->pci_device->irq); - return -EBUSY; - } - cec_priv->irq = cec_priv->pci_device->irq; - if (gpib_request_pseudo_irq(board, cec_interrupt)) { - dev_err(board->gpib_dev, "failed to allocate pseudo irq\n"); - return -1; - } - cec_init(cec_priv, board); - - // enable interrupts on plx chip - outl(PLX9050_LINTR1_EN_BIT | PLX9050_LINTR1_POLARITY_BIT | PLX9050_PCI_INTR_EN_BIT, - cec_priv->plx_iobase + PLX9050_INTCSR_REG); - - return 0; -} - -static void cec_pci_detach(struct gpib_board *board) -{ - struct cec_priv *cec_priv = board->private_data; - struct nec7210_priv *nec_priv; - - if (cec_priv) { - nec_priv = &cec_priv->nec7210_priv; - gpib_free_pseudo_irq(board); - if (cec_priv->irq) { - // disable plx9050 interrupts - outl(0, cec_priv->plx_iobase + PLX9050_INTCSR_REG); - free_irq(cec_priv->irq, board); - } - if (nec_priv->iobase) { - nec7210_board_reset(nec_priv, board); - pci_release_regions(cec_priv->pci_device); - } - if (cec_priv->pci_device) - pci_dev_put(cec_priv->pci_device); - } - cec_free_private(board); -} - -static int cec_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) -{ - return 0; -} - -static const struct pci_device_id cec_pci_table[] = { - {CEC_VENDOR_ID, CEC_DEV_ID, PCI_ANY_ID, CEC_SUBID, 0, 0, 0 }, - {0} -}; -MODULE_DEVICE_TABLE(pci, cec_pci_table); - -static struct pci_driver cec_pci_driver = { - .name = DRV_NAME, - .id_table = cec_pci_table, - .probe = &cec_pci_probe -}; - -static int __init cec_init_module(void) -{ - int result; - - result = pci_register_driver(&cec_pci_driver); - if (result) { - pr_err("pci_register_driver failed: error = %d\n", result); - return result; - } - - result = gpib_register_driver(&cec_pci_interface, THIS_MODULE); - if (result) { - pr_err("gpib_register_driver failed: error = %d\n", result); - return result; - } - - return 0; -} - -static void cec_exit_module(void) -{ - gpib_unregister_driver(&cec_pci_interface); - - pci_unregister_driver(&cec_pci_driver); -} - -module_init(cec_init_module); -module_exit(cec_exit_module); diff --git a/drivers/staging/gpib/common/Makefile b/drivers/staging/gpib/common/Makefile deleted file mode 100644 index 460586edb574..000000000000 --- a/drivers/staging/gpib/common/Makefile +++ /dev/null @@ -1,6 +0,0 @@ - -obj-$(CONFIG_GPIB_COMMON) += gpib_common.o - -gpib_common-objs := gpib_os.o iblib.o - - diff --git a/drivers/staging/gpib/common/gpib_os.c b/drivers/staging/gpib/common/gpib_os.c deleted file mode 100644 index 9dbbac8b8436..000000000000 --- a/drivers/staging/gpib/common/gpib_os.c +++ /dev/null @@ -1,2271 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 - -/*************************************************************************** - * copyright : (C) 2001, 2004 by Frank Mori Hess - *************************************************************************** - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -#define dev_fmt pr_fmt - -#include "ibsys.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("GPIB base support"); -MODULE_ALIAS_CHARDEV_MAJOR(GPIB_CODE); - -static int board_type_ioctl(struct gpib_file_private *file_priv, - struct gpib_board *board, unsigned long arg); -static int read_ioctl(struct gpib_file_private *file_priv, struct gpib_board *board, - unsigned long arg); -static int write_ioctl(struct gpib_file_private *file_priv, struct gpib_board *board, - unsigned long arg); -static int command_ioctl(struct gpib_file_private *file_priv, struct gpib_board *board, - unsigned long arg); -static int open_dev_ioctl(struct file *filep, struct gpib_board *board, unsigned long arg); -static int close_dev_ioctl(struct file *filep, struct gpib_board *board, unsigned long arg); -static int serial_poll_ioctl(struct gpib_board *board, unsigned long arg); -static int wait_ioctl(struct gpib_file_private *file_priv, - struct gpib_board *board, unsigned long arg); -static int parallel_poll_ioctl(struct gpib_board *board, unsigned long arg); -static int online_ioctl(struct gpib_board *board, unsigned long arg); -static int remote_enable_ioctl(struct gpib_board *board, unsigned long arg); -static int take_control_ioctl(struct gpib_board *board, unsigned long arg); -static int line_status_ioctl(struct gpib_board *board, unsigned long arg); -static int pad_ioctl(struct gpib_board *board, struct gpib_file_private *file_priv, - unsigned long arg); -static int sad_ioctl(struct gpib_board *board, struct gpib_file_private *file_priv, - unsigned long arg); -static int eos_ioctl(struct gpib_board *board, unsigned long arg); -static int request_service_ioctl(struct gpib_board *board, unsigned long arg); -static int request_service2_ioctl(struct gpib_board *board, unsigned long arg); -static int iobase_ioctl(struct gpib_board_config *config, unsigned long arg); -static int irq_ioctl(struct gpib_board_config *config, unsigned long arg); -static int dma_ioctl(struct gpib_board_config *config, unsigned long arg); -static int autospoll_ioctl(struct gpib_board *board, struct gpib_file_private *file_priv, - unsigned long arg); -static int mutex_ioctl(struct gpib_board *board, struct gpib_file_private *file_priv, - unsigned long arg); -static int timeout_ioctl(struct gpib_board *board, unsigned long arg); -static int status_bytes_ioctl(struct gpib_board *board, unsigned long arg); -static int board_info_ioctl(const struct gpib_board *board, unsigned long arg); -static int ppc_ioctl(struct gpib_board *board, unsigned long arg); -static int set_local_ppoll_mode_ioctl(struct gpib_board *board, unsigned long arg); -static int get_local_ppoll_mode_ioctl(struct gpib_board *board, unsigned long arg); -static int query_board_rsv_ioctl(struct gpib_board *board, unsigned long arg); -static int interface_clear_ioctl(struct gpib_board *board, unsigned long arg); -static int select_pci_ioctl(struct gpib_board_config *config, unsigned long arg); -static int select_device_path_ioctl(struct gpib_board_config *config, unsigned long arg); -static int event_ioctl(struct gpib_board *board, unsigned long arg); -static int request_system_control_ioctl(struct gpib_board *board, unsigned long arg); -static int t1_delay_ioctl(struct gpib_board *board, unsigned long arg); - -static int cleanup_open_devices(struct gpib_file_private *file_priv, struct gpib_board *board); - -static int pop_gpib_event_nolock(struct gpib_board *board, - struct gpib_event_queue *queue, short *event_type); - -/* - * Timer functions - */ - -/* Watchdog timeout routine */ - -static void watchdog_timeout(struct timer_list *t) -{ - struct gpib_board *board = timer_container_of(board, t, timer); - - set_bit(TIMO_NUM, &board->status); - wake_up_interruptible(&board->wait); -} - -/* install timer interrupt handler */ -void os_start_timer(struct gpib_board *board, unsigned int usec_timeout) -/* Starts the timeout task */ -{ - if (timer_pending(&board->timer)) { - dev_err(board->gpib_dev, "bug! timer already running?\n"); - return; - } - clear_bit(TIMO_NUM, &board->status); - - if (usec_timeout > 0) { - board->timer.function = watchdog_timeout; - /* set number of ticks */ - mod_timer(&board->timer, jiffies + usec_to_jiffies(usec_timeout)); - } -} - -void os_remove_timer(struct gpib_board *board) -/* Removes the timeout task */ -{ - if (timer_pending(&board->timer)) - timer_delete_sync(&board->timer); -} - -int io_timed_out(struct gpib_board *board) -{ - if (test_bit(TIMO_NUM, &board->status)) - return 1; - return 0; -} - -/* - * this is a function instead of a constant because of Suse - * defining HZ to be a function call to get_hz() - */ -static inline int pseudo_irq_period(void) -{ - return (HZ + 99) / 100; -} - -static void pseudo_irq_handler(struct timer_list *t) -{ - struct gpib_pseudo_irq *pseudo_irq = timer_container_of(pseudo_irq, t, - timer); - - if (pseudo_irq->handler) - pseudo_irq->handler(0, pseudo_irq->board); - else - pr_err("gpib: bug! pseudo_irq.handler is NULL\n"); - - if (atomic_read(&pseudo_irq->active)) - mod_timer(&pseudo_irq->timer, jiffies + pseudo_irq_period()); -} - -int gpib_request_pseudo_irq(struct gpib_board *board, irqreturn_t (*handler)(int, void *)) -{ - if (timer_pending(&board->pseudo_irq.timer) || board->pseudo_irq.handler) { - dev_err(board->gpib_dev, "only one pseudo interrupt per board allowed\n"); - return -1; - } - - board->pseudo_irq.handler = handler; - board->pseudo_irq.timer.function = pseudo_irq_handler; - board->pseudo_irq.board = board; - - atomic_set(&board->pseudo_irq.active, 1); - - mod_timer(&board->pseudo_irq.timer, jiffies + pseudo_irq_period()); - - return 0; -} -EXPORT_SYMBOL(gpib_request_pseudo_irq); - -void gpib_free_pseudo_irq(struct gpib_board *board) -{ - atomic_set(&board->pseudo_irq.active, 0); - - timer_delete_sync(&board->pseudo_irq.timer); - board->pseudo_irq.handler = NULL; -} -EXPORT_SYMBOL(gpib_free_pseudo_irq); - -static const unsigned int serial_timeout = 1000000; - -unsigned int num_status_bytes(const struct gpib_status_queue *dev) -{ - if (!dev) - return 0; - return dev->num_status_bytes; -} - -// push status byte onto back of status byte fifo -int push_status_byte(struct gpib_board *board, struct gpib_status_queue *device, u8 poll_byte) -{ - struct list_head *head = &device->status_bytes; - struct gpib_status_byte *status; - static const unsigned int max_num_status_bytes = 1024; - int retval; - - if (num_status_bytes(device) >= max_num_status_bytes) { - u8 lost_byte; - - device->dropped_byte = 1; - retval = pop_status_byte(board, device, &lost_byte); - if (retval < 0) - return retval; - } - - status = kmalloc(sizeof(*status), GFP_KERNEL); - if (!status) - return -ENOMEM; - - INIT_LIST_HEAD(&status->list); - status->poll_byte = poll_byte; - - list_add_tail(&status->list, head); - - device->num_status_bytes++; - - dev_dbg(board->gpib_dev, "pushed status byte 0x%x, %i in queue\n", - (int)poll_byte, num_status_bytes(device)); - - return 0; -} - -// pop status byte from front of status byte fifo -int pop_status_byte(struct gpib_board *board, struct gpib_status_queue *device, u8 *poll_byte) -{ - struct list_head *head = &device->status_bytes; - struct list_head *front = head->next; - struct gpib_status_byte *status; - - if (num_status_bytes(device) == 0) - return -EIO; - - if (front == head) - return -EIO; - - if (device->dropped_byte) { - device->dropped_byte = 0; - return -EPIPE; - } - - status = list_entry(front, struct gpib_status_byte, list); - *poll_byte = status->poll_byte; - - list_del(front); - kfree(status); - - device->num_status_bytes--; - - dev_dbg(board->gpib_dev, "popped status byte 0x%x, %i in queue\n", - (int)*poll_byte, num_status_bytes(device)); - - return 0; -} - -struct gpib_status_queue *get_gpib_status_queue(struct gpib_board *board, unsigned int pad, int sad) -{ - struct gpib_status_queue *device; - struct list_head *list_ptr; - const struct list_head *head = &board->device_list; - - for (list_ptr = head->next; list_ptr != head; list_ptr = list_ptr->next) { - device = list_entry(list_ptr, struct gpib_status_queue, list); - if (gpib_address_equal(device->pad, device->sad, pad, sad)) - return device; - } - - return NULL; -} - -int get_serial_poll_byte(struct gpib_board *board, unsigned int pad, int sad, - unsigned int usec_timeout, u8 *poll_byte) -{ - struct gpib_status_queue *device; - - device = get_gpib_status_queue(board, pad, sad); - if (num_status_bytes(device)) - return pop_status_byte(board, device, poll_byte); - else - return dvrsp(board, pad, sad, usec_timeout, poll_byte); -} - -int autopoll_all_devices(struct gpib_board *board) -{ - int retval; - - if (mutex_lock_interruptible(&board->user_mutex)) - return -ERESTARTSYS; - if (mutex_lock_interruptible(&board->big_gpib_mutex)) { - mutex_unlock(&board->user_mutex); - return -ERESTARTSYS; - } - - dev_dbg(board->gpib_dev, "autopoll has board lock\n"); - - retval = serial_poll_all(board, serial_timeout); - if (retval < 0) { - mutex_unlock(&board->big_gpib_mutex); - mutex_unlock(&board->user_mutex); - return retval; - } - - dev_dbg(board->gpib_dev, "complete\n"); - /* - * need to wake wait queue in case someone is - * waiting on RQS - */ - wake_up_interruptible(&board->wait); - mutex_unlock(&board->big_gpib_mutex); - mutex_unlock(&board->user_mutex); - - return retval; -} - -static int setup_serial_poll(struct gpib_board *board, unsigned int usec_timeout) -{ - u8 cmd_string[8]; - int i; - size_t bytes_written; - int ret; - - os_start_timer(board, usec_timeout); - ret = ibcac(board, 1, 1); - if (ret < 0) { - os_remove_timer(board); - return ret; - } - - i = 0; - cmd_string[i++] = UNL; - cmd_string[i++] = MLA(board->pad); /* controller's listen address */ - if (board->sad >= 0) - cmd_string[i++] = MSA(board->sad); - cmd_string[i++] = SPE; // serial poll enable - - ret = board->interface->command(board, cmd_string, i, &bytes_written); - if (ret < 0 || bytes_written < i) { - dev_dbg(board->gpib_dev, "failed to setup serial poll\n"); - os_remove_timer(board); - return -EIO; - } - os_remove_timer(board); - - return 0; -} - -static int read_serial_poll_byte(struct gpib_board *board, unsigned int pad, - int sad, unsigned int usec_timeout, u8 *result) -{ - u8 cmd_string[8]; - int end_flag; - int ret; - int i; - size_t nbytes; - - dev_dbg(board->gpib_dev, "entering pad=%i sad=%i\n", pad, sad); - - os_start_timer(board, usec_timeout); - ret = ibcac(board, 1, 1); - if (ret < 0) { - os_remove_timer(board); - return ret; - } - - i = 0; - // send talk address - cmd_string[i++] = MTA(pad); - if (sad >= 0) - cmd_string[i++] = MSA(sad); - - ret = board->interface->command(board, cmd_string, i, &nbytes); - if (ret < 0 || nbytes < i) { - dev_err(board->gpib_dev, "failed to setup serial poll\n"); - os_remove_timer(board); - return -EIO; - } - - ibgts(board); - - // read poll result - ret = board->interface->read(board, result, 1, &end_flag, &nbytes); - if (ret < 0 || nbytes < 1) { - dev_err(board->gpib_dev, "serial poll failed\n"); - os_remove_timer(board); - return -EIO; - } - os_remove_timer(board); - - return 0; -} - -static int cleanup_serial_poll(struct gpib_board *board, unsigned int usec_timeout) -{ - u8 cmd_string[8]; - int ret; - size_t bytes_written; - - os_start_timer(board, usec_timeout); - ret = ibcac(board, 1, 1); - if (ret < 0) { - os_remove_timer(board); - return ret; - } - - cmd_string[0] = SPD; /* disable serial poll bytes */ - cmd_string[1] = UNT; - ret = board->interface->command(board, cmd_string, 2, &bytes_written); - if (ret < 0 || bytes_written < 2) { - dev_err(board->gpib_dev, "failed to disable serial poll\n"); - os_remove_timer(board); - return -EIO; - } - os_remove_timer(board); - - return 0; -} - -static int serial_poll_single(struct gpib_board *board, unsigned int pad, int sad, - unsigned int usec_timeout, u8 *result) -{ - int retval, cleanup_retval; - - retval = setup_serial_poll(board, usec_timeout); - if (retval < 0) - return retval; - retval = read_serial_poll_byte(board, pad, sad, usec_timeout, result); - cleanup_retval = cleanup_serial_poll(board, usec_timeout); - if (retval < 0) - return retval; - if (cleanup_retval < 0) - return retval; - - return 0; -} - -int serial_poll_all(struct gpib_board *board, unsigned int usec_timeout) -{ - int retval = 0; - struct list_head *cur; - const struct list_head *head = NULL; - struct gpib_status_queue *device; - u8 result; - unsigned int num_bytes = 0; - - head = &board->device_list; - if (head->next == head) - return 0; - - retval = setup_serial_poll(board, usec_timeout); - if (retval < 0) - return retval; - - for (cur = head->next; cur != head; cur = cur->next) { - device = list_entry(cur, struct gpib_status_queue, list); - retval = read_serial_poll_byte(board, - device->pad, device->sad, usec_timeout, &result); - if (retval < 0) - continue; - if (result & request_service_bit) { - retval = push_status_byte(board, device, result); - if (retval < 0) - continue; - num_bytes++; - } - } - - retval = cleanup_serial_poll(board, usec_timeout); - if (retval < 0) - return retval; - - return num_bytes; -} - -/* - * DVRSP - * This function performs a serial poll of the device with primary - * address pad and secondary address sad. If the device has no - * secondary address, pass a negative number in for this argument. At the - * end of a successful serial poll the response is returned in result. - * SPD and UNT are sent at the completion of the poll. - */ - -int dvrsp(struct gpib_board *board, unsigned int pad, int sad, - unsigned int usec_timeout, u8 *result) -{ - int status = ibstatus(board); - int retval; - - if ((status & CIC) == 0) { - dev_err(board->gpib_dev, "not CIC during serial poll\n"); - return -1; - } - - if (pad > MAX_GPIB_PRIMARY_ADDRESS || sad > MAX_GPIB_SECONDARY_ADDRESS || sad < -1) { - dev_err(board->gpib_dev, "bad address for serial poll"); - return -1; - } - - retval = serial_poll_single(board, pad, sad, usec_timeout, result); - if (io_timed_out(board)) - retval = -ETIMEDOUT; - - return retval; -} - -static struct gpib_descriptor *handle_to_descriptor(const struct gpib_file_private *file_priv, - int handle) -{ - if (handle < 0 || handle >= GPIB_MAX_NUM_DESCRIPTORS) { - pr_err("gpib: invalid handle %i\n", handle); - return NULL; - } - - return file_priv->descriptors[handle]; -} - -static int init_gpib_file_private(struct gpib_file_private *priv) -{ - memset(priv, 0, sizeof(*priv)); - atomic_set(&priv->holding_mutex, 0); - priv->descriptors[0] = kmalloc(sizeof(struct gpib_descriptor), GFP_KERNEL); - if (!priv->descriptors[0]) { - pr_err("gpib: failed to allocate default board descriptor\n"); - return -ENOMEM; - } - init_gpib_descriptor(priv->descriptors[0]); - priv->descriptors[0]->is_board = 1; - mutex_init(&priv->descriptors_mutex); - return 0; -} - -int ibopen(struct inode *inode, struct file *filep) -{ - unsigned int minor = iminor(inode); - struct gpib_board *board; - struct gpib_file_private *priv; - - if (minor >= GPIB_MAX_NUM_BOARDS) { - pr_err("gpib: invalid minor number of device file\n"); - return -ENXIO; - } - - board = &board_array[minor]; - - filep->private_data = kmalloc(sizeof(struct gpib_file_private), GFP_KERNEL); - if (!filep->private_data) - return -ENOMEM; - - priv = filep->private_data; - init_gpib_file_private((struct gpib_file_private *)filep->private_data); - - if (board->use_count == 0) { - int retval; - - retval = request_module("gpib%i", minor); - if (retval) - dev_dbg(board->gpib_dev, "request module returned %i\n", retval); - } - if (board->interface) { - if (!try_module_get(board->provider_module)) { - dev_err(board->gpib_dev, "try_module_get() failed\n"); - return -EIO; - } - board->use_count++; - priv->got_module = 1; - } - return 0; -} - -int ibclose(struct inode *inode, struct file *filep) -{ - unsigned int minor = iminor(inode); - struct gpib_board *board; - struct gpib_file_private *priv = filep->private_data; - struct gpib_descriptor *desc; - - if (minor >= GPIB_MAX_NUM_BOARDS) { - pr_err("gpib: invalid minor number of device file\n"); - return -ENODEV; - } - - board = &board_array[minor]; - - if (priv) { - desc = handle_to_descriptor(priv, 0); - if (desc) { - if (desc->autopoll_enabled) { - dev_dbg(board->gpib_dev, "decrementing autospollers\n"); - if (board->autospollers > 0) - board->autospollers--; - else - dev_err(board->gpib_dev, - "Attempt to decrement zero autospollers\n"); - } - } else { - dev_err(board->gpib_dev, "Unexpected null gpib_descriptor\n"); - } - - cleanup_open_devices(priv, board); - - if (atomic_read(&priv->holding_mutex)) - mutex_unlock(&board->user_mutex); - - if (priv->got_module && board->use_count) { - module_put(board->provider_module); - --board->use_count; - } - - kfree(filep->private_data); - filep->private_data = NULL; - } - - return 0; -} - -long ibioctl(struct file *filep, unsigned int cmd, unsigned long arg) -{ - unsigned int minor = iminor(file_inode(filep)); - struct gpib_board *board; - struct gpib_file_private *file_priv = filep->private_data; - long retval = -ENOTTY; - - if (minor >= GPIB_MAX_NUM_BOARDS) { - pr_err("gpib: invalid minor number of device file\n"); - return -ENODEV; - } - board = &board_array[minor]; - - if (mutex_lock_interruptible(&board->big_gpib_mutex)) - return -ERESTARTSYS; - - dev_dbg(board->gpib_dev, "ioctl %d, interface=%s, use=%d, onl=%d\n", - cmd & 0xff, - board->interface ? board->interface->name : "", - board->use_count, - board->online); - - switch (cmd) { - case CFCBOARDTYPE: - retval = board_type_ioctl(file_priv, board, arg); - goto done; - case IBONL: - retval = online_ioctl(board, arg); - goto done; - default: - break; - } - if (!board->interface) { - dev_err(board->gpib_dev, "no gpib board configured\n"); - retval = -ENODEV; - goto done; - } - if (file_priv->got_module == 0) { - if (!try_module_get(board->provider_module)) { - dev_err(board->gpib_dev, "try_module_get() failed\n"); - retval = -EIO; - goto done; - } - file_priv->got_module = 1; - board->use_count++; - } - switch (cmd) { - case CFCBASE: - retval = iobase_ioctl(&board->config, arg); - goto done; - case CFCIRQ: - retval = irq_ioctl(&board->config, arg); - goto done; - case CFCDMA: - retval = dma_ioctl(&board->config, arg); - goto done; - case IBAUTOSPOLL: - retval = autospoll_ioctl(board, file_priv, arg); - goto done; - case IBBOARD_INFO: - retval = board_info_ioctl(board, arg); - goto done; - case IBMUTEX: - /* - * Need to unlock board->big_gpib_mutex before potentially locking board->user_mutex - * to maintain consistent locking order - */ - mutex_unlock(&board->big_gpib_mutex); - return mutex_ioctl(board, file_priv, arg); - case IBPAD: - retval = pad_ioctl(board, file_priv, arg); - goto done; - case IBSAD: - retval = sad_ioctl(board, file_priv, arg); - goto done; - case IBSELECT_PCI: - retval = select_pci_ioctl(&board->config, arg); - goto done; - case IBSELECT_DEVICE_PATH: - retval = select_device_path_ioctl(&board->config, arg); - goto done; - default: - break; - } - - if (!board->online) { - retval = -EINVAL; - goto done; - } - - switch (cmd) { - case IBEVENT: - retval = event_ioctl(board, arg); - goto done; - case IBCLOSEDEV: - retval = close_dev_ioctl(filep, board, arg); - goto done; - case IBOPENDEV: - retval = open_dev_ioctl(filep, board, arg); - goto done; - case IBSPOLL_BYTES: - retval = status_bytes_ioctl(board, arg); - goto done; - case IBWAIT: - retval = wait_ioctl(file_priv, board, arg); - if (retval == -ERESTARTSYS) - return retval; - goto done; - case IBLINES: - retval = line_status_ioctl(board, arg); - goto done; - case IBLOC: - board->interface->return_to_local(board); - retval = 0; - goto done; - default: - break; - } - - spin_lock(&board->locking_pid_spinlock); - if (current->pid != board->locking_pid) { - spin_unlock(&board->locking_pid_spinlock); - retval = -EPERM; - goto done; - } - spin_unlock(&board->locking_pid_spinlock); - - switch (cmd) { - case IB_T1_DELAY: - retval = t1_delay_ioctl(board, arg); - goto done; - case IBCAC: - retval = take_control_ioctl(board, arg); - goto done; - case IBCMD: - /* - * IO ioctls can take a long time, we need to unlock board->big_gpib_mutex - * before we call them. - */ - mutex_unlock(&board->big_gpib_mutex); - return command_ioctl(file_priv, board, arg); - case IBEOS: - retval = eos_ioctl(board, arg); - goto done; - case IBGTS: - retval = ibgts(board); - goto done; - case IBPPC: - retval = ppc_ioctl(board, arg); - goto done; - case IBPP2_SET: - retval = set_local_ppoll_mode_ioctl(board, arg); - goto done; - case IBPP2_GET: - retval = get_local_ppoll_mode_ioctl(board, arg); - goto done; - case IBQUERY_BOARD_RSV: - retval = query_board_rsv_ioctl(board, arg); - goto done; - case IBRD: - /* - * IO ioctls can take a long time, we need to unlock board->big_gpib_mutex - * before we call them. - */ - mutex_unlock(&board->big_gpib_mutex); - return read_ioctl(file_priv, board, arg); - case IBRPP: - retval = parallel_poll_ioctl(board, arg); - goto done; - case IBRSC: - retval = request_system_control_ioctl(board, arg); - goto done; - case IBRSP: - retval = serial_poll_ioctl(board, arg); - goto done; - case IBRSV: - retval = request_service_ioctl(board, arg); - goto done; - case IBRSV2: - retval = request_service2_ioctl(board, arg); - goto done; - case IBSIC: - retval = interface_clear_ioctl(board, arg); - goto done; - case IBSRE: - retval = remote_enable_ioctl(board, arg); - goto done; - case IBTMO: - retval = timeout_ioctl(board, arg); - goto done; - case IBWRT: - /* - * IO ioctls can take a long time, we need to unlock board->big_gpib_mutex - * before we call them. - */ - mutex_unlock(&board->big_gpib_mutex); - return write_ioctl(file_priv, board, arg); - default: - retval = -ENOTTY; - goto done; - } - -done: - mutex_unlock(&board->big_gpib_mutex); - dev_dbg(board->gpib_dev, "ioctl done status = 0x%lx\n", board->status); - return retval; -} - -static int board_type_ioctl(struct gpib_file_private *file_priv, - struct gpib_board *board, unsigned long arg) -{ - struct list_head *list_ptr; - struct gpib_board_type_ioctl cmd; - int retval; - - if (!capable(CAP_SYS_ADMIN)) - return -EPERM; - if (board->online) - return -EBUSY; - - retval = copy_from_user(&cmd, (void __user *)arg, - sizeof(struct gpib_board_type_ioctl)); - if (retval) - return -EFAULT; - - for (list_ptr = registered_drivers.next; list_ptr != ®istered_drivers; - list_ptr = list_ptr->next) { - struct gpib_interface_list *entry; - - entry = list_entry(list_ptr, struct gpib_interface_list, list); - if (strcmp(entry->interface->name, cmd.name) == 0) { - int i; - int had_module = file_priv->got_module; - - if (board->use_count) { - for (i = 0; i < board->use_count; ++i) - module_put(board->provider_module); - board->interface = NULL; - file_priv->got_module = 0; - } - board->interface = entry->interface; - board->provider_module = entry->module; - for (i = 0; i < board->use_count; ++i) { - if (!try_module_get(entry->module)) { - board->use_count = i; - return -EIO; - } - } - if (had_module == 0) { - if (!try_module_get(entry->module)) - return -EIO; - ++board->use_count; - } - file_priv->got_module = 1; - return 0; - } - } - - return -EINVAL; -} - -static int read_ioctl(struct gpib_file_private *file_priv, struct gpib_board *board, - unsigned long arg) -{ - struct gpib_read_write_ioctl read_cmd; - u8 __user *userbuf; - unsigned long remain; - int end_flag = 0; - int retval; - ssize_t read_ret = 0; - struct gpib_descriptor *desc; - size_t nbytes; - - retval = copy_from_user(&read_cmd, (void __user *)arg, sizeof(read_cmd)); - if (retval) - return -EFAULT; - - if (read_cmd.completed_transfer_count > read_cmd.requested_transfer_count) - return -EINVAL; - - desc = handle_to_descriptor(file_priv, read_cmd.handle); - if (!desc) - return -EINVAL; - - if (WARN_ON_ONCE(sizeof(userbuf) > sizeof(read_cmd.buffer_ptr))) - return -EFAULT; - - userbuf = (u8 __user *)(unsigned long)read_cmd.buffer_ptr; - userbuf += read_cmd.completed_transfer_count; - - remain = read_cmd.requested_transfer_count - read_cmd.completed_transfer_count; - - /* Check write access to buffer */ - if (!access_ok(userbuf, remain)) - return -EFAULT; - - atomic_set(&desc->io_in_progress, 1); - - /* Read buffer loads till we fill the user supplied buffer */ - while (remain > 0 && end_flag == 0) { - nbytes = 0; - read_ret = ibrd(board, board->buffer, (board->buffer_length < remain) ? - board->buffer_length : remain, &end_flag, &nbytes); - if (nbytes == 0) - break; - retval = copy_to_user(userbuf, board->buffer, nbytes); - if (retval) { - retval = -EFAULT; - break; - } - remain -= nbytes; - userbuf += nbytes; - if (read_ret < 0) - break; - } - read_cmd.completed_transfer_count = read_cmd.requested_transfer_count - remain; - read_cmd.end = end_flag; - /* - * suppress errors (for example due to timeout or interruption by device clear) - * if all bytes got sent. This prevents races that can occur in the various drivers - * if a device receives a device clear immediately after a transfer completes and - * the driver code wasn't careful enough to handle that case. - */ - if (remain == 0 || end_flag) - read_ret = 0; - if (retval == 0) - retval = copy_to_user((void __user *)arg, &read_cmd, sizeof(read_cmd)); - - atomic_set(&desc->io_in_progress, 0); - - wake_up_interruptible(&board->wait); - if (retval) - return -EFAULT; - - return read_ret; -} - -static int command_ioctl(struct gpib_file_private *file_priv, - struct gpib_board *board, unsigned long arg) -{ - struct gpib_read_write_ioctl cmd; - u8 __user *userbuf; - unsigned long remain; - int retval; - int fault = 0; - struct gpib_descriptor *desc; - size_t bytes_written; - int no_clear_io_in_prog; - - retval = copy_from_user(&cmd, (void __user *)arg, sizeof(cmd)); - if (retval) - return -EFAULT; - - if (cmd.completed_transfer_count > cmd.requested_transfer_count) - return -EINVAL; - - desc = handle_to_descriptor(file_priv, cmd.handle); - if (!desc) - return -EINVAL; - - userbuf = (u8 __user *)(unsigned long)cmd.buffer_ptr; - userbuf += cmd.completed_transfer_count; - - no_clear_io_in_prog = cmd.end; - cmd.end = 0; - - remain = cmd.requested_transfer_count - cmd.completed_transfer_count; - - /* Check read access to buffer */ - if (!access_ok(userbuf, remain)) - return -EFAULT; - - /* - * Write buffer loads till we empty the user supplied buffer. - * Call drivers at least once, even if remain is zero, in - * order to allow them to insure previous commands were - * completely finished, in the case of a restarted ioctl. - */ - - atomic_set(&desc->io_in_progress, 1); - - do { - fault = copy_from_user(board->buffer, userbuf, (board->buffer_length < remain) ? - board->buffer_length : remain); - if (fault) { - retval = -EFAULT; - bytes_written = 0; - } else { - retval = ibcmd(board, board->buffer, (board->buffer_length < remain) ? - board->buffer_length : remain, &bytes_written); - } - remain -= bytes_written; - userbuf += bytes_written; - if (retval < 0) { - atomic_set(&desc->io_in_progress, 0); - - wake_up_interruptible(&board->wait); - break; - } - } while (remain > 0); - - cmd.completed_transfer_count = cmd.requested_transfer_count - remain; - - if (fault == 0) - fault = copy_to_user((void __user *)arg, &cmd, sizeof(cmd)); - - /* - * no_clear_io_in_prog (cmd.end) is true when io_in_progress should - * not be set to zero because the cmd in progress is the address setup - * operation for an async read or write. This causes CMPL not to be set - * in general_ibstatus until the async read or write completes. - */ - if (!no_clear_io_in_prog || fault) - atomic_set(&desc->io_in_progress, 0); - - wake_up_interruptible(&board->wait); - if (fault) - return -EFAULT; - - return retval; -} - -static int write_ioctl(struct gpib_file_private *file_priv, struct gpib_board *board, - unsigned long arg) -{ - struct gpib_read_write_ioctl write_cmd; - u8 __user *userbuf; - unsigned long remain; - int retval = 0; - int fault; - struct gpib_descriptor *desc; - - fault = copy_from_user(&write_cmd, (void __user *)arg, sizeof(write_cmd)); - if (fault) - return -EFAULT; - - if (write_cmd.completed_transfer_count > write_cmd.requested_transfer_count) - return -EINVAL; - - desc = handle_to_descriptor(file_priv, write_cmd.handle); - if (!desc) - return -EINVAL; - - userbuf = (u8 __user *)(unsigned long)write_cmd.buffer_ptr; - userbuf += write_cmd.completed_transfer_count; - - remain = write_cmd.requested_transfer_count - write_cmd.completed_transfer_count; - - /* Check read access to buffer */ - if (!access_ok(userbuf, remain)) - return -EFAULT; - - atomic_set(&desc->io_in_progress, 1); - - /* Write buffer loads till we empty the user supplied buffer */ - while (remain > 0) { - int send_eoi; - size_t bytes_written = 0; - - send_eoi = remain <= board->buffer_length && write_cmd.end; - fault = copy_from_user(board->buffer, userbuf, (board->buffer_length < remain) ? - board->buffer_length : remain); - if (fault) { - retval = -EFAULT; - break; - } - retval = ibwrt(board, board->buffer, (board->buffer_length < remain) ? - board->buffer_length : remain, send_eoi, &bytes_written); - remain -= bytes_written; - userbuf += bytes_written; - if (retval < 0) - break; - } - write_cmd.completed_transfer_count = write_cmd.requested_transfer_count - remain; - /* - * suppress errors (for example due to timeout or interruption by device clear) - * if all bytes got sent. This prevents races that can occur in the various drivers - * if a device receives a device clear immediately after a transfer completes and - * the driver code wasn't careful enough to handle that case. - */ - if (remain == 0) - retval = 0; - if (fault == 0) - fault = copy_to_user((void __user *)arg, &write_cmd, sizeof(write_cmd)); - - atomic_set(&desc->io_in_progress, 0); - - wake_up_interruptible(&board->wait); - if (fault) - return -EFAULT; - - return retval; -} - -static int status_bytes_ioctl(struct gpib_board *board, unsigned long arg) -{ - struct gpib_status_queue *device; - struct gpib_spoll_bytes_ioctl cmd; - int retval; - - retval = copy_from_user(&cmd, (void __user *)arg, sizeof(cmd)); - if (retval) - return -EFAULT; - - device = get_gpib_status_queue(board, cmd.pad, cmd.sad); - if (!device) - cmd.num_bytes = 0; - else - cmd.num_bytes = num_status_bytes(device); - - retval = copy_to_user((void __user *)arg, &cmd, sizeof(cmd)); - if (retval) - return -EFAULT; - - return 0; -} - -static int increment_open_device_count(struct gpib_board *board, struct list_head *head, - unsigned int pad, int sad) -{ - struct list_head *list_ptr; - struct gpib_status_queue *device; - - /* - * first see if address has already been opened, then increment - * open count - */ - for (list_ptr = head->next; list_ptr != head; list_ptr = list_ptr->next) { - device = list_entry(list_ptr, struct gpib_status_queue, list); - if (gpib_address_equal(device->pad, device->sad, pad, sad)) { - dev_dbg(board->gpib_dev, "incrementing open count for pad %i, sad %i\n", - device->pad, device->sad); - device->reference_count++; - return 0; - } - } - - /* otherwise we need to allocate a new struct gpib_status_queue */ - device = kmalloc(sizeof(struct gpib_status_queue), GFP_ATOMIC); - if (!device) - return -ENOMEM; - init_gpib_status_queue(device); - device->pad = pad; - device->sad = sad; - device->reference_count = 1; - - list_add(&device->list, head); - - dev_dbg(board->gpib_dev, "opened pad %i, sad %i\n", device->pad, device->sad); - - return 0; -} - -static int subtract_open_device_count(struct gpib_board *board, struct list_head *head, - unsigned int pad, int sad, unsigned int count) -{ - struct gpib_status_queue *device; - struct list_head *list_ptr; - - for (list_ptr = head->next; list_ptr != head; list_ptr = list_ptr->next) { - device = list_entry(list_ptr, struct gpib_status_queue, list); - if (gpib_address_equal(device->pad, device->sad, pad, sad)) { - dev_dbg(board->gpib_dev, "decrementing open count for pad %i, sad %i\n", - device->pad, device->sad); - if (count > device->reference_count) { - dev_err(board->gpib_dev, "bug! in %s()\n", __func__); - return -EINVAL; - } - device->reference_count -= count; - if (device->reference_count == 0) { - dev_dbg(board->gpib_dev, "closing pad %i, sad %i\n", - device->pad, device->sad); - list_del(list_ptr); - kfree(device); - } - return 0; - } - } - dev_err(board->gpib_dev, "bug! tried to close address that was never opened!\n"); - return -EINVAL; -} - -static inline int decrement_open_device_count(struct gpib_board *board, struct list_head *head, - unsigned int pad, int sad) -{ - return subtract_open_device_count(board, head, pad, sad, 1); -} - -static int cleanup_open_devices(struct gpib_file_private *file_priv, struct gpib_board *board) -{ - int retval = 0; - int i; - - for (i = 0; i < GPIB_MAX_NUM_DESCRIPTORS; i++) { - struct gpib_descriptor *desc; - - desc = file_priv->descriptors[i]; - if (!desc) - continue; - - if (desc->is_board == 0) { - retval = decrement_open_device_count(board, &board->device_list, desc->pad, - desc->sad); - if (retval < 0) - return retval; - } - kfree(desc); - file_priv->descriptors[i] = NULL; - } - - return 0; -} - -static int open_dev_ioctl(struct file *filep, struct gpib_board *board, unsigned long arg) -{ - struct gpib_open_dev_ioctl open_dev_cmd; - int retval; - struct gpib_file_private *file_priv = filep->private_data; - int i; - - retval = copy_from_user(&open_dev_cmd, (void __user *)arg, sizeof(open_dev_cmd)); - if (retval) - return -EFAULT; - - if (mutex_lock_interruptible(&file_priv->descriptors_mutex)) - return -ERESTARTSYS; - for (i = 0; i < GPIB_MAX_NUM_DESCRIPTORS; i++) - if (!file_priv->descriptors[i]) - break; - if (i == GPIB_MAX_NUM_DESCRIPTORS) { - mutex_unlock(&file_priv->descriptors_mutex); - return -ERANGE; - } - file_priv->descriptors[i] = kmalloc(sizeof(struct gpib_descriptor), GFP_KERNEL); - if (!file_priv->descriptors[i]) { - mutex_unlock(&file_priv->descriptors_mutex); - return -ENOMEM; - } - init_gpib_descriptor(file_priv->descriptors[i]); - - file_priv->descriptors[i]->pad = open_dev_cmd.pad; - file_priv->descriptors[i]->sad = open_dev_cmd.sad; - file_priv->descriptors[i]->is_board = open_dev_cmd.is_board; - mutex_unlock(&file_priv->descriptors_mutex); - - retval = increment_open_device_count(board, &board->device_list, open_dev_cmd.pad, - open_dev_cmd.sad); - if (retval < 0) - return retval; - - /* - * clear stuck srq state, since we may be able to find service request on - * the new device - */ - atomic_set(&board->stuck_srq, 0); - - open_dev_cmd.handle = i; - retval = copy_to_user((void __user *)arg, &open_dev_cmd, sizeof(open_dev_cmd)); - if (retval) - return -EFAULT; - - return 0; -} - -static int close_dev_ioctl(struct file *filep, struct gpib_board *board, unsigned long arg) -{ - struct gpib_close_dev_ioctl cmd; - struct gpib_file_private *file_priv = filep->private_data; - int retval; - - retval = copy_from_user(&cmd, (void __user *)arg, sizeof(cmd)); - if (retval) - return -EFAULT; - - if (cmd.handle >= GPIB_MAX_NUM_DESCRIPTORS) - return -EINVAL; - if (!file_priv->descriptors[cmd.handle]) - return -EINVAL; - - retval = decrement_open_device_count(board, &board->device_list, - file_priv->descriptors[cmd.handle]->pad, - file_priv->descriptors[cmd.handle]->sad); - if (retval < 0) - return retval; - - kfree(file_priv->descriptors[cmd.handle]); - file_priv->descriptors[cmd.handle] = NULL; - - return 0; -} - -static int serial_poll_ioctl(struct gpib_board *board, unsigned long arg) -{ - struct gpib_serial_poll_ioctl serial_cmd; - int retval; - - retval = copy_from_user(&serial_cmd, (void __user *)arg, sizeof(serial_cmd)); - if (retval) - return -EFAULT; - - retval = get_serial_poll_byte(board, serial_cmd.pad, serial_cmd.sad, board->usec_timeout, - &serial_cmd.status_byte); - if (retval < 0) - return retval; - - retval = copy_to_user((void __user *)arg, &serial_cmd, sizeof(serial_cmd)); - if (retval) - return -EFAULT; - - return 0; -} - -static int wait_ioctl(struct gpib_file_private *file_priv, struct gpib_board *board, - unsigned long arg) -{ - struct gpib_wait_ioctl wait_cmd; - int retval; - struct gpib_descriptor *desc; - - retval = copy_from_user(&wait_cmd, (void __user *)arg, sizeof(wait_cmd)); - if (retval) - return -EFAULT; - - desc = handle_to_descriptor(file_priv, wait_cmd.handle); - if (!desc) - return -EINVAL; - - retval = ibwait(board, wait_cmd.wait_mask, wait_cmd.clear_mask, - wait_cmd.set_mask, &wait_cmd.ibsta, wait_cmd.usec_timeout, desc); - if (retval < 0) - return retval; - - retval = copy_to_user((void __user *)arg, &wait_cmd, sizeof(wait_cmd)); - if (retval) - return -EFAULT; - - return 0; -} - -static int parallel_poll_ioctl(struct gpib_board *board, unsigned long arg) -{ - u8 poll_byte; - int retval; - - retval = ibrpp(board, &poll_byte); - if (retval < 0) - return retval; - - retval = copy_to_user((void __user *)arg, &poll_byte, sizeof(poll_byte)); - if (retval) - return -EFAULT; - - return 0; -} - -static int online_ioctl(struct gpib_board *board, unsigned long arg) -{ - struct gpib_online_ioctl online_cmd; - int retval; - void __user *init_data = NULL; - - board->config.init_data = NULL; - - if (!capable(CAP_SYS_ADMIN)) - return -EPERM; - - retval = copy_from_user(&online_cmd, (void __user *)arg, sizeof(online_cmd)); - if (retval) - return -EFAULT; - if (online_cmd.init_data_length > 0) { - board->config.init_data = vmalloc(online_cmd.init_data_length); - if (!board->config.init_data) - return -ENOMEM; - if (WARN_ON_ONCE(sizeof(init_data) > sizeof(online_cmd.init_data_ptr))) - return -EFAULT; - init_data = (void __user *)(unsigned long)(online_cmd.init_data_ptr); - retval = copy_from_user(board->config.init_data, init_data, - online_cmd.init_data_length); - if (retval) { - vfree(board->config.init_data); - return -EFAULT; - } - board->config.init_data_length = online_cmd.init_data_length; - } else { - board->config.init_data = NULL; - board->config.init_data_length = 0; - } - if (online_cmd.online) - retval = ibonline(board); - else - retval = iboffline(board); - if (board->config.init_data) { - vfree(board->config.init_data); - board->config.init_data = NULL; - board->config.init_data_length = 0; - } - return retval; -} - -static int remote_enable_ioctl(struct gpib_board *board, unsigned long arg) -{ - int enable; - int retval; - - retval = copy_from_user(&enable, (void __user *)arg, sizeof(enable)); - if (retval) - return -EFAULT; - - return ibsre(board, enable); -} - -static int take_control_ioctl(struct gpib_board *board, unsigned long arg) -{ - int synchronous; - int retval; - - retval = copy_from_user(&synchronous, (void __user *)arg, sizeof(synchronous)); - if (retval) - return -EFAULT; - - return ibcac(board, synchronous, 1); -} - -static int line_status_ioctl(struct gpib_board *board, unsigned long arg) -{ - short lines; - int retval; - - retval = iblines(board, &lines); - if (retval < 0) - return retval; - - retval = copy_to_user((void __user *)arg, &lines, sizeof(lines)); - if (retval) - return -EFAULT; - - return 0; -} - -static int pad_ioctl(struct gpib_board *board, struct gpib_file_private *file_priv, - unsigned long arg) -{ - struct gpib_pad_ioctl cmd; - int retval; - struct gpib_descriptor *desc; - - retval = copy_from_user(&cmd, (void __user *)arg, sizeof(cmd)); - if (retval) - return -EFAULT; - - desc = handle_to_descriptor(file_priv, cmd.handle); - if (!desc) - return -EINVAL; - - if (desc->is_board) { - retval = ibpad(board, cmd.pad); - if (retval < 0) - return retval; - } else { - retval = decrement_open_device_count(board, &board->device_list, desc->pad, - desc->sad); - if (retval < 0) - return retval; - - desc->pad = cmd.pad; - - retval = increment_open_device_count(board, &board->device_list, desc->pad, - desc->sad); - if (retval < 0) - return retval; - } - - return 0; -} - -static int sad_ioctl(struct gpib_board *board, struct gpib_file_private *file_priv, - unsigned long arg) -{ - struct gpib_sad_ioctl cmd; - int retval; - struct gpib_descriptor *desc; - - retval = copy_from_user(&cmd, (void __user *)arg, sizeof(cmd)); - if (retval) - return -EFAULT; - - desc = handle_to_descriptor(file_priv, cmd.handle); - if (!desc) - return -EINVAL; - - if (desc->is_board) { - retval = ibsad(board, cmd.sad); - if (retval < 0) - return retval; - } else { - retval = decrement_open_device_count(board, &board->device_list, desc->pad, - desc->sad); - if (retval < 0) - return retval; - - desc->sad = cmd.sad; - - retval = increment_open_device_count(board, &board->device_list, desc->pad, - desc->sad); - if (retval < 0) - return retval; - } - return 0; -} - -static int eos_ioctl(struct gpib_board *board, unsigned long arg) -{ - struct gpib_eos_ioctl eos_cmd; - int retval; - - retval = copy_from_user(&eos_cmd, (void __user *)arg, sizeof(eos_cmd)); - if (retval) - return -EFAULT; - - return ibeos(board, eos_cmd.eos, eos_cmd.eos_flags); -} - -static int request_service_ioctl(struct gpib_board *board, unsigned long arg) -{ - u8 status_byte; - int retval; - - retval = copy_from_user(&status_byte, (void __user *)arg, sizeof(status_byte)); - if (retval) - return -EFAULT; - - return ibrsv2(board, status_byte, status_byte & request_service_bit); -} - -static int request_service2_ioctl(struct gpib_board *board, unsigned long arg) -{ - struct gpib_request_service2 request_service2_cmd; - int retval; - - retval = copy_from_user(&request_service2_cmd, (void __user *)arg, - sizeof(struct gpib_request_service2)); - if (retval) - return -EFAULT; - - return ibrsv2(board, request_service2_cmd.status_byte, - request_service2_cmd.new_reason_for_service); -} - -static int iobase_ioctl(struct gpib_board_config *config, unsigned long arg) -{ - u64 base_addr; - int retval; - - if (!capable(CAP_SYS_ADMIN)) - return -EPERM; - - retval = copy_from_user(&base_addr, (void __user *)arg, sizeof(base_addr)); - if (retval) - return -EFAULT; - - if (WARN_ON_ONCE(sizeof(void *) > sizeof(base_addr))) - return -EFAULT; - config->ibbase = base_addr; - - return 0; -} - -static int irq_ioctl(struct gpib_board_config *config, unsigned long arg) -{ - unsigned int irq; - int retval; - - if (!capable(CAP_SYS_ADMIN)) - return -EPERM; - - retval = copy_from_user(&irq, (void __user *)arg, sizeof(irq)); - if (retval) - return -EFAULT; - - config->ibirq = irq; - - return 0; -} - -static int dma_ioctl(struct gpib_board_config *config, unsigned long arg) -{ - unsigned int dma_channel; - int retval; - - if (!capable(CAP_SYS_ADMIN)) - return -EPERM; - - retval = copy_from_user(&dma_channel, (void __user *)arg, sizeof(dma_channel)); - if (retval) - return -EFAULT; - - config->ibdma = dma_channel; - - return 0; -} - -static int autospoll_ioctl(struct gpib_board *board, struct gpib_file_private *file_priv, - unsigned long arg) -{ - short enable; - int retval; - struct gpib_descriptor *desc; - - retval = copy_from_user(&enable, (void __user *)arg, sizeof(enable)); - if (retval) - return -EFAULT; - - desc = handle_to_descriptor(file_priv, 0); /* board handle is 0 */ - - if (enable) { - if (!desc->autopoll_enabled) { - board->autospollers++; - desc->autopoll_enabled = 1; - } - retval = 0; - } else { - if (desc->autopoll_enabled) { - desc->autopoll_enabled = 0; - if (board->autospollers > 0) { - board->autospollers--; - retval = 0; - } else { - dev_err(board->gpib_dev, - "tried to set number of autospollers negative\n"); - retval = -EINVAL; - } - } else { - dev_err(board->gpib_dev, "autopoll disable requested before enable\n"); - retval = -EINVAL; - } - } - return retval; -} - -static int mutex_ioctl(struct gpib_board *board, struct gpib_file_private *file_priv, - unsigned long arg) -{ - int retval, lock_mutex; - - retval = copy_from_user(&lock_mutex, (void __user *)arg, sizeof(lock_mutex)); - if (retval) - return -EFAULT; - - if (lock_mutex) { - retval = mutex_lock_interruptible(&board->user_mutex); - if (retval) - return -ERESTARTSYS; - - spin_lock(&board->locking_pid_spinlock); - board->locking_pid = current->pid; - spin_unlock(&board->locking_pid_spinlock); - - atomic_set(&file_priv->holding_mutex, 1); - - dev_dbg(board->gpib_dev, "locked board mutex\n"); - } else { - spin_lock(&board->locking_pid_spinlock); - if (current->pid != board->locking_pid) { - dev_err(board->gpib_dev, "bug! pid %i tried to release mutex held by pid %i\n", - current->pid, board->locking_pid); - spin_unlock(&board->locking_pid_spinlock); - return -EPERM; - } - board->locking_pid = 0; - spin_unlock(&board->locking_pid_spinlock); - - atomic_set(&file_priv->holding_mutex, 0); - - mutex_unlock(&board->user_mutex); - dev_dbg(board->gpib_dev, "unlocked board mutex\n"); - } - return 0; -} - -static int timeout_ioctl(struct gpib_board *board, unsigned long arg) -{ - unsigned int timeout; - int retval; - - retval = copy_from_user(&timeout, (void __user *)arg, sizeof(timeout)); - if (retval) - return -EFAULT; - - board->usec_timeout = timeout; - dev_dbg(board->gpib_dev, "timeout set to %i usec\n", timeout); - - return 0; -} - -static int ppc_ioctl(struct gpib_board *board, unsigned long arg) -{ - struct gpib_ppoll_config_ioctl cmd; - int retval; - - retval = copy_from_user(&cmd, (void __user *)arg, sizeof(cmd)); - if (retval) - return -EFAULT; - - if (cmd.set_ist) { - board->ist = 1; - board->interface->parallel_poll_response(board, board->ist); - } else if (cmd.clear_ist) { - board->ist = 0; - board->interface->parallel_poll_response(board, board->ist); - } - - if (cmd.config) { - retval = ibppc(board, cmd.config); - if (retval < 0) - return retval; - } - - return 0; -} - -static int set_local_ppoll_mode_ioctl(struct gpib_board *board, unsigned long arg) -{ - short cmd; - int retval; - - retval = copy_from_user(&cmd, (void __user *)arg, sizeof(cmd)); - if (retval) - return -EFAULT; - - if (!board->interface->local_parallel_poll_mode) - return -ENOENT; - board->local_ppoll_mode = cmd != 0; - board->interface->local_parallel_poll_mode(board, board->local_ppoll_mode); - - return 0; -} - -static int get_local_ppoll_mode_ioctl(struct gpib_board *board, unsigned long arg) -{ - short cmd; - int retval; - - cmd = board->local_ppoll_mode; - retval = copy_to_user((void __user *)arg, &cmd, sizeof(cmd)); - if (retval) - return -EFAULT; - - return 0; -} - -static int query_board_rsv_ioctl(struct gpib_board *board, unsigned long arg) -{ - int status; - int retval; - - status = board->interface->serial_poll_status(board); - - retval = copy_to_user((void __user *)arg, &status, sizeof(status)); - if (retval) - return -EFAULT; - - return 0; -} - -static int board_info_ioctl(const struct gpib_board *board, unsigned long arg) -{ - struct gpib_board_info_ioctl info = { }; - int retval; - - info.pad = board->pad; - info.sad = board->sad; - info.parallel_poll_configuration = board->parallel_poll_configuration; - info.is_system_controller = board->master; - if (board->autospollers) - info.autopolling = 1; - else - info.autopolling = 0; - info.t1_delay = board->t1_nano_sec; - info.ist = board->ist; - info.no_7_bit_eos = board->interface->no_7_bit_eos; - retval = copy_to_user((void __user *)arg, &info, sizeof(info)); - if (retval) - return -EFAULT; - - return 0; -} - -static int interface_clear_ioctl(struct gpib_board *board, unsigned long arg) -{ - unsigned int usec_duration; - int retval; - - retval = copy_from_user(&usec_duration, (void __user *)arg, sizeof(usec_duration)); - if (retval) - return -EFAULT; - - return ibsic(board, usec_duration); -} - -static int select_pci_ioctl(struct gpib_board_config *config, unsigned long arg) -{ - struct gpib_select_pci_ioctl selection; - int retval; - - if (!capable(CAP_SYS_ADMIN)) - return -EPERM; - - retval = copy_from_user(&selection, (void __user *)arg, sizeof(selection)); - if (retval) - return -EFAULT; - - config->pci_bus = selection.pci_bus; - config->pci_slot = selection.pci_slot; - - return 0; -} - -static int select_device_path_ioctl(struct gpib_board_config *config, unsigned long arg) -{ - struct gpib_select_device_path_ioctl *selection; - int retval; - - if (!capable(CAP_SYS_ADMIN)) - return -EPERM; - - selection = vmalloc(sizeof(struct gpib_select_device_path_ioctl)); - if (!selection) - return -ENOMEM; - - retval = copy_from_user(selection, (void __user *)arg, - sizeof(struct gpib_select_device_path_ioctl)); - if (retval) { - vfree(selection); - return -EFAULT; - } - - selection->device_path[sizeof(selection->device_path) - 1] = '\0'; - kfree(config->device_path); - config->device_path = NULL; - if (strlen(selection->device_path) > 0) - config->device_path = kstrdup(selection->device_path, GFP_KERNEL); - - vfree(selection); - return 0; -} - -unsigned int num_gpib_events(const struct gpib_event_queue *queue) -{ - return queue->num_events; -} - -static int push_gpib_event_nolock(struct gpib_board *board, short event_type) -{ - struct gpib_event_queue *queue = &board->event_queue; - struct list_head *head = &queue->event_head; - struct gpib_event *event; - static const unsigned int max_num_events = 1024; - int retval; - - if (num_gpib_events(queue) >= max_num_events) { - short lost_event; - - queue->dropped_event = 1; - retval = pop_gpib_event_nolock(board, queue, &lost_event); - if (retval < 0) - return retval; - } - - event = kmalloc(sizeof(struct gpib_event), GFP_ATOMIC); - if (!event) { - queue->dropped_event = 1; - dev_err(board->gpib_dev, "failed to allocate memory for event\n"); - return -ENOMEM; - } - - INIT_LIST_HEAD(&event->list); - event->event_type = event_type; - - list_add_tail(&event->list, head); - - queue->num_events++; - - dev_dbg(board->gpib_dev, "pushed event %i, %i in queue\n", - (int)event_type, num_gpib_events(queue)); - - return 0; -} - -// push event onto back of event queue -int push_gpib_event(struct gpib_board *board, short event_type) -{ - unsigned long flags; - int retval; - - spin_lock_irqsave(&board->event_queue.lock, flags); - retval = push_gpib_event_nolock(board, event_type); - spin_unlock_irqrestore(&board->event_queue.lock, flags); - - if (event_type == EVENT_DEV_TRG) - board->status |= DTAS; - if (event_type == EVENT_DEV_CLR) - board->status |= DCAS; - - return retval; -} -EXPORT_SYMBOL(push_gpib_event); - -static int pop_gpib_event_nolock(struct gpib_board *board, - struct gpib_event_queue *queue, short *event_type) -{ - struct list_head *head = &queue->event_head; - struct list_head *front = head->next; - struct gpib_event *event; - - if (num_gpib_events(queue) == 0) { - *event_type = EVENT_NONE; - return 0; - } - - if (front == head) - return -EIO; - - if (queue->dropped_event) { - queue->dropped_event = 0; - return -EPIPE; - } - - event = list_entry(front, struct gpib_event, list); - *event_type = event->event_type; - - list_del(front); - kfree(event); - - queue->num_events--; - - dev_dbg(board->gpib_dev, "popped event %i, %i in queue\n", - (int)*event_type, num_gpib_events(queue)); - - return 0; -} - -// pop event from front of event queue -int pop_gpib_event(struct gpib_board *board, struct gpib_event_queue *queue, short *event_type) -{ - unsigned long flags; - int retval; - - spin_lock_irqsave(&queue->lock, flags); - retval = pop_gpib_event_nolock(board, queue, event_type); - spin_unlock_irqrestore(&queue->lock, flags); - return retval; -} - -static int event_ioctl(struct gpib_board *board, unsigned long arg) -{ - short user_event; - int retval; - short event; - - retval = pop_gpib_event(board, &board->event_queue, &event); - if (retval < 0) - return retval; - - user_event = event; - - retval = copy_to_user((void __user *)arg, &user_event, sizeof(user_event)); - if (retval) - return -EFAULT; - - return 0; -} - -static int request_system_control_ioctl(struct gpib_board *board, unsigned long arg) -{ - int request_control; - int retval; - - retval = copy_from_user(&request_control, (void __user *)arg, sizeof(request_control)); - if (retval) - return -EFAULT; - - return ibrsc(board, request_control); -} - -static int t1_delay_ioctl(struct gpib_board *board, unsigned long arg) -{ - unsigned int cmd; - unsigned int delay; - int retval; - - if (!board->interface->t1_delay) - return -ENOENT; - - retval = copy_from_user(&cmd, (void __user *)arg, sizeof(cmd)); - if (retval) - return -EFAULT; - - delay = cmd; - - retval = board->interface->t1_delay(board, delay); - if (retval < 0) - return retval; - - board->t1_nano_sec = retval; - return 0; -} - -static const struct file_operations ib_fops = { - .owner = THIS_MODULE, - .llseek = NULL, - .unlocked_ioctl = &ibioctl, - .compat_ioctl = &ibioctl, - .open = &ibopen, - .release = &ibclose, -}; - -struct gpib_board board_array[GPIB_MAX_NUM_BOARDS]; - -LIST_HEAD(registered_drivers); - -void init_gpib_descriptor(struct gpib_descriptor *desc) -{ - desc->pad = 0; - desc->sad = -1; - desc->is_board = 0; - desc->autopoll_enabled = 0; - atomic_set(&desc->io_in_progress, 0); -} - -int gpib_register_driver(struct gpib_interface *interface, struct module *provider_module) -{ - struct gpib_interface_list *entry; - - entry = kmalloc(sizeof(*entry), GFP_KERNEL); - if (!entry) - return -ENOMEM; - - entry->interface = interface; - entry->module = provider_module; - list_add(&entry->list, ®istered_drivers); - - return 0; -} -EXPORT_SYMBOL(gpib_register_driver); - -void gpib_unregister_driver(struct gpib_interface *interface) -{ - int i; - struct list_head *list_ptr; - - for (i = 0; i < GPIB_MAX_NUM_BOARDS; i++) { - struct gpib_board *board = &board_array[i]; - - if (board->interface == interface) { - if (board->use_count > 0) - pr_warn("gpib: Warning: deregistered interface %s in use\n", - interface->name); - iboffline(board); - board->interface = NULL; - } - } - for (list_ptr = registered_drivers.next; list_ptr != ®istered_drivers;) { - struct gpib_interface_list *entry; - - entry = list_entry(list_ptr, struct gpib_interface_list, list); - list_ptr = list_ptr->next; - if (entry->interface == interface) { - list_del(&entry->list); - kfree(entry); - } - } -} -EXPORT_SYMBOL(gpib_unregister_driver); - -static void init_gpib_board_config(struct gpib_board_config *config) -{ - memset(config, 0, sizeof(struct gpib_board_config)); - config->pci_bus = -1; - config->pci_slot = -1; -} - -void init_gpib_board(struct gpib_board *board) -{ - board->interface = NULL; - board->provider_module = NULL; - board->buffer = NULL; - board->buffer_length = 0; - board->status = 0; - init_waitqueue_head(&board->wait); - mutex_init(&board->user_mutex); - mutex_init(&board->big_gpib_mutex); - board->locking_pid = 0; - spin_lock_init(&board->locking_pid_spinlock); - spin_lock_init(&board->spinlock); - timer_setup(&board->timer, NULL, 0); - board->dev = NULL; - board->gpib_dev = NULL; - init_gpib_board_config(&board->config); - board->private_data = NULL; - board->use_count = 0; - INIT_LIST_HEAD(&board->device_list); - board->pad = 0; - board->sad = -1; - board->usec_timeout = 3000000; - board->parallel_poll_configuration = 0; - board->online = 0; - board->autospollers = 0; - board->autospoll_task = NULL; - init_event_queue(&board->event_queue); - board->minor = -1; - init_gpib_pseudo_irq(&board->pseudo_irq); - board->master = 1; - atomic_set(&board->stuck_srq, 0); - board->local_ppoll_mode = 0; -} - -int gpib_allocate_board(struct gpib_board *board) -{ - if (!board->buffer) { - board->buffer_length = 0x4000; - board->buffer = vmalloc(board->buffer_length); - if (!board->buffer) { - board->buffer_length = 0; - return -ENOMEM; - } - } - return 0; -} - -void gpib_deallocate_board(struct gpib_board *board) -{ - short dummy; - - if (board->buffer) { - vfree(board->buffer); - board->buffer = NULL; - board->buffer_length = 0; - } - while (num_gpib_events(&board->event_queue)) - pop_gpib_event(board, &board->event_queue, &dummy); -} - -static void init_board_array(struct gpib_board *board_array, unsigned int length) -{ - int i; - - for (i = 0; i < length; i++) { - init_gpib_board(&board_array[i]); - board_array[i].minor = i; - } -} - -void init_gpib_status_queue(struct gpib_status_queue *device) -{ - INIT_LIST_HEAD(&device->list); - INIT_LIST_HEAD(&device->status_bytes); - device->num_status_bytes = 0; - device->reference_count = 0; - device->dropped_byte = 0; -} - -static struct class *gpib_class; - -static int __init gpib_common_init_module(void) -{ - int i; - - pr_info("GPIB core driver\n"); - init_board_array(board_array, GPIB_MAX_NUM_BOARDS); - if (register_chrdev(GPIB_CODE, "gpib", &ib_fops)) { - pr_err("gpib: can't get major %d\n", GPIB_CODE); - return -EIO; - } - gpib_class = class_create("gpib_common"); - if (IS_ERR(gpib_class)) { - pr_err("gpib: failed to create gpib class\n"); - unregister_chrdev(GPIB_CODE, "gpib"); - return PTR_ERR(gpib_class); - } - for (i = 0; i < GPIB_MAX_NUM_BOARDS; ++i) - board_array[i].gpib_dev = device_create(gpib_class, NULL, - MKDEV(GPIB_CODE, i), NULL, "gpib%i", i); - - return 0; -} - -static void __exit gpib_common_exit_module(void) -{ - int i; - - for (i = 0; i < GPIB_MAX_NUM_BOARDS; ++i) - device_destroy(gpib_class, MKDEV(GPIB_CODE, i)); - - class_destroy(gpib_class); - unregister_chrdev(GPIB_CODE, "gpib"); -} - -int gpib_match_device_path(struct device *dev, const char *device_path_in) -{ - if (device_path_in) { - char *device_path; - - device_path = kobject_get_path(&dev->kobj, GFP_KERNEL); - if (!device_path) { - dev_err(dev, "kobject_get_path returned NULL."); - return 0; - } - if (strcmp(device_path_in, device_path) != 0) { - kfree(device_path); - return 0; - } - kfree(device_path); - } - return 1; -} -EXPORT_SYMBOL(gpib_match_device_path); - -struct pci_dev *gpib_pci_get_device(const struct gpib_board_config *config, unsigned int vendor_id, - unsigned int device_id, struct pci_dev *from) -{ - struct pci_dev *pci_device = from; - - while ((pci_device = pci_get_device(vendor_id, device_id, pci_device))) { - if (config->pci_bus >= 0 && config->pci_bus != pci_device->bus->number) - continue; - if (config->pci_slot >= 0 && config->pci_slot != - PCI_SLOT(pci_device->devfn)) - continue; - if (gpib_match_device_path(&pci_device->dev, config->device_path) == 0) - continue; - return pci_device; - } - return NULL; -} -EXPORT_SYMBOL(gpib_pci_get_device); - -struct pci_dev *gpib_pci_get_subsys(const struct gpib_board_config *config, unsigned int vendor_id, - unsigned int device_id, unsigned int ss_vendor, - unsigned int ss_device, - struct pci_dev *from) -{ - struct pci_dev *pci_device = from; - - while ((pci_device = pci_get_subsys(vendor_id, device_id, - ss_vendor, ss_device, pci_device))) { - if (config->pci_bus >= 0 && config->pci_bus != pci_device->bus->number) - continue; - if (config->pci_slot >= 0 && config->pci_slot != - PCI_SLOT(pci_device->devfn)) - continue; - if (gpib_match_device_path(&pci_device->dev, config->device_path) == 0) - continue; - return pci_device; - } - return NULL; -} -EXPORT_SYMBOL(gpib_pci_get_subsys); - -module_init(gpib_common_init_module); -module_exit(gpib_common_exit_module); - diff --git a/drivers/staging/gpib/common/iblib.c b/drivers/staging/gpib/common/iblib.c deleted file mode 100644 index 7cbb6a467177..000000000000 --- a/drivers/staging/gpib/common/iblib.c +++ /dev/null @@ -1,717 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 - -/*************************************************************************** - * copyright : (C) 2001, 2002 by Frank Mori Hess - ***************************************************************************/ - -#define dev_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include "ibsys.h" -#include -#include -#include - -/* - * IBCAC - * Return to the controller active state from the - * controller standby state, i.e., turn ATN on. Note - * that in order to enter the controller active state - * from the controller idle state, ibsic must be called. - * If sync is non-zero, attempt to take control synchronously. - * If fallback_to_async is non-zero, try to take control asynchronously - * if synchronous attempt fails. - */ -int ibcac(struct gpib_board *board, int sync, int fallback_to_async) -{ - int status = ibstatus(board); - int retval; - - if ((status & CIC) == 0) - return -EINVAL; - - if (status & ATN) - return 0; - - if (sync && (status & LACS) == 0) - /* - * tcs (take control synchronously) can only possibly work when - * controller is listener. Error code also needs to be -ETIMEDOUT - * or it will giveout without doing fallback. - */ - retval = -ETIMEDOUT; - else - retval = board->interface->take_control(board, sync); - - if (retval < 0 && fallback_to_async) { - if (sync && retval == -ETIMEDOUT) - retval = board->interface->take_control(board, 0); - } - board->interface->update_status(board, 0); - - return retval; -} - -/* - * After ATN is asserted, it should cause any connected devices - * to start listening for command bytes and leave acceptor idle state. - * So if ATN is asserted and neither NDAC or NRFD are asserted, - * then there are no devices and ibcmd should error out immediately. - * Some gpib hardware sees itself asserting NDAC/NRFD when it - * is controller in charge, in which case this check will - * do nothing useful (but shouldn't cause any harm either). - * Drivers that don't need this check (ni_usb for example) may - * set the skip_check_for_command_acceptors flag in their - * gpib_interface_struct to avoid useless overhead. - */ -static int check_for_command_acceptors(struct gpib_board *board) -{ - int lines; - - if (board->interface->skip_check_for_command_acceptors) - return 0; - if (!board->interface->line_status) - return 0; - - udelay(2); // allow time for devices to respond to ATN if it was just asserted - - lines = board->interface->line_status(board); - if (lines < 0) - return lines; - - if ((lines & VALID_NRFD) && (lines & VALID_NDAC)) { - if ((lines & BUS_NRFD) == 0 && (lines & BUS_NDAC) == 0) - return -ENOTCONN; - } - - return 0; -} - -/* - * IBCMD - * Write cnt command bytes from buf to the GPIB. The - * command operation terminates only on I/O complete. - * - * NOTE: - * 1. Prior to beginning the command, the interface is - * placed in the controller active state. - * 2. Before calling ibcmd for the first time, ibsic - * must be called to initialize the GPIB and enable - * the interface to leave the controller idle state. - */ -int ibcmd(struct gpib_board *board, u8 *buf, size_t length, size_t *bytes_written) -{ - ssize_t ret = 0; - int status; - - *bytes_written = 0; - - status = ibstatus(board); - - if ((status & CIC) == 0) - return -EINVAL; - - os_start_timer(board, board->usec_timeout); - - ret = ibcac(board, 1, 1); - if (ret == 0) { - ret = check_for_command_acceptors(board); - if (ret == 0) - ret = board->interface->command(board, buf, length, bytes_written); - } - - os_remove_timer(board); - - if (io_timed_out(board)) - ret = -ETIMEDOUT; - - return ret; -} - -/* - * IBGTS - * Go to the controller standby state from the controller - * active state, i.e., turn ATN off. - */ - -int ibgts(struct gpib_board *board) -{ - int status = ibstatus(board); - int retval; - - if ((status & CIC) == 0) - return -EINVAL; - - retval = board->interface->go_to_standby(board); /* go to standby */ - - board->interface->update_status(board, 0); - - return retval; -} - -static int autospoll_wait_should_wake_up(struct gpib_board *board) -{ - int retval; - - mutex_lock(&board->big_gpib_mutex); - - retval = board->master && board->autospollers > 0 && - !atomic_read(&board->stuck_srq) && - test_and_clear_bit(SRQI_NUM, &board->status); - - mutex_unlock(&board->big_gpib_mutex); - return retval; -} - -static int autospoll_thread(void *board_void) -{ - struct gpib_board *board = board_void; - int retval = 0; - - dev_dbg(board->gpib_dev, "entering autospoll thread\n"); - - while (1) { - wait_event_interruptible(board->wait, - kthread_should_stop() || - autospoll_wait_should_wake_up(board)); - dev_dbg(board->gpib_dev, "autospoll wait satisfied\n"); - if (kthread_should_stop()) - break; - - mutex_lock(&board->big_gpib_mutex); - /* make sure we are still good after we have lock */ - if (board->autospollers <= 0 || board->master == 0) { - mutex_unlock(&board->big_gpib_mutex); - continue; - } - mutex_unlock(&board->big_gpib_mutex); - - if (try_module_get(board->provider_module)) { - retval = autopoll_all_devices(board); - module_put(board->provider_module); - } else { - dev_err(board->gpib_dev, "try_module_get() failed!\n"); - } - if (retval <= 0) { - dev_err(board->gpib_dev, "stuck SRQ\n"); - - atomic_set(&board->stuck_srq, 1); // XXX could be better - set_bit(SRQI_NUM, &board->status); - } - } - return retval; -} - -int ibonline(struct gpib_board *board) -{ - int retval; - - if (board->online) - return -EBUSY; - if (!board->interface) - return -ENODEV; - retval = gpib_allocate_board(board); - if (retval < 0) - return retval; - - board->dev = NULL; - board->local_ppoll_mode = 0; - retval = board->interface->attach(board, &board->config); - if (retval < 0) { - board->interface->detach(board); - return retval; - } - /* - * nios2nommu on 2.6.11 uclinux kernel has weird problems - * with autospoll thread causing huge slowdowns - */ -#ifndef CONFIG_NIOS2 - board->autospoll_task = kthread_run(&autospoll_thread, board, - "gpib%d_autospoll_kthread", board->minor); - retval = IS_ERR(board->autospoll_task); - if (retval) { - dev_err(board->gpib_dev, "failed to create autospoll thread\n"); - board->interface->detach(board); - return retval; - } -#endif - board->online = 1; - dev_dbg(board->gpib_dev, "board online\n"); - - return 0; -} - -/* XXX need to make sure board is generally not in use (grab board lock?) */ -int iboffline(struct gpib_board *board) -{ - int retval; - - if (board->online == 0) - return 0; - if (!board->interface) - return -ENODEV; - - if (board->autospoll_task && !IS_ERR(board->autospoll_task)) { - retval = kthread_stop(board->autospoll_task); - if (retval) - dev_err(board->gpib_dev, "kthread_stop returned %i\n", retval); - board->autospoll_task = NULL; - } - - board->interface->detach(board); - gpib_deallocate_board(board); - board->online = 0; - dev_dbg(board->gpib_dev, "board offline\n"); - - return 0; -} - -/* - * IBLINES - * Poll the GPIB control lines and return their status in buf. - * - * LSB (bits 0-7) - VALID lines mask (lines that can be monitored). - * Next LSB (bits 8-15) - STATUS lines mask (lines that are currently set). - * - */ -int iblines(const struct gpib_board *board, short *lines) -{ - int retval; - - *lines = 0; - if (!board->interface->line_status) - return 0; - retval = board->interface->line_status(board); - if (retval < 0) - return retval; - *lines = retval; - return 0; -} - -/* - * IBRD - * Read up to 'length' bytes of data from the GPIB into buf. End - * on detection of END (EOI and or EOS) and set 'end_flag'. - * - * NOTE: - * 1. The interface is placed in the controller standby - * state prior to beginning the read. - * 2. Prior to calling ibrd, the intended devices as well - * as the interface board itself must be addressed by - * calling ibcmd. - */ - -int ibrd(struct gpib_board *board, u8 *buf, size_t length, int *end_flag, size_t *nbytes) -{ - ssize_t ret = 0; - int retval; - size_t bytes_read; - - *nbytes = 0; - *end_flag = 0; - if (length == 0) - return 0; - - if (board->master) { - retval = ibgts(board); - if (retval < 0) - return retval; - } - /* - * XXX resetting timer here could cause timeouts take longer than they should, - * since read_ioctl calls this - * function in a loop, there is probably a similar problem with writes/commands - */ - os_start_timer(board, board->usec_timeout); - - do { - ret = board->interface->read(board, buf, length - *nbytes, end_flag, &bytes_read); - if (ret < 0) - goto ibrd_out; - - buf += bytes_read; - *nbytes += bytes_read; - if (need_resched()) - schedule(); - } while (ret == 0 && *nbytes > 0 && *nbytes < length && *end_flag == 0); -ibrd_out: - os_remove_timer(board); - - return ret; -} - -/* - * IBRPP - * Conduct a parallel poll and return the byte in buf. - * - * NOTE: - * 1. Prior to conducting the poll the interface is placed - * in the controller active state. - */ -int ibrpp(struct gpib_board *board, u8 *result) -{ - int retval = 0; - - os_start_timer(board, board->usec_timeout); - retval = ibcac(board, 1, 1); - if (retval) - return -1; - - retval = board->interface->parallel_poll(board, result); - - os_remove_timer(board); - return retval; -} - -int ibppc(struct gpib_board *board, u8 configuration) -{ - configuration &= 0x1f; - board->interface->parallel_poll_configure(board, configuration); - board->parallel_poll_configuration = configuration; - - return 0; -} - -int ibrsv2(struct gpib_board *board, u8 status_byte, int new_reason_for_service) -{ - int board_status = ibstatus(board); - const unsigned int MSS = status_byte & request_service_bit; - - if ((board_status & CIC)) - return -EINVAL; - - if (MSS == 0 && new_reason_for_service) - return -EINVAL; - - if (board->interface->serial_poll_response2) { - board->interface->serial_poll_response2(board, status_byte, new_reason_for_service); - // fall back on simpler serial_poll_response if the behavior would be the same - } else if (board->interface->serial_poll_response && - (MSS == 0 || (MSS && new_reason_for_service))) { - board->interface->serial_poll_response(board, status_byte); - } else { - return -EOPNOTSUPP; - } - - return 0; -} - -/* - * IBSIC - * Send IFC for at least 100 microseconds. - * - * NOTE: - * 1. Ibsic must be called prior to the first call to - * ibcmd in order to initialize the bus and enable the - * interface to leave the controller idle state. - */ -int ibsic(struct gpib_board *board, unsigned int usec_duration) -{ - if (board->master == 0) - return -EINVAL; - - if (usec_duration < 100) - usec_duration = 100; - if (usec_duration > 1000) - usec_duration = 1000; - - dev_dbg(board->gpib_dev, "sending interface clear, delay = %ius\n", usec_duration); - board->interface->interface_clear(board, 1); - udelay(usec_duration); - board->interface->interface_clear(board, 0); - - return 0; -} - -int ibrsc(struct gpib_board *board, int request_control) -{ - int retval; - - if (!board->interface->request_system_control) - return -EPERM; - - retval = board->interface->request_system_control(board, request_control); - - if (retval) - return retval; - - board->master = request_control != 0; - - return 0; -} - -/* - * IBSRE - * Send REN true if v is non-zero or false if v is zero. - */ -int ibsre(struct gpib_board *board, int enable) -{ - if (board->master == 0) - return -EINVAL; - - board->interface->remote_enable(board, enable); /* set or clear REN */ - if (!enable) - usleep_range(100, 150); - - return 0; -} - -/* - * IBPAD - * change the GPIB address of the interface board. The address - * must be 0 through 30. ibonl resets the address to PAD. - */ -int ibpad(struct gpib_board *board, unsigned int addr) -{ - if (addr > MAX_GPIB_PRIMARY_ADDRESS) - return -EINVAL; - - board->pad = addr; - if (board->online) - board->interface->primary_address(board, board->pad); - dev_dbg(board->gpib_dev, "set primary addr to %i\n", board->pad); - return 0; -} - -/* - * IBSAD - * change the secondary GPIB address of the interface board. - * The address must be 0 through 30, or negative disables. ibonl resets the - * address to SAD. - */ -int ibsad(struct gpib_board *board, int addr) -{ - if (addr > MAX_GPIB_SECONDARY_ADDRESS) - return -EINVAL; - board->sad = addr; - if (board->online) { - if (board->sad >= 0) - board->interface->secondary_address(board, board->sad, 1); - else - board->interface->secondary_address(board, 0, 0); - } - dev_dbg(board->gpib_dev, "set secondary addr to %i\n", board->sad); - - return 0; -} - -/* - * IBEOS - * Set the end-of-string modes for I/O operations to v. - * - */ -int ibeos(struct gpib_board *board, int eos, int eosflags) -{ - int retval; - - if (eosflags & ~EOS_MASK) - return -EINVAL; - if (eosflags & REOS) { - retval = board->interface->enable_eos(board, eos, eosflags & BIN); - } else { - board->interface->disable_eos(board); - retval = 0; - } - return retval; -} - -int ibstatus(struct gpib_board *board) -{ - return general_ibstatus(board, NULL, 0, 0, NULL); -} - -int general_ibstatus(struct gpib_board *board, const struct gpib_status_queue *device, - int clear_mask, int set_mask, struct gpib_descriptor *desc) -{ - int status = 0; - short line_status; - - if (board->private_data) { - status = board->interface->update_status(board, clear_mask); - /* - * XXX should probably stop having drivers use TIMO bit in - * board->status to avoid confusion - */ - status &= ~TIMO; - /* get real SRQI status if we can */ - if (iblines(board, &line_status) == 0) { - if ((line_status & VALID_SRQ)) { - if ((line_status & BUS_SRQ)) - status |= SRQI; - else - status &= ~SRQI; - } - } - } - if (device) - if (num_status_bytes(device)) - status |= RQS; - - if (desc) { - if (set_mask & CMPL) - atomic_set(&desc->io_in_progress, 0); - else if (clear_mask & CMPL) - atomic_set(&desc->io_in_progress, 1); - - if (atomic_read(&desc->io_in_progress)) - status &= ~CMPL; - else - status |= CMPL; - } - if (num_gpib_events(&board->event_queue)) - status |= EVENT; - else - status &= ~EVENT; - - return status; -} - -struct wait_info { - struct gpib_board *board; - struct timer_list timer; - int timed_out; - unsigned long usec_timeout; -}; - -static void wait_timeout(struct timer_list *t) -{ - struct wait_info *winfo = timer_container_of(winfo, t, timer); - - winfo->timed_out = 1; - wake_up_interruptible(&winfo->board->wait); -} - -static void init_wait_info(struct wait_info *winfo) -{ - winfo->board = NULL; - winfo->timed_out = 0; - timer_setup_on_stack(&winfo->timer, wait_timeout, 0); -} - -static int wait_satisfied(struct wait_info *winfo, struct gpib_status_queue *status_queue, - int wait_mask, int *status, struct gpib_descriptor *desc) -{ - struct gpib_board *board = winfo->board; - int temp_status; - - if (mutex_lock_interruptible(&board->big_gpib_mutex)) - return -ERESTARTSYS; - - temp_status = general_ibstatus(board, status_queue, 0, 0, desc); - - mutex_unlock(&board->big_gpib_mutex); - - if (winfo->timed_out) - temp_status |= TIMO; - else - temp_status &= ~TIMO; - if (wait_mask & temp_status) { - *status = temp_status; - return 1; - } -// XXX does wait for END work? - return 0; -} - -/* install timer interrupt handler */ -static void start_wait_timer(struct wait_info *winfo) -/* Starts the timeout task */ -{ - winfo->timed_out = 0; - - if (winfo->usec_timeout > 0) - mod_timer(&winfo->timer, jiffies + usec_to_jiffies(winfo->usec_timeout)); -} - -static void remove_wait_timer(struct wait_info *winfo) -{ - timer_delete_sync(&winfo->timer); - timer_destroy_on_stack(&winfo->timer); -} - -/* - * IBWAIT - * Check or wait for a GPIB event to occur. The mask argument - * is a bit vector corresponding to the status bit vector. It - * has a bit set for each condition which can terminate the wait - * If the mask is 0 then - * no condition is waited for. - */ -int ibwait(struct gpib_board *board, int wait_mask, int clear_mask, int set_mask, - int *status, unsigned long usec_timeout, struct gpib_descriptor *desc) -{ - int retval = 0; - struct gpib_status_queue *status_queue; - struct wait_info winfo; - - if (desc->is_board) - status_queue = NULL; - else - status_queue = get_gpib_status_queue(board, desc->pad, desc->sad); - - if (wait_mask == 0) { - *status = general_ibstatus(board, status_queue, clear_mask, set_mask, desc); - return 0; - } - - mutex_unlock(&board->big_gpib_mutex); - - init_wait_info(&winfo); - winfo.board = board; - winfo.usec_timeout = usec_timeout; - start_wait_timer(&winfo); - - if (wait_event_interruptible(board->wait, wait_satisfied(&winfo, status_queue, - wait_mask, status, desc))) { - dev_dbg(board->gpib_dev, "wait interrupted\n"); - retval = -ERESTARTSYS; - } - remove_wait_timer(&winfo); - - if (retval) - return retval; - if (mutex_lock_interruptible(&board->big_gpib_mutex)) - return -ERESTARTSYS; - - /* make sure we only clear status bits that we are reporting */ - if (*status & clear_mask || set_mask) - general_ibstatus(board, status_queue, *status & clear_mask, set_mask, NULL); - - return 0; -} - -/* - * IBWRT - * Write cnt bytes of data from buf to the GPIB. The write - * operation terminates only on I/O complete. - * - * NOTE: - * 1. Prior to beginning the write, the interface is - * placed in the controller standby state. - * 2. Prior to calling ibwrt, the intended devices as - * well as the interface board itself must be - * addressed by calling ibcmd. - */ -int ibwrt(struct gpib_board *board, u8 *buf, size_t cnt, int send_eoi, size_t *bytes_written) -{ - int ret = 0; - int retval; - - if (cnt == 0) - return 0; - - if (board->master) { - retval = ibgts(board); - if (retval < 0) - return retval; - } - os_start_timer(board, board->usec_timeout); - ret = board->interface->write(board, buf, cnt, send_eoi, bytes_written); - - if (io_timed_out(board)) - ret = -ETIMEDOUT; - - os_remove_timer(board); - - return ret; -} - diff --git a/drivers/staging/gpib/common/ibsys.h b/drivers/staging/gpib/common/ibsys.h deleted file mode 100644 index e5a148f513a8..000000000000 --- a/drivers/staging/gpib/common/ibsys.h +++ /dev/null @@ -1,34 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -#include "gpibP.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#define MAX_GPIB_PRIMARY_ADDRESS 30 -#define MAX_GPIB_SECONDARY_ADDRESS 31 - -int gpib_allocate_board(struct gpib_board *board); -void gpib_deallocate_board(struct gpib_board *board); - -unsigned int num_status_bytes(const struct gpib_status_queue *dev); -int push_status_byte(struct gpib_board *board, struct gpib_status_queue *device, - u8 poll_byte); -int pop_status_byte(struct gpib_board *board, struct gpib_status_queue *device, - u8 *poll_byte); -struct gpib_status_queue *get_gpib_status_queue(struct gpib_board *board, - unsigned int pad, int sad); -int get_serial_poll_byte(struct gpib_board *board, unsigned int pad, int sad, - unsigned int usec_timeout, u8 *poll_byte); -int autopoll_all_devices(struct gpib_board *board); diff --git a/drivers/staging/gpib/eastwood/Makefile b/drivers/staging/gpib/eastwood/Makefile deleted file mode 100644 index 384825195f77..000000000000 --- a/drivers/staging/gpib/eastwood/Makefile +++ /dev/null @@ -1,3 +0,0 @@ - -obj-$(CONFIG_GPIB_FLUKE) += fluke_gpib.o - diff --git a/drivers/staging/gpib/eastwood/fluke_gpib.c b/drivers/staging/gpib/eastwood/fluke_gpib.c deleted file mode 100644 index 3ae848e3f738..000000000000 --- a/drivers/staging/gpib/eastwood/fluke_gpib.c +++ /dev/null @@ -1,1180 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 - -/*************************************************************************** - * GPIB Driver for Fluke cda devices. Basically, its a driver for a (bugfixed) - * cb7210 connected to channel 0 of a pl330 dma controller. - * Author: Frank Mori Hess - * copyright: (C) 2006, 2010, 2015 Fluke Corporation - ***************************************************************************/ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -#define dev_fmt pr_fmt -#define DRV_NAME KBUILD_MODNAME - -#include "fluke_gpib.h" - -#include "gpibP.h" -#include -#include -#include -#include -#include -#include - -MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("GPIB Driver for Fluke cda devices"); - -static int fluke_attach_holdoff_all(struct gpib_board *board, - const struct gpib_board_config *config); -static int fluke_attach_holdoff_end(struct gpib_board *board, - const struct gpib_board_config *config); -static void fluke_detach(struct gpib_board *board); -static int fluke_config_dma(struct gpib_board *board, int output); -static irqreturn_t fluke_gpib_internal_interrupt(struct gpib_board *board); - -static struct platform_device *fluke_gpib_pdev; - -static u8 fluke_locking_read_byte(struct nec7210_priv *nec_priv, unsigned int register_number) -{ - u8 retval; - unsigned long flags; - - spin_lock_irqsave(&nec_priv->register_page_lock, flags); - retval = fluke_read_byte_nolock(nec_priv, register_number); - spin_unlock_irqrestore(&nec_priv->register_page_lock, flags); - return retval; -} - -static void fluke_locking_write_byte(struct nec7210_priv *nec_priv, u8 byte, - unsigned int register_number) -{ - unsigned long flags; - - spin_lock_irqsave(&nec_priv->register_page_lock, flags); - fluke_write_byte_nolock(nec_priv, byte, register_number); - spin_unlock_irqrestore(&nec_priv->register_page_lock, flags); -} - -// wrappers for interface functions -static int fluke_read(struct gpib_board *board, u8 *buffer, size_t length, int *end, - size_t *bytes_read) -{ - struct fluke_priv *priv = board->private_data; - - return nec7210_read(board, &priv->nec7210_priv, buffer, length, end, bytes_read); -} - -static int fluke_write(struct gpib_board *board, u8 *buffer, size_t length, - int send_eoi, size_t *bytes_written) -{ - struct fluke_priv *priv = board->private_data; - - return nec7210_write(board, &priv->nec7210_priv, buffer, length, send_eoi, bytes_written); -} - -static int fluke_command(struct gpib_board *board, u8 *buffer, - size_t length, size_t *bytes_written) -{ - struct fluke_priv *priv = board->private_data; - - return nec7210_command(board, &priv->nec7210_priv, buffer, length, bytes_written); -} - -static int fluke_take_control(struct gpib_board *board, int synchronous) -{ - struct fluke_priv *priv = board->private_data; - - return nec7210_take_control(board, &priv->nec7210_priv, synchronous); -} - -static int fluke_go_to_standby(struct gpib_board *board) -{ - struct fluke_priv *priv = board->private_data; - - return nec7210_go_to_standby(board, &priv->nec7210_priv); -} - -static int fluke_request_system_control(struct gpib_board *board, int request_control) -{ - struct fluke_priv *priv = board->private_data; - struct nec7210_priv *nec_priv = &priv->nec7210_priv; - - return nec7210_request_system_control(board, nec_priv, request_control); -} - -static void fluke_interface_clear(struct gpib_board *board, int assert) -{ - struct fluke_priv *priv = board->private_data; - - nec7210_interface_clear(board, &priv->nec7210_priv, assert); -} - -static void fluke_remote_enable(struct gpib_board *board, int enable) -{ - struct fluke_priv *priv = board->private_data; - - nec7210_remote_enable(board, &priv->nec7210_priv, enable); -} - -static int fluke_enable_eos(struct gpib_board *board, u8 eos_byte, int compare_8_bits) -{ - struct fluke_priv *priv = board->private_data; - - return nec7210_enable_eos(board, &priv->nec7210_priv, eos_byte, compare_8_bits); -} - -static void fluke_disable_eos(struct gpib_board *board) -{ - struct fluke_priv *priv = board->private_data; - - nec7210_disable_eos(board, &priv->nec7210_priv); -} - -static unsigned int fluke_update_status(struct gpib_board *board, unsigned int clear_mask) -{ - struct fluke_priv *priv = board->private_data; - - return nec7210_update_status(board, &priv->nec7210_priv, clear_mask); -} - -static int fluke_primary_address(struct gpib_board *board, unsigned int address) -{ - struct fluke_priv *priv = board->private_data; - - return nec7210_primary_address(board, &priv->nec7210_priv, address); -} - -static int fluke_secondary_address(struct gpib_board *board, unsigned int address, int enable) -{ - struct fluke_priv *priv = board->private_data; - - return nec7210_secondary_address(board, &priv->nec7210_priv, address, enable); -} - -static int fluke_parallel_poll(struct gpib_board *board, u8 *result) -{ - struct fluke_priv *priv = board->private_data; - - return nec7210_parallel_poll(board, &priv->nec7210_priv, result); -} - -static void fluke_parallel_poll_configure(struct gpib_board *board, u8 configuration) -{ - struct fluke_priv *priv = board->private_data; - - nec7210_parallel_poll_configure(board, &priv->nec7210_priv, configuration); -} - -static void fluke_parallel_poll_response(struct gpib_board *board, int ist) -{ - struct fluke_priv *priv = board->private_data; - - nec7210_parallel_poll_response(board, &priv->nec7210_priv, ist); -} - -static void fluke_serial_poll_response(struct gpib_board *board, u8 status) -{ - struct fluke_priv *priv = board->private_data; - - nec7210_serial_poll_response(board, &priv->nec7210_priv, status); -} - -static u8 fluke_serial_poll_status(struct gpib_board *board) -{ - struct fluke_priv *priv = board->private_data; - - return nec7210_serial_poll_status(board, &priv->nec7210_priv); -} - -static void fluke_return_to_local(struct gpib_board *board) -{ - struct fluke_priv *priv = board->private_data; - struct nec7210_priv *nec_priv = &priv->nec7210_priv; - - write_byte(nec_priv, AUX_RTL2, AUXMR); - udelay(1); - write_byte(nec_priv, AUX_RTL, AUXMR); -} - -static int fluke_line_status(const struct gpib_board *board) -{ - int status = VALID_ALL; - int bsr_bits; - struct fluke_priv *e_priv; - - e_priv = board->private_data; - - bsr_bits = fluke_paged_read_byte(e_priv, BUS_STATUS, BUS_STATUS_PAGE); - - if ((bsr_bits & BSR_REN_BIT) == 0) - status |= BUS_REN; - if ((bsr_bits & BSR_IFC_BIT) == 0) - status |= BUS_IFC; - if ((bsr_bits & BSR_SRQ_BIT) == 0) - status |= BUS_SRQ; - if ((bsr_bits & BSR_EOI_BIT) == 0) - status |= BUS_EOI; - if ((bsr_bits & BSR_NRFD_BIT) == 0) - status |= BUS_NRFD; - if ((bsr_bits & BSR_NDAC_BIT) == 0) - status |= BUS_NDAC; - if ((bsr_bits & BSR_DAV_BIT) == 0) - status |= BUS_DAV; - if ((bsr_bits & BSR_ATN_BIT) == 0) - status |= BUS_ATN; - - return status; -} - -static int fluke_t1_delay(struct gpib_board *board, unsigned int nano_sec) -{ - struct fluke_priv *e_priv = board->private_data; - struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; - unsigned int retval; - - retval = nec7210_t1_delay(board, nec_priv, nano_sec); - - if (nano_sec <= 350) { - write_byte(nec_priv, AUX_HI_SPEED, AUXMR); - retval = 350; - } else { - write_byte(nec_priv, AUX_LO_SPEED, AUXMR); - } - return retval; -} - -static int lacs_or_read_ready(struct gpib_board *board) -{ - const struct fluke_priv *e_priv = board->private_data; - const struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; - unsigned long flags; - int retval; - - spin_lock_irqsave(&board->spinlock, flags); - retval = test_bit(LACS_NUM, &board->status) || test_bit(READ_READY_BN, &nec_priv->state); - spin_unlock_irqrestore(&board->spinlock, flags); - return retval; -} - -/* - * Wait until it is possible for a read to do something useful. This - * is not essential, it only exists to prevent RFD holdoff from being released pointlessly. - */ -static int wait_for_read(struct gpib_board *board) -{ - struct fluke_priv *e_priv = board->private_data; - struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; - int retval = 0; - - if (wait_event_interruptible(board->wait, - lacs_or_read_ready(board) || - test_bit(DEV_CLEAR_BN, &nec_priv->state) || - test_bit(TIMO_NUM, &board->status))) - retval = -ERESTARTSYS; - - if (test_bit(TIMO_NUM, &board->status)) - retval = -ETIMEDOUT; - if (test_and_clear_bit(DEV_CLEAR_BN, &nec_priv->state)) - retval = -EINTR; - return retval; -} - -/* - * Check if the SH state machine is in SGNS. We check twice since there is a very small chance - * we could be blowing through SGNS from SIDS to SDYS if there is already a - * byte available in the handshake state machine. We are interested - * in the case where the handshake is stuck in SGNS due to no byte being - * available to the chip (and thus we can be confident a dma transfer will - * result in at least one byte making it into the chip). This matters - * because we want to be confident before sending a "send eoi" auxilary - * command that we will be able to also put the associated data byte - * in the chip before any potential timeout. - */ -static int source_handshake_is_sgns(struct fluke_priv *e_priv) -{ - int i; - - for (i = 0; i < 2; ++i) { - if ((fluke_paged_read_byte(e_priv, STATE1_REG, STATE1_PAGE) & - SOURCE_HANDSHAKE_MASK) != SOURCE_HANDSHAKE_SGNS_BITS) { - return 0; - } - } - return 1; -} - -static int source_handshake_is_sids_or_sgns(struct fluke_priv *e_priv) -{ - unsigned int source_handshake_bits; - - source_handshake_bits = fluke_paged_read_byte(e_priv, STATE1_REG, STATE1_PAGE) & - SOURCE_HANDSHAKE_MASK; - - return (source_handshake_bits == SOURCE_HANDSHAKE_SGNS_BITS) || - (source_handshake_bits == SOURCE_HANDSHAKE_SIDS_BITS); -} - -/* - * Wait until the gpib chip is ready to accept a data out byte. - * If the chip is SGNS it is probably waiting for a a byte to - * be written to it. - */ -static int wait_for_data_out_ready(struct gpib_board *board) -{ - struct fluke_priv *e_priv = board->private_data; - struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; - int retval = 0; - - if (wait_event_interruptible(board->wait, - (test_bit(TACS_NUM, &board->status) && - source_handshake_is_sgns(e_priv)) || - test_bit(DEV_CLEAR_BN, &nec_priv->state) || - test_bit(TIMO_NUM, &board->status))) - retval = -ERESTARTSYS; - if (test_bit(TIMO_NUM, &board->status)) - retval = -ETIMEDOUT; - if (test_and_clear_bit(DEV_CLEAR_BN, &nec_priv->state)) - retval = -EINTR; - return retval; -} - -static int wait_for_sids_or_sgns(struct gpib_board *board) -{ - struct fluke_priv *e_priv = board->private_data; - struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; - int retval = 0; - - if (wait_event_interruptible(board->wait, - source_handshake_is_sids_or_sgns(e_priv) || - test_bit(DEV_CLEAR_BN, &nec_priv->state) || - test_bit(TIMO_NUM, &board->status))) - retval = -ERESTARTSYS; - - if (test_bit(TIMO_NUM, &board->status)) - retval = -ETIMEDOUT; - if (test_and_clear_bit(DEV_CLEAR_BN, &nec_priv->state)) - retval = -EINTR; - return retval; -} - -static void fluke_dma_callback(void *arg) -{ - struct gpib_board *board = arg; - struct fluke_priv *e_priv = board->private_data; - struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; - unsigned long flags; - - spin_lock_irqsave(&board->spinlock, flags); - - nec7210_set_reg_bits(nec_priv, IMR1, HR_DOIE | HR_DIIE, HR_DOIE | HR_DIIE); - wake_up_interruptible(&board->wait); - - fluke_gpib_internal_interrupt(board); - clear_bit(DMA_WRITE_IN_PROGRESS_BN, &nec_priv->state); - clear_bit(DMA_READ_IN_PROGRESS_BN, &nec_priv->state); - - spin_unlock_irqrestore(&board->spinlock, flags); -} - -static int fluke_dma_write(struct gpib_board *board, u8 *buffer, size_t length, - size_t *bytes_written) -{ - struct fluke_priv *e_priv = board->private_data; - struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; - unsigned long flags; - int retval = 0; - dma_addr_t address; - struct dma_async_tx_descriptor *tx_desc; - - *bytes_written = 0; - - if (WARN_ON_ONCE(length > e_priv->dma_buffer_size)) - return -EFAULT; - dmaengine_terminate_all(e_priv->dma_channel); - // write-clear counter - writel(0x0, e_priv->write_transfer_counter); - - memcpy(e_priv->dma_buffer, buffer, length); - address = dma_map_single(board->dev, e_priv->dma_buffer, - length, DMA_TO_DEVICE); - /* program dma controller */ - retval = fluke_config_dma(board, 1); - if (retval) - goto cleanup; - - tx_desc = dmaengine_prep_slave_single(e_priv->dma_channel, address, length, DMA_MEM_TO_DEV, - DMA_PREP_INTERRUPT | DMA_CTRL_ACK); - if (!tx_desc) { - dev_err(board->gpib_dev, "failed to allocate dma transmit descriptor\n"); - retval = -ENOMEM; - goto cleanup; - } - tx_desc->callback = fluke_dma_callback; - tx_desc->callback_param = board; - - spin_lock_irqsave(&board->spinlock, flags); - nec7210_set_reg_bits(nec_priv, IMR1, HR_DOIE, 0); - nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAO, HR_DMAO); - dmaengine_submit(tx_desc); - dma_async_issue_pending(e_priv->dma_channel); - - clear_bit(WRITE_READY_BN, &nec_priv->state); - set_bit(DMA_WRITE_IN_PROGRESS_BN, &nec_priv->state); - - spin_unlock_irqrestore(&board->spinlock, flags); - - // suspend until message is sent - if (wait_event_interruptible(board->wait, - ((readl(e_priv->write_transfer_counter) & - write_transfer_counter_mask) == length) || - test_bit(BUS_ERROR_BN, &nec_priv->state) || - test_bit(DEV_CLEAR_BN, &nec_priv->state) || - test_bit(TIMO_NUM, &board->status))) { - retval = -ERESTARTSYS; - } - if (test_bit(TIMO_NUM, &board->status)) - retval = -ETIMEDOUT; - if (test_and_clear_bit(DEV_CLEAR_BN, &nec_priv->state)) - retval = -EINTR; - if (test_and_clear_bit(BUS_ERROR_BN, &nec_priv->state)) - retval = -EIO; - // disable board's dma - nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAO, 0); - - dmaengine_terminate_all(e_priv->dma_channel); - // make sure fluke_dma_callback got called - if (test_bit(DMA_WRITE_IN_PROGRESS_BN, &nec_priv->state)) - fluke_dma_callback(board); - - /* - * if everything went fine, try to wait until last byte is actually - * transmitted across gpib (but don't try _too_ hard) - */ - if (retval == 0) - retval = wait_for_sids_or_sgns(board); - - *bytes_written = readl(e_priv->write_transfer_counter) & write_transfer_counter_mask; - if (WARN_ON_ONCE(*bytes_written > length)) - return -EFAULT; - -cleanup: - dma_unmap_single(board->dev, address, length, DMA_TO_DEVICE); - return retval; -} - -static int fluke_accel_write(struct gpib_board *board, u8 *buffer, size_t length, - int send_eoi, size_t *bytes_written) -{ - struct fluke_priv *e_priv = board->private_data; - struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; - size_t remainder = length; - size_t transfer_size; - ssize_t retval = 0; - size_t dma_remainder = remainder; - - if (!e_priv->dma_channel) { - dev_err(board->gpib_dev, "No dma channel available, cannot do accel write."); - return -ENXIO; - } - - *bytes_written = 0; - if (length < 1) - return 0; - - clear_bit(DEV_CLEAR_BN, &nec_priv->state); // XXX FIXME - - if (send_eoi) - --dma_remainder; - - while (dma_remainder > 0) { - size_t num_bytes; - - retval = wait_for_data_out_ready(board); - if (retval < 0) - break; - - transfer_size = (e_priv->dma_buffer_size < dma_remainder) ? - e_priv->dma_buffer_size : dma_remainder; - retval = fluke_dma_write(board, buffer, transfer_size, &num_bytes); - *bytes_written += num_bytes; - if (retval < 0) - break; - dma_remainder -= num_bytes; - remainder -= num_bytes; - buffer += num_bytes; - if (need_resched()) - schedule(); - } - if (retval < 0) - return retval; - // handle sending of last byte with eoi - if (send_eoi) { - size_t num_bytes; - - if (WARN_ON_ONCE(remainder != 1)) - return -EFAULT; - - /* - * wait until we are sure we will be able to write the data byte - * into the chip before we send AUX_SEOI. This prevents a timeout - * scenerio where we send AUX_SEOI but then timeout without getting - * any bytes into the gpib chip. This will result in the first byte - * of the next write having a spurious EOI set on the first byte. - */ - retval = wait_for_data_out_ready(board); - if (retval < 0) - return retval; - - write_byte(nec_priv, AUX_SEOI, AUXMR); - retval = fluke_dma_write(board, buffer, remainder, &num_bytes); - *bytes_written += num_bytes; - if (retval < 0) - return retval; - remainder -= num_bytes; - } - return 0; -} - -static int fluke_get_dma_residue(struct dma_chan *chan, dma_cookie_t cookie) -{ - struct dma_tx_state state; - int result; - - result = dmaengine_pause(chan); - if (result < 0) { - pr_err("dma pause failed?\n"); - return result; - } - dmaengine_tx_status(chan, cookie, &state); - /* - * hardware doesn't support resume, so dont call this - * method unless the dma transfer is done. - */ - return state.residue; -} - -static int fluke_dma_read(struct gpib_board *board, u8 *buffer, - size_t length, int *end, size_t *bytes_read) -{ - struct fluke_priv *e_priv = board->private_data; - struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; - int retval = 0; - unsigned long flags; - int residue; - dma_addr_t bus_address; - struct dma_async_tx_descriptor *tx_desc; - dma_cookie_t dma_cookie; - int i; - static const int timeout = 10; - - *bytes_read = 0; - *end = 0; - if (length == 0) - return 0; - - bus_address = dma_map_single(board->dev, e_priv->dma_buffer, - length, DMA_FROM_DEVICE); - - /* program dma controller */ - retval = fluke_config_dma(board, 0); - if (retval) { - dma_unmap_single(board->dev, bus_address, length, DMA_FROM_DEVICE); - return retval; - } - tx_desc = dmaengine_prep_slave_single(e_priv->dma_channel, - bus_address, length, DMA_DEV_TO_MEM, - DMA_PREP_INTERRUPT | DMA_CTRL_ACK); - if (!tx_desc) { - dev_err(board->gpib_dev, "failed to allocate dma transmit descriptor\n"); - dma_unmap_single(NULL, bus_address, length, DMA_FROM_DEVICE); - return -EIO; - } - tx_desc->callback = fluke_dma_callback; - tx_desc->callback_param = board; - - spin_lock_irqsave(&board->spinlock, flags); - // enable nec7210 dma - nec7210_set_reg_bits(nec_priv, IMR1, HR_DIIE, 0); - nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAI, HR_DMAI); - - dma_cookie = dmaengine_submit(tx_desc); - dma_async_issue_pending(e_priv->dma_channel); - - set_bit(DMA_READ_IN_PROGRESS_BN, &nec_priv->state); - clear_bit(READ_READY_BN, &nec_priv->state); - - spin_unlock_irqrestore(&board->spinlock, flags); - // wait for data to transfer - if (wait_event_interruptible(board->wait, - test_bit(DMA_READ_IN_PROGRESS_BN, &nec_priv->state) == 0 || - test_bit(RECEIVED_END_BN, &nec_priv->state) || - test_bit(DEV_CLEAR_BN, &nec_priv->state) || - test_bit(TIMO_NUM, &board->status))) { - retval = -ERESTARTSYS; - } - if (test_bit(TIMO_NUM, &board->status)) - retval = -ETIMEDOUT; - if (test_bit(DEV_CLEAR_BN, &nec_priv->state)) - retval = -EINTR; - - /* - * If we woke up because of end, wait until the dma transfer has pulled - * the data byte associated with the end before we cancel the dma transfer. - */ - if (test_bit(RECEIVED_END_BN, &nec_priv->state)) { - for (i = 0; i < timeout; ++i) { - if (test_bit(DMA_READ_IN_PROGRESS_BN, &nec_priv->state) == 0) - break; - if ((read_byte(nec_priv, ADR0) & DATA_IN_STATUS) == 0) - break; - usleep_range(10, 15); - } - if (i == timeout) - pr_warn("fluke_gpib: timeout waiting for dma to transfer end data byte.\n"); - } - - // stop the dma transfer - nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAI, 0); - /* - * delay a little just to make sure any bytes in dma controller's fifo get - * written to memory before we disable it - */ - usleep_range(10, 15); - residue = fluke_get_dma_residue(e_priv->dma_channel, dma_cookie); - if (WARN_ON_ONCE(residue > length || residue < 0)) - return -EFAULT; - *bytes_read += length - residue; - dmaengine_terminate_all(e_priv->dma_channel); - // make sure fluke_dma_callback got called - if (test_bit(DMA_READ_IN_PROGRESS_BN, &nec_priv->state)) - fluke_dma_callback(board); - - dma_unmap_single(board->dev, bus_address, length, DMA_FROM_DEVICE); - memcpy(buffer, e_priv->dma_buffer, *bytes_read); - - /* - * If we got an end interrupt, figure out if it was - * associated with the last byte we dma'd or with a - * byte still sitting on the cb7210. - */ - spin_lock_irqsave(&board->spinlock, flags); - if (test_bit(READ_READY_BN, &nec_priv->state) == 0) { - /* - * There is no byte sitting on the cb7210. If we - * saw an end interrupt, we need to deal with it now - */ - if (test_and_clear_bit(RECEIVED_END_BN, &nec_priv->state)) - *end = 1; - } - spin_unlock_irqrestore(&board->spinlock, flags); - - return retval; -} - -static int fluke_accel_read(struct gpib_board *board, u8 *buffer, size_t length, - int *end, size_t *bytes_read) -{ - struct fluke_priv *e_priv = board->private_data; - struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; - size_t remain = length; - size_t transfer_size; - int retval = 0; - size_t dma_nbytes; - - *end = 0; - *bytes_read = 0; - - smp_mb__before_atomic(); - clear_bit(DEV_CLEAR_BN, &nec_priv->state); // XXX FIXME - smp_mb__after_atomic(); - - retval = wait_for_read(board); - if (retval < 0) - return retval; - - nec7210_release_rfd_holdoff(board, nec_priv); - - while (remain > 0) { - transfer_size = (e_priv->dma_buffer_size < remain) ? - e_priv->dma_buffer_size : remain; - retval = fluke_dma_read(board, buffer, transfer_size, end, &dma_nbytes); - remain -= dma_nbytes; - buffer += dma_nbytes; - *bytes_read += dma_nbytes; - if (*end) - break; - if (retval < 0) - return retval; - if (need_resched()) - schedule(); - } - - return retval; -} - -static struct gpib_interface fluke_unaccel_interface = { - .name = "fluke_unaccel", - .attach = fluke_attach_holdoff_all, - .detach = fluke_detach, - .read = fluke_read, - .write = fluke_write, - .command = fluke_command, - .take_control = fluke_take_control, - .go_to_standby = fluke_go_to_standby, - .request_system_control = fluke_request_system_control, - .interface_clear = fluke_interface_clear, - .remote_enable = fluke_remote_enable, - .enable_eos = fluke_enable_eos, - .disable_eos = fluke_disable_eos, - .parallel_poll = fluke_parallel_poll, - .parallel_poll_configure = fluke_parallel_poll_configure, - .parallel_poll_response = fluke_parallel_poll_response, - .line_status = fluke_line_status, - .update_status = fluke_update_status, - .primary_address = fluke_primary_address, - .secondary_address = fluke_secondary_address, - .serial_poll_response = fluke_serial_poll_response, - .serial_poll_status = fluke_serial_poll_status, - .t1_delay = fluke_t1_delay, - .return_to_local = fluke_return_to_local, -}; - -/* - * fluke_hybrid uses dma for writes but not for reads. Added - * to deal with occasional corruption of bytes seen when doing dma - * reads. From looking at the cb7210 vhdl, I believe the corruption - * is due to a hardware bug triggered by the cpu reading a cb7210 - * } - * register just as the dma controller is also doing a read. - */ - -static struct gpib_interface fluke_hybrid_interface = { - .name = "fluke_hybrid", - .attach = fluke_attach_holdoff_all, - .detach = fluke_detach, - .read = fluke_read, - .write = fluke_accel_write, - .command = fluke_command, - .take_control = fluke_take_control, - .go_to_standby = fluke_go_to_standby, - .request_system_control = fluke_request_system_control, - .interface_clear = fluke_interface_clear, - .remote_enable = fluke_remote_enable, - .enable_eos = fluke_enable_eos, - .disable_eos = fluke_disable_eos, - .parallel_poll = fluke_parallel_poll, - .parallel_poll_configure = fluke_parallel_poll_configure, - .parallel_poll_response = fluke_parallel_poll_response, - .line_status = fluke_line_status, - .update_status = fluke_update_status, - .primary_address = fluke_primary_address, - .secondary_address = fluke_secondary_address, - .serial_poll_response = fluke_serial_poll_response, - .serial_poll_status = fluke_serial_poll_status, - .t1_delay = fluke_t1_delay, - .return_to_local = fluke_return_to_local, -}; - -static struct gpib_interface fluke_interface = { - .name = "fluke", - .attach = fluke_attach_holdoff_end, - .detach = fluke_detach, - .read = fluke_accel_read, - .write = fluke_accel_write, - .command = fluke_command, - .take_control = fluke_take_control, - .go_to_standby = fluke_go_to_standby, - .request_system_control = fluke_request_system_control, - .interface_clear = fluke_interface_clear, - .remote_enable = fluke_remote_enable, - .enable_eos = fluke_enable_eos, - .disable_eos = fluke_disable_eos, - .parallel_poll = fluke_parallel_poll, - .parallel_poll_configure = fluke_parallel_poll_configure, - .parallel_poll_response = fluke_parallel_poll_response, - .line_status = fluke_line_status, - .update_status = fluke_update_status, - .primary_address = fluke_primary_address, - .secondary_address = fluke_secondary_address, - .serial_poll_response = fluke_serial_poll_response, - .serial_poll_status = fluke_serial_poll_status, - .t1_delay = fluke_t1_delay, - .return_to_local = fluke_return_to_local, -}; - -irqreturn_t fluke_gpib_internal_interrupt(struct gpib_board *board) -{ - int status0, status1, status2; - struct fluke_priv *priv = board->private_data; - struct nec7210_priv *nec_priv = &priv->nec7210_priv; - int retval = IRQ_NONE; - - if (read_byte(nec_priv, ADR0) & DATA_IN_STATUS) - set_bit(READ_READY_BN, &nec_priv->state); - - status0 = fluke_paged_read_byte(priv, ISR0_IMR0, ISR0_IMR0_PAGE); - status1 = read_byte(nec_priv, ISR1); - status2 = read_byte(nec_priv, ISR2); - - if (status0 & FLUKE_IFCI_BIT) { - push_gpib_event(board, EVENT_IFC); - retval = IRQ_HANDLED; - } - - if (nec7210_interrupt_have_status(board, nec_priv, status1, status2) == IRQ_HANDLED) - retval = IRQ_HANDLED; - - if (read_byte(nec_priv, ADR0) & DATA_IN_STATUS) { - if (test_bit(RFD_HOLDOFF_BN, &nec_priv->state)) - set_bit(READ_READY_BN, &nec_priv->state); - else - clear_bit(READ_READY_BN, &nec_priv->state); - } - - if (retval == IRQ_HANDLED) - wake_up_interruptible(&board->wait); - - return retval; -} - -static irqreturn_t fluke_gpib_interrupt(int irq, void *arg) -{ - struct gpib_board *board = arg; - unsigned long flags; - irqreturn_t retval; - - spin_lock_irqsave(&board->spinlock, flags); - retval = fluke_gpib_internal_interrupt(board); - spin_unlock_irqrestore(&board->spinlock, flags); - return retval; -} - -static int fluke_allocate_private(struct gpib_board *board) -{ - struct fluke_priv *priv; - - board->private_data = kmalloc(sizeof(struct fluke_priv), GFP_KERNEL); - if (!board->private_data) - return -ENOMEM; - priv = board->private_data; - memset(priv, 0, sizeof(struct fluke_priv)); - init_nec7210_private(&priv->nec7210_priv); - priv->dma_buffer_size = 0x7ff; - priv->dma_buffer = kmalloc(priv->dma_buffer_size, GFP_KERNEL); - if (!priv->dma_buffer) - return -ENOMEM; - return 0; -} - -static void fluke_generic_detach(struct gpib_board *board) -{ - if (board->private_data) { - struct fluke_priv *e_priv = board->private_data; - - kfree(e_priv->dma_buffer); - kfree(board->private_data); - board->private_data = NULL; - } -} - -// generic part of attach functions shared by all cb7210 boards -static int fluke_generic_attach(struct gpib_board *board) -{ - struct fluke_priv *e_priv; - struct nec7210_priv *nec_priv; - int retval; - - board->status = 0; - - retval = fluke_allocate_private(board); - if (retval < 0) - return retval; - e_priv = board->private_data; - nec_priv = &e_priv->nec7210_priv; - nec_priv->read_byte = fluke_locking_read_byte; - nec_priv->write_byte = fluke_locking_write_byte; - nec_priv->offset = fluke_reg_offset; - nec_priv->type = CB7210; - return 0; -} - -static int fluke_config_dma(struct gpib_board *board, int output) -{ - struct fluke_priv *e_priv = board->private_data; - struct dma_slave_config config; - - config.src_maxburst = 1; - config.dst_maxburst = 1; - config.device_fc = true; - - if (output) { - config.direction = DMA_MEM_TO_DEV; - config.src_addr = 0; - config.dst_addr = e_priv->dma_port_res->start; - config.src_addr_width = 1; - config.dst_addr_width = 1; - } else { - config.direction = DMA_DEV_TO_MEM; - config.src_addr = e_priv->dma_port_res->start; - config.dst_addr = 0; - config.src_addr_width = 1; - config.dst_addr_width = 1; - } - return dmaengine_slave_config(e_priv->dma_channel, &config); -} - -static int fluke_init(struct fluke_priv *e_priv, struct gpib_board *board, int handshake_mode) -{ - struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; - - nec7210_board_reset(nec_priv, board); - write_byte(nec_priv, AUX_LO_SPEED, AUXMR); - /* - * set clock register for driving frequency - * ICR should be set to clock in megahertz (1-15) and to zero - * for clocks faster than 15 MHz (max 20MHz) - */ - write_byte(nec_priv, ICR | 10, AUXMR); - nec7210_set_handshake_mode(board, nec_priv, handshake_mode); - - nec7210_board_online(nec_priv, board); - - /* poll so we can detect ATN changes */ - if (gpib_request_pseudo_irq(board, fluke_gpib_interrupt)) { - dev_err(board->gpib_dev, "failed to allocate pseudo_irq\n"); - return -EINVAL; - } - - fluke_paged_write_byte(e_priv, FLUKE_IFCIE_BIT, ISR0_IMR0, ISR0_IMR0_PAGE); - return 0; -} - -/* - * This function is passed to dma_request_channel() in order to - * select the pl330 dma channel which has been hardwired to - * the gpib controller. - */ -static bool gpib_dma_channel_filter(struct dma_chan *chan, void *filter_param) -{ - // select the channel which is wired to the gpib chip - return chan->chan_id == 0; -} - -static int fluke_attach_impl(struct gpib_board *board, const struct gpib_board_config *config, - unsigned int handshake_mode) -{ - struct fluke_priv *e_priv; - struct nec7210_priv *nec_priv; - int isr_flags = 0; - int retval; - int irq; - struct resource *res; - dma_cap_mask_t dma_cap; - - if (!fluke_gpib_pdev) { - dev_err(board->gpib_dev, "No fluke device was found, attach failed.\n"); - return -ENODEV; - } - - retval = fluke_generic_attach(board); - if (retval) - return retval; - - e_priv = board->private_data; - nec_priv = &e_priv->nec7210_priv; - nec_priv->offset = fluke_reg_offset; - board->dev = &fluke_gpib_pdev->dev; - - res = platform_get_resource(fluke_gpib_pdev, IORESOURCE_MEM, 0); - if (!res) { - dev_err(&fluke_gpib_pdev->dev, "Unable to locate mmio resource\n"); - return -ENODEV; - } - - if (request_mem_region(res->start, - resource_size(res), - fluke_gpib_pdev->name) == NULL) { - dev_err(&fluke_gpib_pdev->dev, "cannot claim registers\n"); - return -ENXIO; - } - e_priv->gpib_iomem_res = res; - - nec_priv->mmiobase = ioremap(e_priv->gpib_iomem_res->start, - resource_size(e_priv->gpib_iomem_res)); - if (!nec_priv->mmiobase) { - dev_err(&fluke_gpib_pdev->dev, "Could not map I/O memory\n"); - return -ENOMEM; - } - - res = platform_get_resource(fluke_gpib_pdev, IORESOURCE_MEM, 1); - if (!res) { - dev_err(&fluke_gpib_pdev->dev, "Unable to locate mmio resource for gpib dma port\n"); - return -ENODEV; - } - if (request_mem_region(res->start, - resource_size(res), - fluke_gpib_pdev->name) == NULL) { - dev_err(&fluke_gpib_pdev->dev, "cannot claim registers\n"); - return -ENXIO; - } - e_priv->dma_port_res = res; - - res = platform_get_resource(fluke_gpib_pdev, IORESOURCE_MEM, 2); - if (!res) { - dev_err(&fluke_gpib_pdev->dev, "Unable to locate mmio resource for write transfer counter\n"); - return -ENODEV; - } - - if (request_mem_region(res->start, - resource_size(res), - fluke_gpib_pdev->name) == NULL) { - dev_err(&fluke_gpib_pdev->dev, "cannot claim registers\n"); - return -ENXIO; - } - e_priv->write_transfer_counter_res = res; - - e_priv->write_transfer_counter = ioremap(e_priv->write_transfer_counter_res->start, - resource_size(e_priv->write_transfer_counter_res)); - if (!e_priv->write_transfer_counter) { - dev_err(&fluke_gpib_pdev->dev, "Could not map I/O memory\n"); - return -ENOMEM; - } - - irq = platform_get_irq(fluke_gpib_pdev, 0); - if (irq < 0) - return -EBUSY; - retval = request_irq(irq, fluke_gpib_interrupt, isr_flags, fluke_gpib_pdev->name, board); - if (retval) { - dev_err(&fluke_gpib_pdev->dev, - "cannot register interrupt handler err=%d\n", - retval); - return retval; - } - e_priv->irq = irq; - - dma_cap_zero(dma_cap); - dma_cap_set(DMA_SLAVE, dma_cap); - e_priv->dma_channel = dma_request_channel(dma_cap, gpib_dma_channel_filter, NULL); - if (!e_priv->dma_channel) { - dev_err(board->gpib_dev, "failed to allocate a dma channel.\n"); - /* - * we don't error out here because unaccel interface will still - * work without dma - */ - } - - return fluke_init(e_priv, board, handshake_mode); -} - -int fluke_attach_holdoff_all(struct gpib_board *board, const struct gpib_board_config *config) -{ - return fluke_attach_impl(board, config, HR_HLDA); -} - -int fluke_attach_holdoff_end(struct gpib_board *board, const struct gpib_board_config *config) -{ - return fluke_attach_impl(board, config, HR_HLDE); -} - -void fluke_detach(struct gpib_board *board) -{ - struct fluke_priv *e_priv = board->private_data; - struct nec7210_priv *nec_priv; - - if (e_priv) { - if (e_priv->dma_channel) - dma_release_channel(e_priv->dma_channel); - gpib_free_pseudo_irq(board); - nec_priv = &e_priv->nec7210_priv; - - if (nec_priv->mmiobase) { - fluke_paged_write_byte(e_priv, 0, ISR0_IMR0, ISR0_IMR0_PAGE); - nec7210_board_reset(nec_priv, board); - } - if (e_priv->irq) - free_irq(e_priv->irq, board); - if (e_priv->write_transfer_counter_res) { - release_mem_region(e_priv->write_transfer_counter_res->start, - resource_size(e_priv->write_transfer_counter_res)); - } - if (e_priv->dma_port_res) { - release_mem_region(e_priv->dma_port_res->start, - resource_size(e_priv->dma_port_res)); - } - if (e_priv->gpib_iomem_res) - release_mem_region(e_priv->gpib_iomem_res->start, - resource_size(e_priv->gpib_iomem_res)); - } - fluke_generic_detach(board); -} - -static int fluke_gpib_probe(struct platform_device *pdev) -{ - fluke_gpib_pdev = pdev; - return 0; -} - -static const struct of_device_id fluke_gpib_of_match[] = { - { .compatible = "flk,fgpib-4.0"}, - { {0} } -}; -MODULE_DEVICE_TABLE(of, fluke_gpib_of_match); - -static struct platform_driver fluke_gpib_platform_driver = { - .driver = { - .name = DRV_NAME, - .of_match_table = fluke_gpib_of_match, - }, - .probe = &fluke_gpib_probe -}; - -static int __init fluke_init_module(void) -{ - int result; - - result = platform_driver_register(&fluke_gpib_platform_driver); - if (result) { - pr_err("platform_driver_register failed: error = %d\n", result); - return result; - } - - result = gpib_register_driver(&fluke_unaccel_interface, THIS_MODULE); - if (result) { - pr_err("gpib_register_driver failed: error = %d\n", result); - goto err_unaccel; - } - - result = gpib_register_driver(&fluke_hybrid_interface, THIS_MODULE); - if (result) { - pr_err("gpib_register_driver failed: error = %d\n", result); - goto err_hybrid; - } - - result = gpib_register_driver(&fluke_interface, THIS_MODULE); - if (result) { - pr_err("gpib_register_driver failed: error = %d\n", result); - goto err_interface; - } - - return 0; - -err_interface: - gpib_unregister_driver(&fluke_hybrid_interface); -err_hybrid: - gpib_unregister_driver(&fluke_unaccel_interface); -err_unaccel: - platform_driver_unregister(&fluke_gpib_platform_driver); - - return result; -} - -static void __exit fluke_exit_module(void) -{ - gpib_unregister_driver(&fluke_unaccel_interface); - gpib_unregister_driver(&fluke_hybrid_interface); - gpib_unregister_driver(&fluke_interface); - platform_driver_unregister(&fluke_gpib_platform_driver); -} - -module_init(fluke_init_module); -module_exit(fluke_exit_module); diff --git a/drivers/staging/gpib/eastwood/fluke_gpib.h b/drivers/staging/gpib/eastwood/fluke_gpib.h deleted file mode 100644 index 493c200d0bbf..000000000000 --- a/drivers/staging/gpib/eastwood/fluke_gpib.h +++ /dev/null @@ -1,146 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -/*************************************************************************** - * Author: Frank Mori Hess - * copyright: (C) 2006, 2010, 2015 Fluke Corporation - ***************************************************************************/ - -#include -#include -#include -#include -#include -#include "nec7210.h" - -struct fluke_priv { - struct nec7210_priv nec7210_priv; - struct resource *gpib_iomem_res; - struct resource *write_transfer_counter_res; - struct resource *dma_port_res; - int irq; - struct dma_chan *dma_channel; - u8 *dma_buffer; - int dma_buffer_size; - void __iomem *write_transfer_counter; -}; - -// cb7210 specific registers and bits -enum cb7210_regs { - STATE1_REG = 0x4, - ISR0_IMR0 = 0x6, - BUS_STATUS = 0x7 -}; - -enum cb7210_page_in { - ISR0_IMR0_PAGE = 1, - BUS_STATUS_PAGE = 1, - STATE1_PAGE = 1 -}; - -/* IMR0 -- Interrupt Mode Register 0 */ -enum imr0_bits { - FLUKE_IFCIE_BIT = 0x8, /* interface clear interrupt */ -}; - -/* ISR0 -- Interrupt Status Register 0 */ -enum isr0_bits { - FLUKE_IFCI_BIT = 0x8, /* interface clear interrupt */ -}; - -enum state1_bits { - SOURCE_HANDSHAKE_SIDS_BITS = 0x0, /* source idle state */ - SOURCE_HANDSHAKE_SGNS_BITS = 0x1, /* source generate state */ - SOURCE_HANDSHAKE_SDYS_BITS = 0x2, /* source delay state */ - SOURCE_HANDSHAKE_STRS_BITS = 0x5, /* source transfer state */ - SOURCE_HANDSHAKE_MASK = 0x7 -}; - -/* - * we customized the cb7210 vhdl to give the "data in" status - * on the unused bit 7 of the address0 register. - */ -enum cb7210_address0 { - DATA_IN_STATUS = 0x80 -}; - -static inline int cb7210_page_in_bits(unsigned int page) -{ - return 0x50 | (page & 0xf); -} - -// don't use without locking nec_priv->register_page_lock -static inline u8 fluke_read_byte_nolock(struct nec7210_priv *nec_priv, - int register_num) -{ - u8 retval; - - retval = readl(nec_priv->mmiobase + register_num * nec_priv->offset); - return retval; -} - -// don't use without locking nec_priv->register_page_lock -static inline void fluke_write_byte_nolock(struct nec7210_priv *nec_priv, u8 data, - int register_num) -{ - writel(data, nec_priv->mmiobase + register_num * nec_priv->offset); -} - -static inline u8 fluke_paged_read_byte(struct fluke_priv *e_priv, - unsigned int register_num, unsigned int page) -{ - struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; - u8 retval; - unsigned long flags; - - spin_lock_irqsave(&nec_priv->register_page_lock, flags); - fluke_write_byte_nolock(nec_priv, cb7210_page_in_bits(page), AUXMR); - udelay(1); - /* chip auto clears the page after a read */ - retval = fluke_read_byte_nolock(nec_priv, register_num); - spin_unlock_irqrestore(&nec_priv->register_page_lock, flags); - return retval; -} - -static inline void fluke_paged_write_byte(struct fluke_priv *e_priv, u8 data, - unsigned int register_num, unsigned int page) -{ - struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; - unsigned long flags; - - spin_lock_irqsave(&nec_priv->register_page_lock, flags); - fluke_write_byte_nolock(nec_priv, cb7210_page_in_bits(page), AUXMR); - udelay(1); - fluke_write_byte_nolock(nec_priv, data, register_num); - spin_unlock_irqrestore(&nec_priv->register_page_lock, flags); -} - -enum bus_status_bits { - BSR_ATN_BIT = 0x1, - BSR_EOI_BIT = 0x2, - BSR_SRQ_BIT = 0x4, - BSR_IFC_BIT = 0x8, - BSR_REN_BIT = 0x10, - BSR_DAV_BIT = 0x20, - BSR_NRFD_BIT = 0x40, - BSR_NDAC_BIT = 0x80, -}; - -enum cb7210_aux_cmds { -/* - * AUX_RTL2 is an undocumented aux command which causes cb7210 to assert - * (and keep asserted) local rtl message. This is used in conjunction - * with the (stupid) cb7210 implementation - * of the normal nec7210 AUX_RTL aux command, which - * causes the rtl message to toggle between on and off. - */ - AUX_RTL2 = 0xd, - AUX_NBAF = 0xe, // new byte available false (also clears seoi) - AUX_LO_SPEED = 0x40, - AUX_HI_SPEED = 0x41, -}; - -enum { - fluke_reg_offset = 4, - fluke_num_regs = 8, - write_transfer_counter_mask = 0x7ff, -}; diff --git a/drivers/staging/gpib/fmh_gpib/Makefile b/drivers/staging/gpib/fmh_gpib/Makefile deleted file mode 100644 index cc4d9e7cd5cd..000000000000 --- a/drivers/staging/gpib/fmh_gpib/Makefile +++ /dev/null @@ -1,2 +0,0 @@ - -obj-$(CONFIG_GPIB_FMH) += fmh_gpib.o diff --git a/drivers/staging/gpib/fmh_gpib/fmh_gpib.c b/drivers/staging/gpib/fmh_gpib/fmh_gpib.c deleted file mode 100644 index f7bfb4a8e553..000000000000 --- a/drivers/staging/gpib/fmh_gpib/fmh_gpib.c +++ /dev/null @@ -1,1754 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 - -/*************************************************************************** - * GPIB Driver for fmh_gpib_core, see - * https://github.com/fmhess/fmh_gpib_core - * - * More specifically, it is a driver for the hardware arrangement described by - * src/examples/fmh_gpib_top.vhd in the fmh_gpib_core repository. - * - * Author: Frank Mori Hess - * Copyright: (C) 2006, 2010, 2015 Fluke Corporation - * (C) 2017 Frank Mori Hess - ***************************************************************************/ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -#define dev_fmt pr_fmt -#define DRV_NAME KBUILD_MODNAME - -#include "fmh_gpib.h" - -#include "gpibP.h" -#include -#include -#include -#include -#include -#include -#include - -MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("GPIB Driver for fmh_gpib_core"); -MODULE_AUTHOR("Frank Mori Hess "); - -static irqreturn_t fmh_gpib_interrupt(int irq, void *arg); -static int fmh_gpib_attach_holdoff_all(struct gpib_board *board, - const struct gpib_board_config *config); -static int fmh_gpib_attach_holdoff_end(struct gpib_board *board, - const struct gpib_board_config *config); -static void fmh_gpib_detach(struct gpib_board *board); -static int fmh_gpib_pci_attach_holdoff_all(struct gpib_board *board, - const struct gpib_board_config *config); -static int fmh_gpib_pci_attach_holdoff_end(struct gpib_board *board, - const struct gpib_board_config *config); -static void fmh_gpib_pci_detach(struct gpib_board *board); -static int fmh_gpib_config_dma(struct gpib_board *board, int output); -static irqreturn_t fmh_gpib_internal_interrupt(struct gpib_board *board); -static struct platform_driver fmh_gpib_platform_driver; -static struct pci_driver fmh_gpib_pci_driver; - -// wrappers for interface functions -static int fmh_gpib_read(struct gpib_board *board, u8 *buffer, size_t length, - int *end, size_t *bytes_read) -{ - struct fmh_priv *priv = board->private_data; - - return nec7210_read(board, &priv->nec7210_priv, buffer, length, end, bytes_read); -} - -static int fmh_gpib_write(struct gpib_board *board, u8 *buffer, size_t length, - int send_eoi, size_t *bytes_written) -{ - struct fmh_priv *priv = board->private_data; - - return nec7210_write(board, &priv->nec7210_priv, buffer, length, send_eoi, bytes_written); -} - -static int fmh_gpib_command(struct gpib_board *board, u8 *buffer, size_t length, - size_t *bytes_written) -{ - struct fmh_priv *priv = board->private_data; - - return nec7210_command(board, &priv->nec7210_priv, buffer, length, bytes_written); -} - -static int fmh_gpib_take_control(struct gpib_board *board, int synchronous) -{ - struct fmh_priv *priv = board->private_data; - - return nec7210_take_control(board, &priv->nec7210_priv, synchronous); -} - -static int fmh_gpib_go_to_standby(struct gpib_board *board) -{ - struct fmh_priv *priv = board->private_data; - - return nec7210_go_to_standby(board, &priv->nec7210_priv); -} - -static int fmh_gpib_request_system_control(struct gpib_board *board, int request_control) -{ - struct fmh_priv *priv = board->private_data; - struct nec7210_priv *nec_priv = &priv->nec7210_priv; - - return nec7210_request_system_control(board, nec_priv, request_control); -} - -static void fmh_gpib_interface_clear(struct gpib_board *board, int assert) -{ - struct fmh_priv *priv = board->private_data; - - nec7210_interface_clear(board, &priv->nec7210_priv, assert); -} - -static void fmh_gpib_remote_enable(struct gpib_board *board, int enable) -{ - struct fmh_priv *priv = board->private_data; - - nec7210_remote_enable(board, &priv->nec7210_priv, enable); -} - -static int fmh_gpib_enable_eos(struct gpib_board *board, u8 eos_byte, int compare_8_bits) -{ - struct fmh_priv *priv = board->private_data; - - return nec7210_enable_eos(board, &priv->nec7210_priv, eos_byte, compare_8_bits); -} - -static void fmh_gpib_disable_eos(struct gpib_board *board) -{ - struct fmh_priv *priv = board->private_data; - - nec7210_disable_eos(board, &priv->nec7210_priv); -} - -static unsigned int fmh_gpib_update_status(struct gpib_board *board, unsigned int clear_mask) -{ - struct fmh_priv *priv = board->private_data; - - return nec7210_update_status(board, &priv->nec7210_priv, clear_mask); -} - -static int fmh_gpib_primary_address(struct gpib_board *board, unsigned int address) -{ - struct fmh_priv *priv = board->private_data; - - return nec7210_primary_address(board, &priv->nec7210_priv, address); -} - -static int fmh_gpib_secondary_address(struct gpib_board *board, unsigned int address, int enable) -{ - struct fmh_priv *priv = board->private_data; - - return nec7210_secondary_address(board, &priv->nec7210_priv, address, enable); -} - -static int fmh_gpib_parallel_poll(struct gpib_board *board, u8 *result) -{ - struct fmh_priv *priv = board->private_data; - - return nec7210_parallel_poll(board, &priv->nec7210_priv, result); -} - -static void fmh_gpib_parallel_poll_configure(struct gpib_board *board, u8 configuration) -{ - struct fmh_priv *priv = board->private_data; - - nec7210_parallel_poll_configure(board, &priv->nec7210_priv, configuration); -} - -static void fmh_gpib_parallel_poll_response(struct gpib_board *board, int ist) -{ - struct fmh_priv *priv = board->private_data; - - nec7210_parallel_poll_response(board, &priv->nec7210_priv, ist); -} - -static void fmh_gpib_local_parallel_poll_mode(struct gpib_board *board, int local) -{ - struct fmh_priv *priv = board->private_data; - - if (local) { - write_byte(&priv->nec7210_priv, AUX_I_REG | LOCAL_PPOLL_MODE_BIT, AUXMR); - } else { - /* - * For fmh_gpib_core, remote parallel poll config mode is unaffected by the - * state of the disable bit of the parallel poll register (unlike the tnt4882). - * So, we don't need to worry about that. - */ - write_byte(&priv->nec7210_priv, AUX_I_REG | 0x0, AUXMR); - } -} - -static void fmh_gpib_serial_poll_response2(struct gpib_board *board, u8 status, - int new_reason_for_service) -{ - struct fmh_priv *priv = board->private_data; - unsigned long flags; - const int MSS = status & request_service_bit; - const int reqt = MSS && new_reason_for_service; - const int reqf = MSS == 0; - - spin_lock_irqsave(&board->spinlock, flags); - if (reqt) { - priv->nec7210_priv.srq_pending = 1; - clear_bit(SPOLL_NUM, &board->status); - } else if (reqf) { - priv->nec7210_priv.srq_pending = 0; - } - - if (reqt) { - /* - * It may seem like a race to issue reqt before updating - * the status byte, but it is not. The chip does not - * issue the reqt until the SPMR is written to at - * a later time. - */ - write_byte(&priv->nec7210_priv, AUX_REQT, AUXMR); - } else if (reqf) { - write_byte(&priv->nec7210_priv, AUX_REQF, AUXMR); - } - /* - * We need to always zero bit 6 of the status byte before writing it to - * the SPMR to insure we are using - * serial poll mode SP1, and not accidentally triggering mode SP3. - */ - write_byte(&priv->nec7210_priv, status & ~request_service_bit, SPMR); - spin_unlock_irqrestore(&board->spinlock, flags); -} - -static u8 fmh_gpib_serial_poll_status(struct gpib_board *board) -{ - struct fmh_priv *priv = board->private_data; - - return nec7210_serial_poll_status(board, &priv->nec7210_priv); -} - -static void fmh_gpib_return_to_local(struct gpib_board *board) -{ - struct fmh_priv *priv = board->private_data; - struct nec7210_priv *nec_priv = &priv->nec7210_priv; - - write_byte(nec_priv, AUX_RTL2, AUXMR); - udelay(1); - write_byte(nec_priv, AUX_RTL, AUXMR); -} - -static int fmh_gpib_line_status(const struct gpib_board *board) -{ - int status = VALID_ALL; - int bsr_bits; - struct fmh_priv *e_priv; - struct nec7210_priv *nec_priv; - - e_priv = board->private_data; - nec_priv = &e_priv->nec7210_priv; - - bsr_bits = read_byte(nec_priv, BUS_STATUS_REG); - - if ((bsr_bits & BSR_REN_BIT) == 0) - status |= BUS_REN; - if ((bsr_bits & BSR_IFC_BIT) == 0) - status |= BUS_IFC; - if ((bsr_bits & BSR_SRQ_BIT) == 0) - status |= BUS_SRQ; - if ((bsr_bits & BSR_EOI_BIT) == 0) - status |= BUS_EOI; - if ((bsr_bits & BSR_NRFD_BIT) == 0) - status |= BUS_NRFD; - if ((bsr_bits & BSR_NDAC_BIT) == 0) - status |= BUS_NDAC; - if ((bsr_bits & BSR_DAV_BIT) == 0) - status |= BUS_DAV; - if ((bsr_bits & BSR_ATN_BIT) == 0) - status |= BUS_ATN; - - return status; -} - -static int fmh_gpib_t1_delay(struct gpib_board *board, unsigned int nano_sec) -{ - struct fmh_priv *e_priv = board->private_data; - struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; - unsigned int retval; - - retval = nec7210_t1_delay(board, nec_priv, nano_sec); - - if (nano_sec <= 350) { - write_byte(nec_priv, AUX_HI_SPEED, AUXMR); - retval = 350; - } else { - write_byte(nec_priv, AUX_LO_SPEED, AUXMR); - } - return retval; -} - -static int lacs_or_read_ready(struct gpib_board *board) -{ - const struct fmh_priv *e_priv = board->private_data; - const struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; - int retval = 0; - unsigned long flags; - - spin_lock_irqsave(&board->spinlock, flags); - retval = test_bit(LACS_NUM, &board->status) || - test_bit(READ_READY_BN, &nec_priv->state); - spin_unlock_irqrestore(&board->spinlock, flags); - - return retval; -} - -static int wait_for_read(struct gpib_board *board) -{ - struct fmh_priv *e_priv = board->private_data; - struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; - int retval = 0; - - if (wait_event_interruptible(board->wait, - lacs_or_read_ready(board) || - test_bit(DEV_CLEAR_BN, &nec_priv->state) || - test_bit(TIMO_NUM, &board->status))) - retval = -ERESTARTSYS; - - if (test_bit(TIMO_NUM, &board->status)) - retval = -ETIMEDOUT; - if (test_and_clear_bit(DEV_CLEAR_BN, &nec_priv->state)) - retval = -EINTR; - return retval; -} - -static int wait_for_rx_fifo_half_full_or_end(struct gpib_board *board) -{ - struct fmh_priv *e_priv = board->private_data; - struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; - int retval = 0; - - if (wait_event_interruptible(board->wait, - (fifos_read(e_priv, FIFO_CONTROL_STATUS_REG) & - RX_FIFO_HALF_FULL) || - test_bit(RECEIVED_END_BN, &nec_priv->state) || - test_bit(DEV_CLEAR_BN, &nec_priv->state) || - test_bit(TIMO_NUM, &board->status))) - retval = -ERESTARTSYS; - - if (test_bit(TIMO_NUM, &board->status)) - retval = -ETIMEDOUT; - if (test_and_clear_bit(DEV_CLEAR_BN, &nec_priv->state)) - retval = -EINTR; - return retval; -} - -/* - * Wait until the gpib chip is ready to accept a data out byte. - */ -static int wait_for_data_out_ready(struct gpib_board *board) -{ - struct fmh_priv *e_priv = board->private_data; - struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; - int retval = 0; - - if (wait_event_interruptible(board->wait, - (test_bit(TACS_NUM, &board->status) && - (read_byte(nec_priv, EXT_STATUS_1_REG) & - DATA_OUT_STATUS_BIT)) || - test_bit(DEV_CLEAR_BN, &nec_priv->state) || - test_bit(TIMO_NUM, &board->status))) - retval = -ERESTARTSYS; - - if (test_bit(TIMO_NUM, &board->status)) - retval = -ETIMEDOUT; - if (test_and_clear_bit(DEV_CLEAR_BN, &nec_priv->state)) - retval = -EINTR; - - return retval; -} - -static void fmh_gpib_dma_callback(void *arg) -{ - struct gpib_board *board = arg; - struct fmh_priv *e_priv = board->private_data; - struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; - unsigned long flags; - - spin_lock_irqsave(&board->spinlock, flags); - - nec7210_set_reg_bits(nec_priv, IMR1, HR_DOIE | HR_DIIE, HR_DOIE | HR_DIIE); - wake_up_interruptible(&board->wait); - - fmh_gpib_internal_interrupt(board); - - clear_bit(DMA_WRITE_IN_PROGRESS_BN, &nec_priv->state); - clear_bit(DMA_READ_IN_PROGRESS_BN, &nec_priv->state); - - spin_unlock_irqrestore(&board->spinlock, flags); -} - -/* - * returns true when all the bytes of a write have been transferred to - * the chip and successfully transferred out over the gpib bus. - */ -static int fmh_gpib_all_bytes_are_sent(struct fmh_priv *e_priv) -{ - if (fifos_read(e_priv, FIFO_XFER_COUNTER_REG) & fifo_xfer_counter_mask) - return 0; - - if ((read_byte(&e_priv->nec7210_priv, EXT_STATUS_1_REG) & DATA_OUT_STATUS_BIT) == 0) - return 0; - - return 1; -} - -static int fmh_gpib_dma_write(struct gpib_board *board, u8 *buffer, size_t length, - size_t *bytes_written) -{ - struct fmh_priv *e_priv = board->private_data; - struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; - unsigned long flags; - int retval = 0; - dma_addr_t address; - struct dma_async_tx_descriptor *tx_desc; - - *bytes_written = 0; - if (WARN_ON_ONCE(length > e_priv->dma_buffer_size)) - return -EFAULT; - dmaengine_terminate_all(e_priv->dma_channel); - memcpy(e_priv->dma_buffer, buffer, length); - address = dma_map_single(board->dev, e_priv->dma_buffer, length, DMA_TO_DEVICE); - if (dma_mapping_error(board->dev, address)) - dev_err(board->gpib_dev, "dma mapping error in dma write!\n"); - /* program dma controller */ - retval = fmh_gpib_config_dma(board, 1); - if (retval) - goto cleanup; - - tx_desc = dmaengine_prep_slave_single(e_priv->dma_channel, address, length, DMA_MEM_TO_DEV, - DMA_PREP_INTERRUPT | DMA_CTRL_ACK); - if (!tx_desc) { - dev_err(board->gpib_dev, "failed to allocate dma transmit descriptor\n"); - retval = -ENOMEM; - goto cleanup; - } - tx_desc->callback = fmh_gpib_dma_callback; - tx_desc->callback_param = board; - - spin_lock_irqsave(&board->spinlock, flags); - fifos_write(e_priv, length & fifo_xfer_counter_mask, FIFO_XFER_COUNTER_REG); - fifos_write(e_priv, TX_FIFO_DMA_REQUEST_ENABLE | TX_FIFO_CLEAR, FIFO_CONTROL_STATUS_REG); - nec7210_set_reg_bits(nec_priv, IMR1, HR_DOIE, 0); - nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAO, HR_DMAO); - - dmaengine_submit(tx_desc); - dma_async_issue_pending(e_priv->dma_channel); - clear_bit(WRITE_READY_BN, &nec_priv->state); - set_bit(DMA_WRITE_IN_PROGRESS_BN, &nec_priv->state); - - spin_unlock_irqrestore(&board->spinlock, flags); - - // suspend until message is sent - if (wait_event_interruptible(board->wait, - fmh_gpib_all_bytes_are_sent(e_priv) || - test_bit(BUS_ERROR_BN, &nec_priv->state) || - test_bit(DEV_CLEAR_BN, &nec_priv->state) || - test_bit(TIMO_NUM, &board->status))) - retval = -ERESTARTSYS; - - if (test_bit(TIMO_NUM, &board->status)) - retval = -ETIMEDOUT; - if (test_and_clear_bit(DEV_CLEAR_BN, &nec_priv->state)) - retval = -EINTR; - if (test_and_clear_bit(BUS_ERROR_BN, &nec_priv->state)) - retval = -EIO; - // disable board's dma - nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAO, 0); - fifos_write(e_priv, 0, FIFO_CONTROL_STATUS_REG); - - dmaengine_terminate_all(e_priv->dma_channel); - // make sure fmh_gpib_dma_callback got called - if (test_bit(DMA_WRITE_IN_PROGRESS_BN, &nec_priv->state)) - fmh_gpib_dma_callback(board); - - *bytes_written = length - (fifos_read(e_priv, FIFO_XFER_COUNTER_REG) & - fifo_xfer_counter_mask); - if (WARN_ON_ONCE(*bytes_written > length)) - return -EFAULT; -cleanup: - dma_unmap_single(board->dev, address, length, DMA_TO_DEVICE); - return retval; -} - -static int fmh_gpib_accel_write(struct gpib_board *board, u8 *buffer, - size_t length, int send_eoi, size_t *bytes_written) -{ - struct fmh_priv *e_priv = board->private_data; - struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; - size_t remainder = length; - size_t transfer_size; - ssize_t retval = 0; - size_t dma_remainder = remainder; - - if (!e_priv->dma_channel) { - dev_err(board->gpib_dev, "No dma channel available, cannot do accel write."); - return -ENXIO; - } - - *bytes_written = 0; - if (length < 1) - return 0; - - smp_mb__before_atomic(); - clear_bit(DEV_CLEAR_BN, &nec_priv->state); // XXX FIXME - smp_mb__after_atomic(); - - if (send_eoi) - --dma_remainder; - - while (dma_remainder > 0) { - size_t num_bytes; - - retval = wait_for_data_out_ready(board); - if (retval < 0) - break; - - transfer_size = (e_priv->dma_buffer_size < dma_remainder) ? - e_priv->dma_buffer_size : dma_remainder; - retval = fmh_gpib_dma_write(board, buffer, transfer_size, &num_bytes); - *bytes_written += num_bytes; - if (retval < 0) - break; - dma_remainder -= num_bytes; - remainder -= num_bytes; - buffer += num_bytes; - if (need_resched()) - schedule(); - } - if (retval < 0) - return retval; - // handle sending of last byte with eoi - if (send_eoi) { - size_t num_bytes; - - if (WARN_ON_ONCE(remainder != 1)) - return -EFAULT; - - /* - * wait until we are sure we will be able to write the data byte - * into the chip before we send AUX_SEOI. This prevents a timeout - * scenario where we send AUX_SEOI but then timeout without getting - * any bytes into the gpib chip. This will result in the first byte - * of the next write having a spurious EOI set on the first byte. - */ - retval = wait_for_data_out_ready(board); - if (retval < 0) - return retval; - - write_byte(nec_priv, AUX_SEOI, AUXMR); - retval = fmh_gpib_dma_write(board, buffer, remainder, &num_bytes); - *bytes_written += num_bytes; - if (retval < 0) - return retval; - remainder -= num_bytes; - } - return 0; -} - -static int fmh_gpib_get_dma_residue(struct dma_chan *chan, dma_cookie_t cookie) -{ - struct dma_tx_state state; - int result; - - result = dmaengine_pause(chan); - if (result < 0) { - pr_err("dma pause failed?\n"); - return result; - } - dmaengine_tx_status(chan, cookie, &state); - /* - * dma330 hardware doesn't support resume, so dont call this - * method unless the dma transfer is done. - */ - return state.residue; -} - -static int wait_for_tx_fifo_half_empty(struct gpib_board *board) -{ - struct fmh_priv *e_priv = board->private_data; - struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; - int retval = 0; - - if (wait_event_interruptible(board->wait, - (test_bit(TACS_NUM, &board->status) && - (fifos_read(e_priv, FIFO_CONTROL_STATUS_REG) & - TX_FIFO_HALF_EMPTY)) || - test_bit(DEV_CLEAR_BN, &nec_priv->state) || - test_bit(TIMO_NUM, &board->status))) - retval = -ERESTARTSYS; - - if (test_bit(TIMO_NUM, &board->status)) - retval = -ETIMEDOUT; - if (test_and_clear_bit(DEV_CLEAR_BN, &nec_priv->state)) - retval = -EINTR; - - return retval; -} - -/* - * supports writing a chunk of data whose length must fit into the hardware'd xfer counter, - * called in a loop by fmh_gpib_fifo_write() - */ -static int fmh_gpib_fifo_write_countable(struct gpib_board *board, u8 *buffer, - size_t length, int send_eoi, size_t *bytes_written) -{ - struct fmh_priv *e_priv = board->private_data; - struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; - int retval = 0; - unsigned int remainder; - - *bytes_written = 0; - if (WARN_ON_ONCE(length > fifo_xfer_counter_mask)) - return -EFAULT; - - fifos_write(e_priv, length & fifo_xfer_counter_mask, FIFO_XFER_COUNTER_REG); - fifos_write(e_priv, TX_FIFO_CLEAR, FIFO_CONTROL_STATUS_REG); - nec7210_set_reg_bits(nec_priv, IMR1, HR_DOIE, 0); - nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAO, HR_DMAO); - - remainder = length; - while (remainder > 0) { - int i; - - fifos_write(e_priv, TX_FIFO_HALF_EMPTY_INTERRUPT_ENABLE, FIFO_CONTROL_STATUS_REG); - retval = wait_for_tx_fifo_half_empty(board); - if (retval < 0) - goto cleanup; - - for (i = 0; i < fmh_gpib_half_fifo_size(e_priv) && remainder > 0; ++i) { - unsigned int data_value = *buffer; - - if (send_eoi && remainder == 1) - data_value |= FIFO_DATA_EOI_FLAG; - fifos_write(e_priv, data_value, FIFO_DATA_REG); - ++buffer; - --remainder; - } - } - - // suspend until last byte is sent - nec7210_set_reg_bits(nec_priv, IMR1, HR_DOIE, HR_DOIE); - if (wait_event_interruptible(board->wait, - fmh_gpib_all_bytes_are_sent(e_priv) || - test_bit(BUS_ERROR_BN, &nec_priv->state) || - test_bit(DEV_CLEAR_BN, &nec_priv->state) || - test_bit(TIMO_NUM, &board->status))) - retval = -ERESTARTSYS; - - if (test_bit(TIMO_NUM, &board->status)) - retval = -ETIMEDOUT; - if (test_and_clear_bit(DEV_CLEAR_BN, &nec_priv->state)) - retval = -EINTR; - if (test_and_clear_bit(BUS_ERROR_BN, &nec_priv->state)) - retval = -EIO; - -cleanup: - nec7210_set_reg_bits(nec_priv, IMR1, HR_DOIE, 0); - nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAO, 0); - fifos_write(e_priv, 0, FIFO_CONTROL_STATUS_REG); - - *bytes_written = length - (fifos_read(e_priv, FIFO_XFER_COUNTER_REG) & - fifo_xfer_counter_mask); - if (WARN_ON_ONCE(*bytes_written > length)) - return -EFAULT; - - return retval; -} - -static int fmh_gpib_fifo_write(struct gpib_board *board, u8 *buffer, size_t length, - int send_eoi, size_t *bytes_written) -{ - struct fmh_priv *e_priv = board->private_data; - struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; - size_t remainder = length; - size_t transfer_size; - ssize_t retval = 0; - - *bytes_written = 0; - if (length < 1) - return 0; - - clear_bit(DEV_CLEAR_BN, &nec_priv->state); // XXX FIXME - - while (remainder > 0) { - size_t num_bytes; - int last_pass; - - retval = wait_for_data_out_ready(board); - if (retval < 0) - break; - - if (fifo_xfer_counter_mask < remainder) { - // round transfer size to a multiple of half fifo size - transfer_size = (fifo_xfer_counter_mask / - fmh_gpib_half_fifo_size(e_priv)) * - fmh_gpib_half_fifo_size(e_priv); - last_pass = 0; - } else { - transfer_size = remainder; - last_pass = 1; - } - retval = fmh_gpib_fifo_write_countable(board, buffer, transfer_size, - last_pass && send_eoi, &num_bytes); - *bytes_written += num_bytes; - if (retval < 0) - break; - remainder -= num_bytes; - buffer += num_bytes; - if (need_resched()) - schedule(); - } - - return retval; -} - -static int fmh_gpib_dma_read(struct gpib_board *board, u8 *buffer, - size_t length, int *end, size_t *bytes_read) -{ - struct fmh_priv *e_priv = board->private_data; - struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; - int retval = 0; - unsigned long flags; - int residue; - int wait_retval; - dma_addr_t bus_address; - struct dma_async_tx_descriptor *tx_desc; - dma_cookie_t dma_cookie; - - *bytes_read = 0; - *end = 0; - if (length == 0) - return 0; - - bus_address = dma_map_single(board->dev, e_priv->dma_buffer, - length, DMA_FROM_DEVICE); - if (dma_mapping_error(board->dev, bus_address)) - dev_err(board->gpib_dev, "dma mapping error in dma read!"); - - /* program dma controller */ - retval = fmh_gpib_config_dma(board, 0); - if (retval) { - dma_unmap_single(board->dev, bus_address, length, DMA_FROM_DEVICE); - return retval; - } - tx_desc = dmaengine_prep_slave_single(e_priv->dma_channel, bus_address, - length, DMA_DEV_TO_MEM, - DMA_PREP_INTERRUPT | DMA_CTRL_ACK); - if (!tx_desc) { - dev_err(board->gpib_dev, "failed to allocate dma transmit descriptor\n"); - dma_unmap_single(board->dev, bus_address, length, DMA_FROM_DEVICE); - return -EIO; - } - tx_desc->callback = fmh_gpib_dma_callback; - tx_desc->callback_param = board; - - spin_lock_irqsave(&board->spinlock, flags); - // enable nec7210 dma - fifos_write(e_priv, length & fifo_xfer_counter_mask, FIFO_XFER_COUNTER_REG); - fifos_write(e_priv, RX_FIFO_DMA_REQUEST_ENABLE | RX_FIFO_CLEAR, FIFO_CONTROL_STATUS_REG); - nec7210_set_reg_bits(nec_priv, IMR1, HR_DIIE, 0); - nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAI, HR_DMAI); - - dma_cookie = dmaengine_submit(tx_desc); - dma_async_issue_pending(e_priv->dma_channel); - - set_bit(DMA_READ_IN_PROGRESS_BN, &nec_priv->state); - - spin_unlock_irqrestore(&board->spinlock, flags); - - // wait for data to transfer - wait_retval = wait_event_interruptible(board->wait, - test_bit(DMA_READ_IN_PROGRESS_BN, &nec_priv->state) - == 0 || - test_bit(RECEIVED_END_BN, &nec_priv->state) || - test_bit(DEV_CLEAR_BN, &nec_priv->state) || - test_bit(TIMO_NUM, &board->status)); - if (wait_retval) - retval = -ERESTARTSYS; - - if (test_bit(TIMO_NUM, &board->status)) - retval = -ETIMEDOUT; - if (test_bit(DEV_CLEAR_BN, &nec_priv->state)) - retval = -EINTR; - // stop the dma transfer - nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAI, 0); - fifos_write(e_priv, 0, FIFO_CONTROL_STATUS_REG); - /* - * give time for pl330 to transfer any in-flight data, since - * pl330 will throw it away when dmaengine_pause is called. - */ - usleep_range(10, 15); - residue = fmh_gpib_get_dma_residue(e_priv->dma_channel, dma_cookie); - if (WARN_ON_ONCE(residue > length || residue < 0)) - return -EFAULT; - *bytes_read += length - residue; - dmaengine_terminate_all(e_priv->dma_channel); - // make sure fmh_gpib_dma_callback got called - if (test_bit(DMA_READ_IN_PROGRESS_BN, &nec_priv->state)) - fmh_gpib_dma_callback(board); - - dma_unmap_single(board->dev, bus_address, length, DMA_FROM_DEVICE); - memcpy(buffer, e_priv->dma_buffer, *bytes_read); - - /* Manually read any dregs out of fifo. */ - while ((fifos_read(e_priv, FIFO_CONTROL_STATUS_REG) & RX_FIFO_EMPTY) == 0) { - if ((*bytes_read) >= length) { - dev_err(board->dev, "unexpected extra bytes in rx fifo, discarding! bytes_read=%d length=%d residue=%d\n", - (int)(*bytes_read), (int)length, (int)residue); - break; - } - buffer[(*bytes_read)++] = fifos_read(e_priv, FIFO_DATA_REG) & fifo_data_mask; - } - - /* - * If we got an end interrupt, figure out if it was - * associated with the last byte we dma'd or with a - * byte still sitting on the cb7210. - */ - spin_lock_irqsave(&board->spinlock, flags); - if (*bytes_read > 0 && test_bit(READ_READY_BN, &nec_priv->state) == 0) { - /* - * If there is no byte sitting on the cb7210 and we - * saw an end, we need to deal with it now - */ - if (test_and_clear_bit(RECEIVED_END_BN, &nec_priv->state)) - *end = 1; - } - spin_unlock_irqrestore(&board->spinlock, flags); - - return retval; -} - -static void fmh_gpib_release_rfd_holdoff(struct gpib_board *board, struct fmh_priv *e_priv) -{ - struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; - unsigned int ext_status_1; - unsigned long flags; - - spin_lock_irqsave(&board->spinlock, flags); - - ext_status_1 = read_byte(nec_priv, EXT_STATUS_1_REG); - - /* - * if there is an end byte sitting on the chip, don't release - * holdoff. We want it left set after we read out the end - * byte. - */ - if ((ext_status_1 & (DATA_IN_STATUS_BIT | END_STATUS_BIT)) != - (DATA_IN_STATUS_BIT | END_STATUS_BIT)) { - if (ext_status_1 & RFD_HOLDOFF_STATUS_BIT) - write_byte(nec_priv, AUX_FH, AUXMR); - - /* - * Check if an end byte raced in before we executed the AUX_FH command. - * If it did, we want to make sure the rfd holdoff is in effect. The end - * byte can arrive since - * AUX_RFD_HOLDOFF_ASAP doesn't immediately force the acceptor handshake - * to leave ACRS. - */ - if ((read_byte(nec_priv, EXT_STATUS_1_REG) & - (RFD_HOLDOFF_STATUS_BIT | DATA_IN_STATUS_BIT | END_STATUS_BIT)) == - (DATA_IN_STATUS_BIT | END_STATUS_BIT)) { - write_byte(nec_priv, AUX_RFD_HOLDOFF_ASAP, AUXMR); - set_bit(RFD_HOLDOFF_BN, &nec_priv->state); - } else { - clear_bit(RFD_HOLDOFF_BN, &nec_priv->state); - } - } - spin_unlock_irqrestore(&board->spinlock, flags); -} - -static int fmh_gpib_accel_read(struct gpib_board *board, u8 *buffer, size_t length, - int *end, size_t *bytes_read) -{ - struct fmh_priv *e_priv = board->private_data; - struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; - size_t remain = length; - size_t transfer_size; - int retval = 0; - size_t dma_nbytes; - unsigned long flags; - - smp_mb__before_atomic(); - clear_bit(DEV_CLEAR_BN, &nec_priv->state); // XXX FIXME - smp_mb__after_atomic(); - *end = 0; - *bytes_read = 0; - - retval = wait_for_read(board); - if (retval < 0) - return retval; - - fmh_gpib_release_rfd_holdoff(board, e_priv); - while (remain > 0) { - transfer_size = (e_priv->dma_buffer_size < remain) ? - e_priv->dma_buffer_size : remain; - retval = fmh_gpib_dma_read(board, buffer, transfer_size, end, &dma_nbytes); - remain -= dma_nbytes; - buffer += dma_nbytes; - *bytes_read += dma_nbytes; - if (*end) - break; - if (retval < 0) - break; - if (need_resched()) - schedule(); - } - - spin_lock_irqsave(&board->spinlock, flags); - if (test_bit(RFD_HOLDOFF_BN, &nec_priv->state) == 0) { - write_byte(nec_priv, AUX_RFD_HOLDOFF_ASAP, AUXMR); - set_bit(RFD_HOLDOFF_BN, &nec_priv->state); - } - spin_unlock_irqrestore(&board->spinlock, flags); - - return retval; -} - -/* - * Read a chunk of data whose length is within the limits of the hardware's - * xfer counter. Called in a loop from fmh_gpib_fifo_read(). - */ -static int fmh_gpib_fifo_read_countable(struct gpib_board *board, u8 *buffer, - size_t length, int *end, size_t *bytes_read) -{ - struct fmh_priv *e_priv = board->private_data; - struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; - int retval = 0; - - *bytes_read = 0; - *end = 0; - if (length == 0) - return 0; - - fifos_write(e_priv, length & fifo_xfer_counter_mask, FIFO_XFER_COUNTER_REG); - fifos_write(e_priv, RX_FIFO_CLEAR, FIFO_CONTROL_STATUS_REG); - nec7210_set_reg_bits(nec_priv, IMR1, HR_DIIE, 0); - nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAI, HR_DMAI); - - while (*bytes_read < length && *end == 0) { - int i; - - fifos_write(e_priv, RX_FIFO_HALF_FULL_INTERRUPT_ENABLE, FIFO_CONTROL_STATUS_REG); - retval = wait_for_rx_fifo_half_full_or_end(board); - if (retval < 0) - goto cleanup; - - for (i = 0; i < fmh_gpib_half_fifo_size(e_priv) && *end == 0; ++i) { - unsigned int data_value; - - data_value = fifos_read(e_priv, FIFO_DATA_REG); - buffer[(*bytes_read)++] = data_value & fifo_data_mask; - if (data_value & FIFO_DATA_EOI_FLAG) - *end = 1; - } - } - -cleanup: - // stop the transfer - nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAI, 0); - fifos_write(e_priv, 0, FIFO_CONTROL_STATUS_REG); - - /* Manually read any dregs out of fifo. */ - while ((fifos_read(e_priv, FIFO_CONTROL_STATUS_REG) & RX_FIFO_EMPTY) == 0) { - unsigned int data_value; - - if ((*bytes_read) >= length) { - dev_err(board->dev, "unexpected extra bytes in rx fifo, discarding! bytes_read=%d length=%d\n", - (int)(*bytes_read), (int)length); - break; - } - data_value = fifos_read(e_priv, FIFO_DATA_REG); - buffer[(*bytes_read)++] = data_value & fifo_data_mask; - if (data_value & FIFO_DATA_EOI_FLAG) - *end = 1; - } - - return retval; -} - -static int fmh_gpib_fifo_read(struct gpib_board *board, u8 *buffer, size_t length, - int *end, size_t *bytes_read) -{ - struct fmh_priv *e_priv = board->private_data; - struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; - size_t remain = length; - size_t transfer_size; - int retval = 0; - size_t nbytes; - unsigned long flags; - - clear_bit(DEV_CLEAR_BN, &nec_priv->state); // XXX FIXME - *end = 0; - *bytes_read = 0; - - /* - * Do a little prep with data in interrupt so that following wait_for_read() - * will wake up if a data byte is received. - */ - nec7210_set_reg_bits(nec_priv, IMR1, HR_DIIE, HR_DIIE); - fmh_gpib_interrupt(0, board); - - retval = wait_for_read(board); - if (retval < 0) - return retval; - - fmh_gpib_release_rfd_holdoff(board, e_priv); - while (remain > 0) { - if (fifo_xfer_counter_mask < remain) { - // round transfer size to a multiple of half fifo size - transfer_size = (fifo_xfer_counter_mask / - fmh_gpib_half_fifo_size(e_priv)) * - fmh_gpib_half_fifo_size(e_priv); - } else { - transfer_size = remain; - } - retval = fmh_gpib_fifo_read_countable(board, buffer, transfer_size, end, &nbytes); - remain -= nbytes; - buffer += nbytes; - *bytes_read += nbytes; - if (*end) - break; - if (retval < 0) - break; - if (need_resched()) - schedule(); - } - - if (*end == 0) { - spin_lock_irqsave(&board->spinlock, flags); - write_byte(nec_priv, AUX_RFD_HOLDOFF_ASAP, AUXMR); - set_bit(RFD_HOLDOFF_BN, &nec_priv->state); - spin_unlock_irqrestore(&board->spinlock, flags); - } - - return retval; -} - -static struct gpib_interface fmh_gpib_unaccel_interface = { - .name = "fmh_gpib_unaccel", - .attach = fmh_gpib_attach_holdoff_all, - .detach = fmh_gpib_detach, - .read = fmh_gpib_read, - .write = fmh_gpib_write, - .command = fmh_gpib_command, - .take_control = fmh_gpib_take_control, - .go_to_standby = fmh_gpib_go_to_standby, - .request_system_control = fmh_gpib_request_system_control, - .interface_clear = fmh_gpib_interface_clear, - .remote_enable = fmh_gpib_remote_enable, - .enable_eos = fmh_gpib_enable_eos, - .disable_eos = fmh_gpib_disable_eos, - .parallel_poll = fmh_gpib_parallel_poll, - .parallel_poll_configure = fmh_gpib_parallel_poll_configure, - .parallel_poll_response = fmh_gpib_parallel_poll_response, - .local_parallel_poll_mode = fmh_gpib_local_parallel_poll_mode, - .line_status = fmh_gpib_line_status, - .update_status = fmh_gpib_update_status, - .primary_address = fmh_gpib_primary_address, - .secondary_address = fmh_gpib_secondary_address, - .serial_poll_response2 = fmh_gpib_serial_poll_response2, - .serial_poll_status = fmh_gpib_serial_poll_status, - .t1_delay = fmh_gpib_t1_delay, - .return_to_local = fmh_gpib_return_to_local, -}; - -static struct gpib_interface fmh_gpib_interface = { - .name = "fmh_gpib", - .attach = fmh_gpib_attach_holdoff_end, - .detach = fmh_gpib_detach, - .read = fmh_gpib_accel_read, - .write = fmh_gpib_accel_write, - .command = fmh_gpib_command, - .take_control = fmh_gpib_take_control, - .go_to_standby = fmh_gpib_go_to_standby, - .request_system_control = fmh_gpib_request_system_control, - .interface_clear = fmh_gpib_interface_clear, - .remote_enable = fmh_gpib_remote_enable, - .enable_eos = fmh_gpib_enable_eos, - .disable_eos = fmh_gpib_disable_eos, - .parallel_poll = fmh_gpib_parallel_poll, - .parallel_poll_configure = fmh_gpib_parallel_poll_configure, - .parallel_poll_response = fmh_gpib_parallel_poll_response, - .local_parallel_poll_mode = fmh_gpib_local_parallel_poll_mode, - .line_status = fmh_gpib_line_status, - .update_status = fmh_gpib_update_status, - .primary_address = fmh_gpib_primary_address, - .secondary_address = fmh_gpib_secondary_address, - .serial_poll_response2 = fmh_gpib_serial_poll_response2, - .serial_poll_status = fmh_gpib_serial_poll_status, - .t1_delay = fmh_gpib_t1_delay, - .return_to_local = fmh_gpib_return_to_local, -}; - -static struct gpib_interface fmh_gpib_pci_interface = { - .name = "fmh_gpib_pci", - .attach = fmh_gpib_pci_attach_holdoff_end, - .detach = fmh_gpib_pci_detach, - .read = fmh_gpib_fifo_read, - .write = fmh_gpib_fifo_write, - .command = fmh_gpib_command, - .take_control = fmh_gpib_take_control, - .go_to_standby = fmh_gpib_go_to_standby, - .request_system_control = fmh_gpib_request_system_control, - .interface_clear = fmh_gpib_interface_clear, - .remote_enable = fmh_gpib_remote_enable, - .enable_eos = fmh_gpib_enable_eos, - .disable_eos = fmh_gpib_disable_eos, - .parallel_poll = fmh_gpib_parallel_poll, - .parallel_poll_configure = fmh_gpib_parallel_poll_configure, - .parallel_poll_response = fmh_gpib_parallel_poll_response, - .local_parallel_poll_mode = fmh_gpib_local_parallel_poll_mode, - .line_status = fmh_gpib_line_status, - .update_status = fmh_gpib_update_status, - .primary_address = fmh_gpib_primary_address, - .secondary_address = fmh_gpib_secondary_address, - .serial_poll_response2 = fmh_gpib_serial_poll_response2, - .serial_poll_status = fmh_gpib_serial_poll_status, - .t1_delay = fmh_gpib_t1_delay, - .return_to_local = fmh_gpib_return_to_local, -}; - -static struct gpib_interface fmh_gpib_pci_unaccel_interface = { - .name = "fmh_gpib_pci_unaccel", - .attach = fmh_gpib_pci_attach_holdoff_all, - .detach = fmh_gpib_pci_detach, - .read = fmh_gpib_read, - .write = fmh_gpib_write, - .command = fmh_gpib_command, - .take_control = fmh_gpib_take_control, - .go_to_standby = fmh_gpib_go_to_standby, - .request_system_control = fmh_gpib_request_system_control, - .interface_clear = fmh_gpib_interface_clear, - .remote_enable = fmh_gpib_remote_enable, - .enable_eos = fmh_gpib_enable_eos, - .disable_eos = fmh_gpib_disable_eos, - .parallel_poll = fmh_gpib_parallel_poll, - .parallel_poll_configure = fmh_gpib_parallel_poll_configure, - .parallel_poll_response = fmh_gpib_parallel_poll_response, - .local_parallel_poll_mode = fmh_gpib_local_parallel_poll_mode, - .line_status = fmh_gpib_line_status, - .update_status = fmh_gpib_update_status, - .primary_address = fmh_gpib_primary_address, - .secondary_address = fmh_gpib_secondary_address, - .serial_poll_response2 = fmh_gpib_serial_poll_response2, - .serial_poll_status = fmh_gpib_serial_poll_status, - .t1_delay = fmh_gpib_t1_delay, - .return_to_local = fmh_gpib_return_to_local, -}; - -irqreturn_t fmh_gpib_internal_interrupt(struct gpib_board *board) -{ - unsigned int status0, status1, status2, ext_status_1, fifo_status; - struct fmh_priv *priv = board->private_data; - struct nec7210_priv *nec_priv = &priv->nec7210_priv; - int retval = IRQ_NONE; - - status0 = read_byte(nec_priv, ISR0_IMR0_REG); - status1 = read_byte(nec_priv, ISR1); - status2 = read_byte(nec_priv, ISR2); - fifo_status = fifos_read(priv, FIFO_CONTROL_STATUS_REG); - - if (status0 & IFC_INTERRUPT_BIT) { - push_gpib_event(board, EVENT_IFC); - retval = IRQ_HANDLED; - } - - if (nec7210_interrupt_have_status(board, nec_priv, status1, status2) == IRQ_HANDLED) - retval = IRQ_HANDLED; - - ext_status_1 = read_byte(nec_priv, EXT_STATUS_1_REG); - - if (ext_status_1 & DATA_IN_STATUS_BIT) - set_bit(READ_READY_BN, &nec_priv->state); - else - clear_bit(READ_READY_BN, &nec_priv->state); - - if (ext_status_1 & DATA_OUT_STATUS_BIT) - set_bit(WRITE_READY_BN, &nec_priv->state); - else - clear_bit(WRITE_READY_BN, &nec_priv->state); - - if (ext_status_1 & COMMAND_OUT_STATUS_BIT) - set_bit(COMMAND_READY_BN, &nec_priv->state); - else - clear_bit(COMMAND_READY_BN, &nec_priv->state); - - if (ext_status_1 & RFD_HOLDOFF_STATUS_BIT) - set_bit(RFD_HOLDOFF_BN, &nec_priv->state); - else - clear_bit(RFD_HOLDOFF_BN, &nec_priv->state); - - if (ext_status_1 & END_STATUS_BIT) { - /* - * only set RECEIVED_END while there is still a data - * byte sitting in the chip, to avoid spuriously - * setting it multiple times after it has been cleared - * during a read. - */ - if (ext_status_1 & DATA_IN_STATUS_BIT) - set_bit(RECEIVED_END_BN, &nec_priv->state); - } else { - clear_bit(RECEIVED_END_BN, &nec_priv->state); - } - - if ((fifo_status & TX_FIFO_HALF_EMPTY_INTERRUPT_IS_ENABLED) && - (fifo_status & TX_FIFO_HALF_EMPTY)) { - /* - * We really only want to clear the - * TX_FIFO_HALF_EMPTY_INTERRUPT_ENABLE bit in the - * FIFO_CONTROL_STATUS_REG. Since we are not being - * careful, this also has a side effect of disabling - * DMA requests and the RX fifo interrupt. That is - * fine though, since they should never be in use at - * the same time as the TX fifo interrupt. - */ - fifos_write(priv, 0x0, FIFO_CONTROL_STATUS_REG); - retval = IRQ_HANDLED; - } - - if ((fifo_status & RX_FIFO_HALF_FULL_INTERRUPT_IS_ENABLED) && - (fifo_status & RX_FIFO_HALF_FULL)) { - /* - * We really only want to clear the - * RX_FIFO_HALF_FULL_INTERRUPT_ENABLE bit in the - * FIFO_CONTROL_STATUS_REG. Since we are not being - * careful, this also has a side effect of disabling - * DMA requests and the TX fifo interrupt. That is - * fine though, since they should never be in use at - * the same time as the RX fifo interrupt. - */ - fifos_write(priv, 0x0, FIFO_CONTROL_STATUS_REG); - retval = IRQ_HANDLED; - } - - if (retval == IRQ_HANDLED) - wake_up_interruptible(&board->wait); - - return retval; -} - -irqreturn_t fmh_gpib_interrupt(int irq, void *arg) -{ - struct gpib_board *board = arg; - unsigned long flags; - irqreturn_t retval; - - spin_lock_irqsave(&board->spinlock, flags); - retval = fmh_gpib_internal_interrupt(board); - spin_unlock_irqrestore(&board->spinlock, flags); - return retval; -} - -static int fmh_gpib_allocate_private(struct gpib_board *board) -{ - struct fmh_priv *priv; - - board->private_data = kmalloc(sizeof(struct fmh_priv), GFP_KERNEL); - if (!board->private_data) - return -ENOMEM; - priv = board->private_data; - memset(priv, 0, sizeof(struct fmh_priv)); - init_nec7210_private(&priv->nec7210_priv); - priv->dma_buffer_size = 0x800; - priv->dma_buffer = kmalloc(priv->dma_buffer_size, GFP_KERNEL); - if (!priv->dma_buffer) - return -ENOMEM; - return 0; -} - -static void fmh_gpib_generic_detach(struct gpib_board *board) -{ - if (board->private_data) { - struct fmh_priv *e_priv = board->private_data; - - kfree(e_priv->dma_buffer); - kfree(board->private_data); - board->private_data = NULL; - } - if (board->dev) - dev_set_drvdata(board->dev, NULL); -} - -// generic part of attach functions -static int fmh_gpib_generic_attach(struct gpib_board *board) -{ - struct fmh_priv *e_priv; - struct nec7210_priv *nec_priv; - int retval; - - board->status = 0; - - retval = fmh_gpib_allocate_private(board); - if (retval < 0) - return retval; - e_priv = board->private_data; - nec_priv = &e_priv->nec7210_priv; - nec_priv->read_byte = gpib_cs_read_byte; - nec_priv->write_byte = gpib_cs_write_byte; - nec_priv->offset = 1; - nec_priv->type = CB7210; - return 0; -} - -static int fmh_gpib_config_dma(struct gpib_board *board, int output) -{ - struct fmh_priv *e_priv = board->private_data; - struct dma_slave_config config; - - config.device_fc = true; - - if (e_priv->dma_burst_length < 1) { - config.src_maxburst = 1; - config.dst_maxburst = 1; - } else { - config.src_maxburst = e_priv->dma_burst_length; - config.dst_maxburst = e_priv->dma_burst_length; - } - - config.src_addr_width = 1; - config.dst_addr_width = 1; - - if (output) { - config.direction = DMA_MEM_TO_DEV; - config.src_addr = 0; - config.dst_addr = e_priv->dma_port_res->start + FIFO_DATA_REG * fifo_reg_offset; - } else { - config.direction = DMA_DEV_TO_MEM; - config.src_addr = e_priv->dma_port_res->start + FIFO_DATA_REG * fifo_reg_offset; - config.dst_addr = 0; - } - return dmaengine_slave_config(e_priv->dma_channel, &config); -} - -static int fmh_gpib_init(struct fmh_priv *e_priv, struct gpib_board *board, int handshake_mode) -{ - struct nec7210_priv *nec_priv = &e_priv->nec7210_priv; - unsigned long flags; - unsigned int fifo_status_bits; - - fifos_write(e_priv, RX_FIFO_CLEAR | TX_FIFO_CLEAR, FIFO_CONTROL_STATUS_REG); - - nec7210_board_reset(nec_priv, board); - write_byte(nec_priv, AUX_LO_SPEED, AUXMR); - nec7210_set_handshake_mode(board, nec_priv, handshake_mode); - - /* Hueristically check if hardware supports fifo half full/empty interrupts */ - fifo_status_bits = fifos_read(e_priv, FIFO_CONTROL_STATUS_REG); - e_priv->supports_fifo_interrupts = (fifo_status_bits & TX_FIFO_EMPTY) && - (fifo_status_bits & TX_FIFO_HALF_EMPTY); - - nec7210_board_online(nec_priv, board); - - write_byte(nec_priv, IFC_INTERRUPT_ENABLE_BIT | ATN_INTERRUPT_ENABLE_BIT, ISR0_IMR0_REG); - - spin_lock_irqsave(&board->spinlock, flags); - write_byte(nec_priv, AUX_RFD_HOLDOFF_ASAP, AUXMR); - set_bit(RFD_HOLDOFF_BN, &nec_priv->state); - spin_unlock_irqrestore(&board->spinlock, flags); - return 0; -} - -/* Match callback for driver_find_device */ -static int fmh_gpib_device_match(struct device *dev, const void *data) -{ - const struct gpib_board_config *config = data; - - if (dev_get_drvdata(dev)) - return 0; - - if (gpib_match_device_path(dev, config->device_path) == 0) - return 0; - - // driver doesn't support selection by serial number - if (config->serial_number) - return 0; - - dev_dbg(dev, "matched: %s\n", of_node_full_name(dev_of_node((dev)))); - return 1; -} - -static int fmh_gpib_attach_impl(struct gpib_board *board, const struct gpib_board_config *config, - unsigned int handshake_mode, int acquire_dma) -{ - struct fmh_priv *e_priv; - struct nec7210_priv *nec_priv; - int retval; - int irq; - struct resource *res; - struct platform_device *pdev; - - board->dev = driver_find_device(&fmh_gpib_platform_driver.driver, - NULL, (const void *)config, &fmh_gpib_device_match); - if (!board->dev) { - dev_err(board->gpib_dev, "No matching fmh_gpib_core device was found, attach failed."); - return -ENODEV; - } - // currently only used to mark the device as already attached - dev_set_drvdata(board->dev, board); - pdev = to_platform_device(board->dev); - - retval = fmh_gpib_generic_attach(board); - if (retval) - return retval; - - e_priv = board->private_data; - nec_priv = &e_priv->nec7210_priv; - - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "gpib_control_status"); - if (!res) { - dev_err(board->dev, "Unable to locate mmio resource\n"); - return -ENODEV; - } - - if (request_mem_region(res->start, - resource_size(res), - pdev->name) == NULL) { - dev_err(board->dev, "cannot claim registers\n"); - return -ENXIO; - } - e_priv->gpib_iomem_res = res; - - nec_priv->mmiobase = ioremap(e_priv->gpib_iomem_res->start, - resource_size(e_priv->gpib_iomem_res)); - if (!nec_priv->mmiobase) { - dev_err(board->dev, "Could not map I/O memory\n"); - return -ENOMEM; - } - dev_dbg(board->dev, "iobase %pr remapped to %p\n", - e_priv->gpib_iomem_res, nec_priv->mmiobase); - - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dma_fifos"); - if (!res) { - dev_err(board->dev, "Unable to locate mmio resource for gpib dma port\n"); - return -ENODEV; - } - if (request_mem_region(res->start, - resource_size(res), - pdev->name) == NULL) { - dev_err(board->dev, "cannot claim registers\n"); - return -ENXIO; - } - e_priv->dma_port_res = res; - e_priv->fifo_base = ioremap(e_priv->dma_port_res->start, - resource_size(e_priv->dma_port_res)); - if (!e_priv->fifo_base) { - dev_err(board->dev, "Could not map I/O memory for fifos\n"); - return -ENOMEM; - } - dev_dbg(board->dev, "dma fifos 0x%lx remapped to %p, length=%ld\n", - (unsigned long)e_priv->dma_port_res->start, e_priv->fifo_base, - (unsigned long)resource_size(e_priv->dma_port_res)); - - irq = platform_get_irq(pdev, 0); - if (irq < 0) - return -EBUSY; - retval = request_irq(irq, fmh_gpib_interrupt, IRQF_SHARED, pdev->name, board); - if (retval) { - dev_err(board->dev, - "cannot register interrupt handler err=%d\n", - retval); - return retval; - } - e_priv->irq = irq; - - if (acquire_dma) { - e_priv->dma_channel = dma_request_slave_channel(board->dev, "rxtx"); - if (!e_priv->dma_channel) { - dev_err(board->dev, "failed to acquire dma channel \"rxtx\".\n"); - return -EIO; - } - } - /* - * in the future we might want to know the half-fifo size - * (dma_burst_length) even when not using dma, so go ahead an - * initialize it unconditionally. - */ - e_priv->dma_burst_length = fifos_read(e_priv, FIFO_MAX_BURST_LENGTH_REG) & - fifo_max_burst_length_mask; - - return fmh_gpib_init(e_priv, board, handshake_mode); -} - -int fmh_gpib_attach_holdoff_all(struct gpib_board *board, const struct gpib_board_config *config) -{ - return fmh_gpib_attach_impl(board, config, HR_HLDA, 0); -} - -int fmh_gpib_attach_holdoff_end(struct gpib_board *board, const struct gpib_board_config *config) -{ - return fmh_gpib_attach_impl(board, config, HR_HLDE, 1); -} - -void fmh_gpib_detach(struct gpib_board *board) -{ - struct fmh_priv *e_priv = board->private_data; - struct nec7210_priv *nec_priv; - - if (e_priv) { - if (e_priv->dma_channel) - dma_release_channel(e_priv->dma_channel); - nec_priv = &e_priv->nec7210_priv; - - if (e_priv->irq) - free_irq(e_priv->irq, board); - if (e_priv->fifo_base) - fifos_write(e_priv, 0, FIFO_CONTROL_STATUS_REG); - if (nec_priv->mmiobase) { - write_byte(nec_priv, 0, ISR0_IMR0_REG); - nec7210_board_reset(nec_priv, board); - } - if (e_priv->fifo_base) - iounmap(e_priv->fifo_base); - if (nec_priv->mmiobase) - iounmap(nec_priv->mmiobase); - if (e_priv->dma_port_res) { - release_mem_region(e_priv->dma_port_res->start, - resource_size(e_priv->dma_port_res)); - } - if (e_priv->gpib_iomem_res) - release_mem_region(e_priv->gpib_iomem_res->start, - resource_size(e_priv->gpib_iomem_res)); - } - fmh_gpib_generic_detach(board); - - if (board->dev) { - put_device(board->dev); - board->dev = NULL; - } -} - -static int fmh_gpib_pci_attach_impl(struct gpib_board *board, - const struct gpib_board_config *config, - unsigned int handshake_mode) -{ - struct fmh_priv *e_priv; - struct nec7210_priv *nec_priv; - int retval; - struct pci_dev *pci_device; - - retval = fmh_gpib_generic_attach(board); - if (retval) - return retval; - - e_priv = board->private_data; - nec_priv = &e_priv->nec7210_priv; - - // find board - pci_device = gpib_pci_get_device(config, BOGUS_PCI_VENDOR_ID_FLUKE, - BOGUS_PCI_DEVICE_ID_FLUKE_BLADERUNNER, NULL); - if (!pci_device) { - dev_err(board->gpib_dev, "No matching fmh_gpib_core pci device was found, attach failed."); - return -ENODEV; - } - board->dev = &pci_device->dev; - - // bladerunner prototype has offset of 4 between gpib control/status registers - nec_priv->offset = 4; - - if (pci_enable_device(pci_device)) { - dev_err(board->dev, "error enabling pci device\n"); - return -EIO; - } - if (pci_request_regions(pci_device, KBUILD_MODNAME)) { - dev_err(board->dev, "pci_request_regions failed\n"); - return -EIO; - } - e_priv->gpib_iomem_res = &pci_device->resource[gpib_control_status_pci_resource_index]; - e_priv->dma_port_res = &pci_device->resource[gpib_fifo_pci_resource_index]; - - nec_priv->mmiobase = ioremap(pci_resource_start(pci_device, - gpib_control_status_pci_resource_index), - pci_resource_len(pci_device, - gpib_control_status_pci_resource_index)); - dev_dbg(board->dev, "base address for gpib control/status registers remapped to 0x%p\n", - nec_priv->mmiobase); - - if (e_priv->dma_port_res->flags & IORESOURCE_MEM) { - e_priv->fifo_base = ioremap(pci_resource_start(pci_device, - gpib_fifo_pci_resource_index), - pci_resource_len(pci_device, - gpib_fifo_pci_resource_index)); - dev_dbg(board->dev, "base address for gpib fifo registers remapped to 0x%p\n", - e_priv->fifo_base); - } else { - e_priv->fifo_base = NULL; - dev_dbg(board->dev, "hardware has no gpib fifo registers.\n"); - } - - if (pci_device->irq) { - retval = request_irq(pci_device->irq, fmh_gpib_interrupt, IRQF_SHARED, - KBUILD_MODNAME, board); - if (retval) { - dev_err(board->dev, "cannot register interrupt handler err=%d\n", retval); - return retval; - } - } - e_priv->irq = pci_device->irq; - - e_priv->dma_burst_length = fifos_read(e_priv, FIFO_MAX_BURST_LENGTH_REG) & - fifo_max_burst_length_mask; - - return fmh_gpib_init(e_priv, board, handshake_mode); -} - -int fmh_gpib_pci_attach_holdoff_all(struct gpib_board *board, - const struct gpib_board_config *config) -{ - return fmh_gpib_pci_attach_impl(board, config, HR_HLDA); -} - -int fmh_gpib_pci_attach_holdoff_end(struct gpib_board *board, - const struct gpib_board_config *config) -{ - int retval; - struct fmh_priv *e_priv; - - retval = fmh_gpib_pci_attach_impl(board, config, HR_HLDE); - e_priv = board->private_data; - if (retval == 0 && e_priv && e_priv->supports_fifo_interrupts == 0) { - dev_err(board->gpib_dev, "your fmh_gpib_core does not appear to support fifo interrupts. Try the fmh_gpib_pci_unaccel board type instead."); - return -EIO; - } - return retval; -} - -void fmh_gpib_pci_detach(struct gpib_board *board) -{ - struct fmh_priv *e_priv = board->private_data; - struct nec7210_priv *nec_priv; - - if (e_priv) { - nec_priv = &e_priv->nec7210_priv; - - if (e_priv->irq) - free_irq(e_priv->irq, board); - if (e_priv->fifo_base) - fifos_write(e_priv, 0, FIFO_CONTROL_STATUS_REG); - if (nec_priv->mmiobase) { - write_byte(nec_priv, 0, ISR0_IMR0_REG); - nec7210_board_reset(nec_priv, board); - } - if (e_priv->fifo_base) - iounmap(e_priv->fifo_base); - if (nec_priv->mmiobase) - iounmap(nec_priv->mmiobase); - if (e_priv->dma_port_res || e_priv->gpib_iomem_res) - pci_release_regions(to_pci_dev(board->dev)); - if (board->dev) - pci_dev_put(to_pci_dev(board->dev)); - } - fmh_gpib_generic_detach(board); -} - -static int fmh_gpib_platform_probe(struct platform_device *pdev) -{ - return 0; -} - -static const struct of_device_id fmh_gpib_of_match[] = { - { .compatible = "fmhess,fmh_gpib_core"}, - { {0} } -}; -MODULE_DEVICE_TABLE(of, fmh_gpib_of_match); - -static struct platform_driver fmh_gpib_platform_driver = { - .driver = { - .name = DRV_NAME, - .of_match_table = fmh_gpib_of_match, - }, - .probe = &fmh_gpib_platform_probe -}; - -static int fmh_gpib_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) -{ - return 0; -} - -static const struct pci_device_id fmh_gpib_pci_match[] = { - { BOGUS_PCI_VENDOR_ID_FLUKE, BOGUS_PCI_DEVICE_ID_FLUKE_BLADERUNNER, 0, 0, 0 }, - { 0 } -}; -MODULE_DEVICE_TABLE(pci, fmh_gpib_pci_match); - -static struct pci_driver fmh_gpib_pci_driver = { - .name = DRV_NAME, - .id_table = fmh_gpib_pci_match, - .probe = &fmh_gpib_pci_probe -}; - -static int __init fmh_gpib_init_module(void) -{ - int result; - - result = platform_driver_register(&fmh_gpib_platform_driver); - if (result) { - pr_err("platform_driver_register failed: error = %d\n", result); - return result; - } - - result = pci_register_driver(&fmh_gpib_pci_driver); - if (result) { - pr_err("pci_register_driver failed: error = %d\n", result); - goto err_pci_driver; - } - - result = gpib_register_driver(&fmh_gpib_unaccel_interface, THIS_MODULE); - if (result) { - pr_err("gpib_register_driver failed: error = %d\n", result); - goto err_unaccel; - } - - result = gpib_register_driver(&fmh_gpib_interface, THIS_MODULE); - if (result) { - pr_err("gpib_register_driver failed: error = %d\n", result); - goto err_interface; - } - - result = gpib_register_driver(&fmh_gpib_pci_unaccel_interface, THIS_MODULE); - if (result) { - pr_err("gpib_register_driver failed: error = %d\n", result); - goto err_pci_unaccel; - } - - result = gpib_register_driver(&fmh_gpib_pci_interface, THIS_MODULE); - if (result) { - pr_err("gpib_register_driver failed: error = %d\n", result); - goto err_pci; - } - - return 0; - -err_pci: - gpib_unregister_driver(&fmh_gpib_pci_unaccel_interface); -err_pci_unaccel: - gpib_unregister_driver(&fmh_gpib_interface); -err_interface: - gpib_unregister_driver(&fmh_gpib_unaccel_interface); -err_unaccel: - pci_unregister_driver(&fmh_gpib_pci_driver); -err_pci_driver: - platform_driver_unregister(&fmh_gpib_platform_driver); - - return result; -} - -static void __exit fmh_gpib_exit_module(void) -{ - gpib_unregister_driver(&fmh_gpib_pci_interface); - gpib_unregister_driver(&fmh_gpib_pci_unaccel_interface); - gpib_unregister_driver(&fmh_gpib_unaccel_interface); - gpib_unregister_driver(&fmh_gpib_interface); - - pci_unregister_driver(&fmh_gpib_pci_driver); - platform_driver_unregister(&fmh_gpib_platform_driver); -} - -module_init(fmh_gpib_init_module); -module_exit(fmh_gpib_exit_module); diff --git a/drivers/staging/gpib/fmh_gpib/fmh_gpib.h b/drivers/staging/gpib/fmh_gpib/fmh_gpib.h deleted file mode 100644 index e7602d7e1401..000000000000 --- a/drivers/staging/gpib/fmh_gpib/fmh_gpib.h +++ /dev/null @@ -1,177 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -/*************************************************************************** - * Author: Frank Mori Hess - * Copyright: (C) 2006, 2010, 2015 Fluke Corporation - * (C) 2017 Frank Mori Hess - ***************************************************************************/ - -#include -#include -#include -#include -#include "nec7210.h" - -static const int fifo_reg_offset = 2; - -static const int gpib_control_status_pci_resource_index; -static const int gpib_fifo_pci_resource_index = 1; - -/* We don't have a real pci vendor/device id, the following will need to be - * patched to match prototype hardware. - */ -#define BOGUS_PCI_VENDOR_ID_FLUKE 0xffff -#define BOGUS_PCI_DEVICE_ID_FLUKE_BLADERUNNER 0x0 - -struct fmh_priv { - struct nec7210_priv nec7210_priv; - struct resource *gpib_iomem_res; - struct resource *write_transfer_counter_res; - struct resource *dma_port_res; - int irq; - struct dma_chan *dma_channel; - u8 *dma_buffer; - int dma_buffer_size; - int dma_burst_length; - void __iomem *fifo_base; - unsigned supports_fifo_interrupts : 1; -}; - -static inline int fmh_gpib_half_fifo_size(struct fmh_priv *priv) -{ - return priv->dma_burst_length; -} - -// registers beyond the nec7210 register set -enum fmh_gpib_regs { - EXT_STATUS_1_REG = 0x9, - STATE1_REG = 0xc, - ISR0_IMR0_REG = 0xe, - BUS_STATUS_REG = 0xf -}; - -/* IMR0 -- Interrupt Mode Register 0 */ -enum imr0_bits { - ATN_INTERRUPT_ENABLE_BIT = 0x4, - IFC_INTERRUPT_ENABLE_BIT = 0x8 -}; - -/* ISR0 -- Interrupt Status Register 0 */ -enum isr0_bits { - ATN_INTERRUPT_BIT = 0x4, - IFC_INTERRUPT_BIT = 0x8 -}; - -enum state1_bits { - SOURCE_HANDSHAKE_SIDS_BITS = 0x0, /* source idle state */ - SOURCE_HANDSHAKE_SGNS_BITS = 0x1, /* source generate state */ - SOURCE_HANDSHAKE_SDYS_BITS = 0x2, /* source delay state */ - SOURCE_HANDSHAKE_STRS_BITS = 0x5, /* source transfer state */ - SOURCE_HANDSHAKE_MASK = 0x7 -}; - -enum fmh_gpib_auxmr_bits { - AUX_I_REG = 0xe0, -}; - -enum aux_reg_i_bits { - LOCAL_PPOLL_MODE_BIT = 0x4 -}; - -enum ext_status_1_bits { - DATA_IN_STATUS_BIT = 0x01, - DATA_OUT_STATUS_BIT = 0x02, - COMMAND_OUT_STATUS_BIT = 0x04, - RFD_HOLDOFF_STATUS_BIT = 0x08, - END_STATUS_BIT = 0x10 -}; - -/* dma fifo reg and bits */ -enum dma_fifo_regs { - FIFO_DATA_REG = 0x0, - FIFO_CONTROL_STATUS_REG = 0x1, - FIFO_XFER_COUNTER_REG = 0x2, - FIFO_MAX_BURST_LENGTH_REG = 0x3 -}; - -enum fifo_data_bits { - FIFO_DATA_EOI_FLAG = 0x100 -}; - -enum fifo_control_bits { - TX_FIFO_DMA_REQUEST_ENABLE = 0x0001, - TX_FIFO_CLEAR = 0x0002, - TX_FIFO_HALF_EMPTY_INTERRUPT_ENABLE = 0x0008, - RX_FIFO_DMA_REQUEST_ENABLE = 0x0100, - RX_FIFO_CLEAR = 0x0200, - RX_FIFO_HALF_FULL_INTERRUPT_ENABLE = 0x0800 -}; - -enum fifo_status_bits { - TX_FIFO_EMPTY = 0x0001, - TX_FIFO_FULL = 0x0002, - TX_FIFO_HALF_EMPTY = 0x0004, - TX_FIFO_HALF_EMPTY_INTERRUPT_IS_ENABLED = 0x0008, - TX_FIFO_DMA_REQUEST_IS_ENABLED = 0x0010, - RX_FIFO_EMPTY = 0x0100, - RX_FIFO_FULL = 0x0200, - RX_FIFO_HALF_FULL = 0x0400, - RX_FIFO_HALF_FULL_INTERRUPT_IS_ENABLED = 0x0800, - RX_FIFO_DMA_REQUEST_IS_ENABLED = 0x1000 -}; - -static const unsigned int fifo_data_mask = 0x00ff; -static const unsigned int fifo_xfer_counter_mask = 0x0fff; -static const unsigned int fifo_max_burst_length_mask = 0x00ff; - -static inline u8 gpib_cs_read_byte(struct nec7210_priv *nec_priv, - unsigned int register_num) -{ - return readb(nec_priv->mmiobase + register_num * nec_priv->offset); -} - -static inline void gpib_cs_write_byte(struct nec7210_priv *nec_priv, u8 data, - unsigned int register_num) -{ - writeb(data, nec_priv->mmiobase + register_num * nec_priv->offset); -} - -static inline uint16_t fifos_read(struct fmh_priv *fmh_priv, int register_num) -{ - if (!fmh_priv->fifo_base) - return 0; - return readw(fmh_priv->fifo_base + register_num * fifo_reg_offset); -} - -static inline void fifos_write(struct fmh_priv *fmh_priv, uint16_t data, int register_num) -{ - if (!fmh_priv->fifo_base) - return; - writew(data, fmh_priv->fifo_base + register_num * fifo_reg_offset); -} - -enum bus_status_bits { - BSR_ATN_BIT = 0x01, - BSR_EOI_BIT = 0x02, - BSR_SRQ_BIT = 0x04, - BSR_IFC_BIT = 0x08, - BSR_REN_BIT = 0x10, - BSR_DAV_BIT = 0x20, - BSR_NRFD_BIT = 0x40, - BSR_NDAC_BIT = 0x80, -}; - -enum fmh_gpib_aux_cmds { - /* AUX_RTL2 is an auxiliary command which causes the cb7210 to assert - * (and keep asserted) the local rtl message. This is used in conjunction - * with the normal nec7210 AUX_RTL command, which - * pulses the rtl message, having the effect of clearing rtl if it was left - * asserted by AUX_RTL2. - */ - AUX_RTL2 = 0x0d, - AUX_RFD_HOLDOFF_ASAP = 0x15, - AUX_REQT = 0x18, - AUX_REQF = 0x19, - AUX_LO_SPEED = 0x40, - AUX_HI_SPEED = 0x41 -}; diff --git a/drivers/staging/gpib/gpio/Makefile b/drivers/staging/gpib/gpio/Makefile deleted file mode 100644 index 00ea52abdda7..000000000000 --- a/drivers/staging/gpib/gpio/Makefile +++ /dev/null @@ -1,4 +0,0 @@ - -obj-$(CONFIG_GPIB_GPIO) += gpib_bitbang.o - - diff --git a/drivers/staging/gpib/gpio/gpib_bitbang.c b/drivers/staging/gpib/gpio/gpib_bitbang.c deleted file mode 100644 index 374cd61355e9..000000000000 --- a/drivers/staging/gpib/gpio/gpib_bitbang.c +++ /dev/null @@ -1,1469 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 - -/************************************************************************* - * This code has been developed at the Institute of Sensor and Actuator * - * Systems (Technical University of Vienna, Austria) to enable the GPIO * - * lines (e.g. of a raspberry pi) to function as a GPIO master device * - * * - * authors : Thomas Klima * - * Marcello Carla' * - * Dave Penkler * - * * - * copyright : (C) 2016 Thomas Klima * - * * - *************************************************************************/ - -/* - * limitations: - * works only on RPi - * cannot function as non-CIC system controller with SN7516x because - * SN75161B cannot simultaneously make ATN input with IFC and REN as - * outputs. - * not implemented: - * parallel poll - * return2local - * device support (non master operation) - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -#define dev_fmt pr_fmt -#define NAME KBUILD_MODNAME - -#define ENABLE_IRQ(IRQ, TYPE) irq_set_irq_type(IRQ, TYPE) -#define DISABLE_IRQ(IRQ) irq_set_irq_type(IRQ, IRQ_TYPE_NONE) - -/* - * Debug print levels: - * 0 = load/unload info and errors that make the driver fail; - * 1 = + warnings for unforeseen events that may break the current - * operation and lead to a timeout, but do not affect the - * driver integrity (mainly unexpected interrupts); - * 2 = + trace of function calls; - * 3 = + trace of protocol codes; - * 4 = + trace of interrupt operation. - */ -#define dbg_printk(level, frm, ...) \ - do { if (debug >= (level)) \ - dev_dbg(board->gpib_dev, frm, ## __VA_ARGS__); } \ - while (0) - -#define LINVAL gpiod_get_value(DAV), \ - gpiod_get_value(NRFD), \ - gpiod_get_value(NDAC), \ - gpiod_get_value(SRQ) -#define LINFMT "DAV: %d NRFD:%d NDAC: %d SRQ: %d" - -#include "gpibP.h" -#include "gpib_state_machines.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static int sn7516x_used = 1, sn7516x; -module_param(sn7516x_used, int, 0660); - -#define PINMAP_0 "elektronomikon" -#define PINMAP_1 "gpib4pi-1.1" -#define PINMAP_2 "yoga" -static char *pin_map = PINMAP_0; -module_param(pin_map, charp, 0660); -MODULE_PARM_DESC(pin_map, " valid values: " PINMAP_0 " " PINMAP_1 " " PINMAP_2); - -/********************************************** - * Signal pairing and pin wiring between the * - * Raspberry-Pi connector and the GPIB bus * - * * - * signal pin wiring * - * GPIB Pi-gpio GPIB -> RPi * - ********************************************** - */ -enum lines_t { - D01_pin_nr = 20, /* 1 -> 38 */ - D02_pin_nr = 26, /* 2 -> 37 */ - D03_pin_nr = 16, /* 3 -> 36 */ - D04_pin_nr = 19, /* 4 -> 35 */ - D05_pin_nr = 13, /* 13 -> 33 */ - D06_pin_nr = 12, /* 14 -> 32 */ - D07_pin_nr = 6, /* 15 -> 31 */ - D08_pin_nr = 5, /* 16 -> 29 */ - EOI_pin_nr = 9, /* 5 -> 21 */ - DAV_pin_nr = 10, /* 6 -> 19 */ - NRFD_pin_nr = 24, /* 7 -> 18 */ - NDAC_pin_nr = 23, /* 8 -> 16 */ - IFC_pin_nr = 22, /* 9 -> 15 */ - SRQ_pin_nr = 11, /* 10 -> 23 */ - _ATN_pin_nr = 25, /* 11 -> 22 */ - REN_pin_nr = 27, /* 17 -> 13 */ -/* - * GROUND PINS - * 12,18,19,20,21,22,23,24 => 14,20,25,30,34,39 - */ - -/* - * These lines are used to control the external - * SN75160/161 driver chips when used. - * When not used there is reduced fan out; - * currently tested with up to 4 devices. - */ - -/* Pi GPIO RPI 75161B 75160B Description */ - PE_pin_nr = 7, /* 26 -> nc 11 Pullup Enable */ - DC_pin_nr = 8, /* 24 -> 12 nc Direction control */ - TE_pin_nr = 18, /* 12 -> 2 1 Talk Enable */ - ACT_LED_pin_nr = 4, /* 7 -> LED */ - -/* YOGA adapter uses different pinout to ease layout */ - YOGA_D03_pin_nr = 13, - YOGA_D04_pin_nr = 12, - YOGA_D05_pin_nr = 21, - YOGA_D06_pin_nr = 19, -}; - -/* - * GPIO descriptors and pins - WARNING: STRICTLY KEEP ITEMS ORDER - */ - -#define GPIB_PINS 16 -#define SN7516X_PINS 4 -#define NUM_PINS (GPIB_PINS + SN7516X_PINS) - -#define ACT_LED_ON do { \ - if (ACT_LED) \ - gpiod_direction_output(ACT_LED, 1); \ - } while (0) -#define ACT_LED_OFF do { \ - if (ACT_LED) \ - gpiod_direction_output(ACT_LED, 0); \ - } while (0) - -static struct gpio_desc *all_descriptors[GPIB_PINS + SN7516X_PINS]; - -#define D01 all_descriptors[0] -#define D02 all_descriptors[1] -#define D03 all_descriptors[2] -#define D04 all_descriptors[3] -#define D05 all_descriptors[4] -#define D06 all_descriptors[5] -#define D07 all_descriptors[6] -#define D08 all_descriptors[7] - -#define EOI all_descriptors[8] -#define NRFD all_descriptors[9] -#define IFC all_descriptors[10] -#define _ATN all_descriptors[11] -#define REN all_descriptors[12] -#define DAV all_descriptors[13] -#define NDAC all_descriptors[14] -#define SRQ all_descriptors[15] - -#define PE all_descriptors[16] -#define DC all_descriptors[17] -#define TE all_descriptors[18] -#define ACT_LED all_descriptors[19] - -/* YOGA adapter uses a global enable for the buffer chips, re-using the TE pin */ -#define YOGA_ENABLE TE - -static int gpios_vector[] = { - D01_pin_nr, - D02_pin_nr, - D03_pin_nr, - D04_pin_nr, - D05_pin_nr, - D06_pin_nr, - D07_pin_nr, - D08_pin_nr, - - EOI_pin_nr, - NRFD_pin_nr, - IFC_pin_nr, - _ATN_pin_nr, - REN_pin_nr, - DAV_pin_nr, - NDAC_pin_nr, - SRQ_pin_nr, - - PE_pin_nr, - DC_pin_nr, - TE_pin_nr, - ACT_LED_pin_nr -}; - -/* Lookup table for general GPIOs */ - -static struct gpiod_lookup_table gpib_gpio_table_1 = { - // for bcm2835/6 - .dev_id = "", // device id of board device - .table = { - GPIO_LOOKUP_IDX("GPIO_GCLK", U16_MAX, NULL, 4, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("GPIO5", U16_MAX, NULL, 5, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("GPIO6", U16_MAX, NULL, 6, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("SPI_CE1_N", U16_MAX, NULL, 7, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("SPI_CE0_N", U16_MAX, NULL, 8, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("SPI_MISO", U16_MAX, NULL, 9, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("SPI_MOSI", U16_MAX, NULL, 10, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("SPI_SCLK", U16_MAX, NULL, 11, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("GPIO12", U16_MAX, NULL, 12, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("GPIO13", U16_MAX, NULL, 13, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("GPIO16", U16_MAX, NULL, 16, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("GPIO17", U16_MAX, NULL, 17, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("GPIO18", U16_MAX, NULL, 18, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("GPIO19", U16_MAX, NULL, 19, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("GPIO20", U16_MAX, NULL, 20, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("GPIO21", U16_MAX, NULL, 21, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("GPIO22", U16_MAX, NULL, 22, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("GPIO23", U16_MAX, NULL, 23, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("GPIO24", U16_MAX, NULL, 24, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("GPIO25", U16_MAX, NULL, 25, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("GPIO26", U16_MAX, NULL, 26, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("GPIO27", U16_MAX, NULL, 27, GPIO_ACTIVE_HIGH), - { } - }, -}; - -static struct gpiod_lookup_table gpib_gpio_table_0 = { - .dev_id = "", // device id of board device - .table = { - // for bcm27xx based pis (b b+ 2b 3b 3b+ 4 5) - GPIO_LOOKUP_IDX("GPIO4", U16_MAX, NULL, 4, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("GPIO5", U16_MAX, NULL, 5, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("GPIO6", U16_MAX, NULL, 6, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("GPIO7", U16_MAX, NULL, 7, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("GPIO8", U16_MAX, NULL, 8, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("GPIO9", U16_MAX, NULL, 9, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("GPIO10", U16_MAX, NULL, 10, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("GPIO11", U16_MAX, NULL, 11, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("GPIO12", U16_MAX, NULL, 12, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("GPIO13", U16_MAX, NULL, 13, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("GPIO16", U16_MAX, NULL, 16, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("GPIO17", U16_MAX, NULL, 17, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("GPIO18", U16_MAX, NULL, 18, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("GPIO19", U16_MAX, NULL, 19, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("GPIO20", U16_MAX, NULL, 20, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("GPIO21", U16_MAX, NULL, 21, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("GPIO22", U16_MAX, NULL, 22, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("GPIO23", U16_MAX, NULL, 23, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("GPIO24", U16_MAX, NULL, 24, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("GPIO25", U16_MAX, NULL, 25, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("GPIO26", U16_MAX, NULL, 26, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("GPIO27", U16_MAX, NULL, 27, GPIO_ACTIVE_HIGH), - { } - }, -}; - -static struct gpiod_lookup_table *lookup_tables[] = { - &gpib_gpio_table_0, - &gpib_gpio_table_1, - NULL -}; - -/* struct which defines private_data for gpio driver */ - -struct bb_priv { - int irq_NRFD; - int irq_NDAC; - int irq_DAV; - int irq_SRQ; - int dav_mode; /* dav interrupt mode 0/1 -> edge/levels */ - int nrfd_mode; /* nrfd interrupt mode 0/1 -> edge/levels */ - int ndac_mode; /* nrfd interrupt mode 0/1 -> edge/levels */ - int dav_tx; /* keep trace of DAV status while sending */ - int dav_rx; /* keep trace of DAV status while receiving */ - u8 eos; /* eos character */ - short eos_flags; /* eos mode */ - short eos_check; /* eos check required in current operation ... */ - short eos_check_8; /* ... with byte comparison */ - short eos_mask_7; /* ... with 7 bit masked character */ - short int end; - int request; - int count; - int direction; - int t1_delay; - u8 *rbuf; - u8 *wbuf; - int end_flag; - int r_busy; /* 0==idle 1==busy */ - int w_busy; - int write_done; - int cmd; /* 1 = cmd write in progress */ - size_t w_cnt; - size_t length; - u8 *w_buf; - spinlock_t rw_lock; /* protect mods to rw_lock */ - int phase; - int ndac_idle; - int ndac_seq; - int nrfd_idle; - int nrfd_seq; - int dav_seq; - long all_irqs; - int dav_idle; - - enum talker_function_state talker_state; - enum listener_function_state listener_state; -}; - -static inline long usec_diff(struct timespec64 *a, struct timespec64 *b); -static void bb_buffer_print(struct gpib_board *board, unsigned char *buffer, size_t length, - int cmd, int eoi); -static void set_data_lines(u8 byte); -static u8 get_data_lines(void); -static void set_data_lines_input(void); -static void set_data_lines_output(void); -static inline int check_for_eos(struct bb_priv *priv, u8 byte); -static void set_atn(struct gpib_board *board, int atn_asserted); - -static inline void SET_DIR_WRITE(struct bb_priv *priv); -static inline void SET_DIR_READ(struct bb_priv *priv); - -#define DIR_READ 0 -#define DIR_WRITE 1 - -MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("GPIB helper functions for bitbanging I/O"); - -/**** global variables ****/ -static int debug; -module_param(debug, int, 0644); - -static char printable(char x) -{ - if (x < 32 || x > 126) - return ' '; - return x; -} - -/*************************************************************************** - * * - * READ * - * * - ***************************************************************************/ - -static int bb_read(struct gpib_board *board, u8 *buffer, size_t length, - int *end, size_t *bytes_read) -{ - struct bb_priv *priv = board->private_data; - unsigned long flags; - int retval = 0; - - ACT_LED_ON; - SET_DIR_READ(priv); - - dbg_printk(2, "board: %p lock %d length: %zu\n", - board, mutex_is_locked(&board->user_mutex), length); - - priv->end = 0; - priv->count = 0; - priv->rbuf = buffer; - if (length == 0) - goto read_end; - priv->request = length; - priv->eos_check = (priv->eos_flags & REOS) == 0; /* do eos check */ - priv->eos_check_8 = priv->eos_flags & BIN; /* over 8 bits */ - priv->eos_mask_7 = priv->eos & 0x7f; /* with this 7 bit eos */ - - dbg_printk(3, ".........." LINFMT "\n", LINVAL); - - spin_lock_irqsave(&priv->rw_lock, flags); - priv->dav_mode = 1; - priv->dav_rx = 1; - ENABLE_IRQ(priv->irq_DAV, IRQ_TYPE_LEVEL_LOW); - priv->end_flag = 0; - gpiod_set_value(NRFD, 1); // ready for data - priv->r_busy = 1; - priv->phase = 100; - spin_unlock_irqrestore(&priv->rw_lock, flags); - - /* wait for the interrupt routines finish their work */ - - retval = wait_event_interruptible(board->wait, - (priv->end_flag || board->status & TIMO)); - - dbg_printk(3, "awake from wait queue: %d\n", retval); - - if (retval == 0 && board->status & TIMO) { - retval = -ETIMEDOUT; - dbg_printk(1, "timeout\n"); - } else if (retval) { - retval = -ERESTARTSYS; - } - - DISABLE_IRQ(priv->irq_DAV); - spin_lock_irqsave(&priv->rw_lock, flags); - gpiod_set_value(NRFD, 0); // DIR_READ line state - priv->r_busy = 0; - spin_unlock_irqrestore(&priv->rw_lock, flags); - -read_end: - ACT_LED_OFF; - *bytes_read = priv->count; - *end = priv->end; - priv->r_busy = 0; - dbg_printk(2, "return: %d eoi|eos: %d count: %d\n\n", retval, priv->end, priv->count); - return retval; -} - -/*************************************************************************** - * * - * READ interrupt routine (DAV line) * - * * - ***************************************************************************/ - -static irqreturn_t bb_DAV_interrupt(int irq, void *arg) -{ - struct gpib_board *board = arg; - struct bb_priv *priv = board->private_data; - int val; - unsigned long flags; - - spin_lock_irqsave(&priv->rw_lock, flags); - - priv->all_irqs++; - - if (priv->dav_mode) { - ENABLE_IRQ(priv->irq_DAV, IRQ_TYPE_EDGE_BOTH); - priv->dav_mode = 0; - } - - if (priv->r_busy == 0) { - dbg_printk(1, "interrupt while idle after %d at %d\n", - priv->count, priv->phase); - priv->dav_idle++; - priv->phase = 200; - goto dav_exit; /* idle */ - } - - val = gpiod_get_value(DAV); - if (val == priv->dav_rx) { - dbg_printk(1, "out of order DAV interrupt %d/%d after %zu/%zu at %d cmd %d " - LINFMT ".\n", val, priv->dav_rx, priv->w_cnt, priv->length, - priv->phase, priv->cmd, LINVAL); - priv->dav_seq++; - } - priv->dav_rx = val; - - dbg_printk(3, "> irq: %d DAV: %d st: %4lx dir: %d busy: %d:%d\n", - irq, val, board->status, priv->direction, priv->r_busy, priv->w_busy); - - if (val == 0) { - gpiod_set_value(NRFD, 0); // not ready for data - priv->rbuf[priv->count++] = get_data_lines(); - priv->end = !gpiod_get_value(EOI); - gpiod_set_value(NDAC, 1); // data accepted - priv->end |= check_for_eos(priv, priv->rbuf[priv->count - 1]); - priv->end_flag = ((priv->count >= priv->request) || priv->end); - priv->phase = 210; - } else { - gpiod_set_value(NDAC, 0); // data not accepted - if (priv->end_flag) { - priv->r_busy = 0; - wake_up_interruptible(&board->wait); - priv->phase = 220; - } else { - gpiod_set_value(NRFD, 1); // ready for data - priv->phase = 230; - } - } - -dav_exit: - spin_unlock_irqrestore(&priv->rw_lock, flags); - dbg_printk(3, "< irq: %d count %d\n", irq, priv->count); - return IRQ_HANDLED; -} - -/*************************************************************************** - * * - * WRITE * - * * - ***************************************************************************/ - -static int bb_write(struct gpib_board *board, u8 *buffer, size_t length, - int send_eoi, size_t *bytes_written) -{ - unsigned long flags; - int retval = 0; - - struct bb_priv *priv = board->private_data; - - ACT_LED_ON; - - priv->w_cnt = 0; - priv->w_buf = buffer; - dbg_printk(2, "board %p lock %d length: %zu\n", - board, mutex_is_locked(&board->user_mutex), length); - - if (debug > 1) - bb_buffer_print(board, buffer, length, priv->cmd, send_eoi); - priv->count = 0; - priv->phase = 300; - - if (length == 0) - goto write_end; - priv->end = send_eoi; - priv->length = length; - - SET_DIR_WRITE(priv); - - dbg_printk(2, "Enabling interrupts - NRFD: %d NDAC: %d\n", - gpiod_get_value(NRFD), gpiod_get_value(NDAC)); - - if (gpiod_get_value(NRFD) && gpiod_get_value(NDAC)) { /* check for listener */ - retval = -ENOTCONN; - goto write_end; - } - - spin_lock_irqsave(&priv->rw_lock, flags); - priv->w_busy = 1; /* make the interrupt routines active */ - priv->write_done = 0; - priv->nrfd_mode = 1; - priv->ndac_mode = 1; - priv->dav_tx = 1; - ENABLE_IRQ(priv->irq_NDAC, IRQ_TYPE_LEVEL_HIGH); - ENABLE_IRQ(priv->irq_NRFD, IRQ_TYPE_LEVEL_HIGH); - spin_unlock_irqrestore(&priv->rw_lock, flags); - - /* wait for the interrupt routines finish their work */ - - retval = wait_event_interruptible(board->wait, - priv->write_done || (board->status & TIMO)); - - dbg_printk(3, "awake from wait queue: %d\n", retval); - - if (retval == 0) { - if (board->status & TIMO) { - retval = -ETIMEDOUT; - dbg_printk(1, "timeout after %zu/%zu at %d " LINFMT " eoi: %d\n", - priv->w_cnt, length, priv->phase, LINVAL, send_eoi); - } else { - retval = priv->w_cnt; - } - } else { - retval = -ERESTARTSYS; - } - - DISABLE_IRQ(priv->irq_NRFD); - DISABLE_IRQ(priv->irq_NDAC); - - spin_lock_irqsave(&priv->rw_lock, flags); - priv->w_busy = 0; - gpiod_set_value(DAV, 1); // DIR_WRITE line state - gpiod_set_value(EOI, 1); // De-assert EOI (in case) - spin_unlock_irqrestore(&priv->rw_lock, flags); - -write_end: - *bytes_written = priv->w_cnt; - ACT_LED_OFF; - dbg_printk(2, "sent %zu bytes\r\n\r\n", *bytes_written); - priv->phase = 310; - return retval; -} - -/*************************************************************************** - * * - * WRITE interrupt routine (NRFD line) * - * * - ***************************************************************************/ - -static irqreturn_t bb_NRFD_interrupt(int irq, void *arg) -{ - struct gpib_board *board = arg; - struct bb_priv *priv = board->private_data; - unsigned long flags; - int nrfd; - - spin_lock_irqsave(&priv->rw_lock, flags); - - nrfd = gpiod_get_value(NRFD); - priv->all_irqs++; - - dbg_printk(3, "> irq: %d NRFD: %d NDAC: %d st: %4lx dir: %d busy: %d:%d\n", - irq, nrfd, gpiod_get_value(NDAC), board->status, priv->direction, - priv->w_busy, priv->r_busy); - - if (priv->nrfd_mode) { - ENABLE_IRQ(priv->irq_NRFD, IRQ_TYPE_EDGE_RISING); - priv->nrfd_mode = 0; - } - - if (priv->w_busy == 0) { - dbg_printk(1, "interrupt while idle after %zu/%zu at %d\n", - priv->w_cnt, priv->length, priv->phase); - priv->nrfd_idle++; - goto nrfd_exit; /* idle */ - } - if (nrfd == 0) { - dbg_printk(1, "out of order interrupt after %zu/%zu at %d cmd %d " LINFMT ".\n", - priv->w_cnt, priv->length, priv->phase, priv->cmd, LINVAL); - priv->phase = 400; - priv->nrfd_seq++; - goto nrfd_exit; - } - if (!priv->dav_tx) { - dbg_printk(1, "DAV low after %zu/%zu cmd %d " LINFMT ". No action.\n", - priv->w_cnt, priv->length, priv->cmd, LINVAL); - priv->dav_seq++; - goto nrfd_exit; - } - - if (priv->w_cnt >= priv->length) { // test for missed NDAC end of transfer - dev_err(board->gpib_dev, "Unexpected NRFD exit\n"); - priv->write_done = 1; - priv->w_busy = 0; - wake_up_interruptible(&board->wait); - goto nrfd_exit; - } - - dbg_printk(3, "sending %zu\n", priv->w_cnt); - - set_data_lines(priv->w_buf[priv->w_cnt++]); // put the data on the lines - - if (priv->w_cnt == priv->length && priv->end) { - dbg_printk(3, "Asserting EOI\n"); - gpiod_set_value(EOI, 0); // Assert EOI - } - - gpiod_set_value(DAV, 0); // Data available - priv->dav_tx = 0; - priv->phase = 410; - -nrfd_exit: - spin_unlock_irqrestore(&priv->rw_lock, flags); - - return IRQ_HANDLED; -} - -/*************************************************************************** - * * - * WRITE interrupt routine (NDAC line) * - * * - ***************************************************************************/ - -static irqreturn_t bb_NDAC_interrupt(int irq, void *arg) -{ - struct gpib_board *board = arg; - struct bb_priv *priv = board->private_data; - unsigned long flags; - int ndac; - - spin_lock_irqsave(&priv->rw_lock, flags); - - ndac = gpiod_get_value(NDAC); - priv->all_irqs++; - dbg_printk(3, "> irq: %d NRFD: %d NDAC: %d st: %4lx dir: %d busy: %d:%d\n", - irq, gpiod_get_value(NRFD), ndac, board->status, priv->direction, - priv->w_busy, priv->r_busy); - - if (priv->ndac_mode) { - ENABLE_IRQ(priv->irq_NDAC, IRQ_TYPE_EDGE_RISING); - priv->ndac_mode = 0; - } - - if (priv->w_busy == 0) { - dbg_printk(1, "interrupt while idle.\n"); - priv->ndac_idle++; - goto ndac_exit; - } - if (ndac == 0) { - dbg_printk(1, "out of order interrupt at %zu:%d.\n", priv->w_cnt, priv->phase); - priv->phase = 500; - priv->ndac_seq++; - goto ndac_exit; - } - if (priv->dav_tx) { - dbg_printk(1, "DAV high after %zu/%zu cmd %d " LINFMT ". No action.\n", - priv->w_cnt, priv->length, priv->cmd, LINVAL); - priv->dav_seq++; - goto ndac_exit; - } - - dbg_printk(3, "accepted %zu\n", priv->w_cnt - 1); - - gpiod_set_value(DAV, 1); // Data not available - priv->dav_tx = 1; - priv->phase = 510; - - if (priv->w_cnt >= priv->length) { // test for end of transfer - priv->write_done = 1; - priv->w_busy = 0; - wake_up_interruptible(&board->wait); - } - -ndac_exit: - spin_unlock_irqrestore(&priv->rw_lock, flags); - return IRQ_HANDLED; -} - -/*************************************************************************** - * * - * interrupt routine for SRQ line * - * * - ***************************************************************************/ - -static irqreturn_t bb_SRQ_interrupt(int irq, void *arg) -{ - struct gpib_board *board = arg; - - int val = gpiod_get_value(SRQ); - - dbg_printk(3, "> %d st: %4lx\n", val, board->status); - - if (!val) - set_bit(SRQI_NUM, &board->status); /* set_bit() is atomic */ - - wake_up_interruptible(&board->wait); - - return IRQ_HANDLED; -} - -static int bb_command(struct gpib_board *board, u8 *buffer, - size_t length, size_t *bytes_written) -{ - int ret; - struct bb_priv *priv = board->private_data; - int i; - - dbg_printk(2, "%p %p\n", buffer, board->buffer); - - /* the _ATN line has already been asserted by bb_take_control() */ - - priv->cmd = 1; - - ret = bb_write(board, buffer, length, 0, bytes_written); // no eoi - - for (i = 0; i < length; i++) { - if (buffer[i] == UNT) { - priv->talker_state = talker_idle; - } else { - if (buffer[i] == UNL) { - priv->listener_state = listener_idle; - } else { - if (buffer[i] == (MTA(board->pad))) { - priv->talker_state = talker_addressed; - priv->listener_state = listener_idle; - } else if (buffer[i] == (MLA(board->pad))) { - priv->listener_state = listener_addressed; - priv->talker_state = talker_idle; - } - } - } - } - - /* the _ATN line will be released by bb_go_to_stby */ - - priv->cmd = 0; - - return ret; -} - -/*************************************************************************** - * * - * Buffer print with decode for debug/trace * - * * - ***************************************************************************/ - -static char *cmd_string[32] = { - "", // 0x00 - "GTL", // 0x01 - "", // 0x02 - "", // 0x03 - "SDC", // 0x04 - "PPC", // 0x05 - "", // 0x06 - "", // 0x07 - "GET", // 0x08 - "TCT", // 0x09 - "", // 0x0a - "", // 0x0b - "", // 0x0c - "", // 0x0d - "", // 0x0e - "", // 0x0f - "", // 0x10 - "LLO", // 0x11 - "", // 0x12 - "", // 0x13 - "DCL", // 0x14 - "PPU", // 0x15 - "", // 0x16 - "", // 0x17 - "SPE", // 0x18 - "SPD", // 0x19 - "", // 0x1a - "", // 0x1b - "", // 0x1c - "", // 0x1d - "", // 0x1e - "CFE" // 0x1f -}; - -static void bb_buffer_print(struct gpib_board *board, unsigned char *buffer, size_t length, - int cmd, int eoi) -{ - int i; - - if (cmd) { - dbg_printk(2, "\n", length); - for (i = 0; i < length; i++) { - if (buffer[i] < 0x20) { - dbg_printk(3, "0x%x=%s\n", buffer[i], cmd_string[buffer[i]]); - } else if (buffer[i] == 0x3f) { - dbg_printk(3, "0x%x=%s\n", buffer[i], "UNL"); - } else if (buffer[i] == 0x5f) { - dbg_printk(3, "0x%x=%s\n", buffer[i], "UNT"); - } else if (buffer[i] < 0x60) { - dbg_printk(3, "0x%x=%s%d\n", buffer[i], - (buffer[i] & 0x40) ? "TLK" : "LSN", buffer[i] & 0x1F); - } else { - dbg_printk(3, "0x%x\n", buffer[i]); - } - } - } else { - dbg_printk(2, "\n", length, (eoi) ? "w.EOI" : " "); - for (i = 0; i < length; i++) - dbg_printk(2, "%3d 0x%x->%c\n", i, buffer[i], printable(buffer[i])); - } -} - -/*************************************************************************** - * * - * STATUS Management * - * * - ***************************************************************************/ -static void set_atn(struct gpib_board *board, int atn_asserted) -{ - struct bb_priv *priv = board->private_data; - - if (priv->listener_state != listener_idle && - priv->talker_state != talker_idle) { - dev_err(board->gpib_dev, "listener/talker state machine conflict\n"); - } - if (atn_asserted) { - if (priv->listener_state == listener_active) - priv->listener_state = listener_addressed; - if (priv->talker_state == talker_active) - priv->talker_state = talker_addressed; - SET_DIR_WRITE(priv); // need to be able to read bus NRFD/NDAC - } else { - if (priv->listener_state == listener_addressed) { - priv->listener_state = listener_active; - SET_DIR_READ(priv); // make sure holdoff is active when we unassert ATN - } - if (priv->talker_state == talker_addressed) - priv->talker_state = talker_active; - } - gpiod_direction_output(_ATN, !atn_asserted); -} - -static int bb_take_control(struct gpib_board *board, int synchronous) -{ - dbg_printk(2, "%d\n", synchronous); - set_atn(board, 1); - return 0; -} - -static int bb_go_to_standby(struct gpib_board *board) -{ - dbg_printk(2, "\n"); - set_atn(board, 0); - return 0; -} - -static int bb_request_system_control(struct gpib_board *board, int request_control) -{ - struct bb_priv *priv = board->private_data; - - dbg_printk(2, "%d\n", request_control); - if (!request_control) - return -EINVAL; - - gpiod_direction_output(REN, 1); /* user space must enable REN if needed */ - gpiod_direction_output(IFC, 1); /* user space must toggle IFC if needed */ - if (sn7516x) - gpiod_direction_output(DC, 0); /* enable ATN as output on SN75161/2 */ - - gpiod_direction_input(SRQ); - - ENABLE_IRQ(priv->irq_SRQ, IRQ_TYPE_EDGE_FALLING); - - return 0; -} - -static void bb_interface_clear(struct gpib_board *board, int assert) -{ - struct bb_priv *priv = board->private_data; - - dbg_printk(2, "%d\n", assert); - if (assert) { - gpiod_direction_output(IFC, 0); - priv->talker_state = talker_idle; - priv->listener_state = listener_idle; - set_bit(CIC_NUM, &board->status); - } else { - gpiod_direction_output(IFC, 1); - } -} - -static void bb_remote_enable(struct gpib_board *board, int enable) -{ - dbg_printk(2, "%d\n", enable); - if (enable) { - set_bit(REM_NUM, &board->status); - gpiod_direction_output(REN, 0); - } else { - clear_bit(REM_NUM, &board->status); - gpiod_direction_output(REN, 1); - } -} - -static int bb_enable_eos(struct gpib_board *board, u8 eos_byte, int compare_8_bits) -{ - struct bb_priv *priv = board->private_data; - - dbg_printk(2, "%s\n", "EOS_en"); - priv->eos = eos_byte; - priv->eos_flags = REOS; - if (compare_8_bits) - priv->eos_flags |= BIN; - - return 0; -} - -static void bb_disable_eos(struct gpib_board *board) -{ - struct bb_priv *priv = board->private_data; - - dbg_printk(2, "\n"); - priv->eos_flags &= ~REOS; -} - -static unsigned int bb_update_status(struct gpib_board *board, unsigned int clear_mask) -{ - struct bb_priv *priv = board->private_data; - - board->status &= ~clear_mask; - - if (gpiod_get_value(SRQ)) /* SRQ asserted low */ - clear_bit(SRQI_NUM, &board->status); - else - set_bit(SRQI_NUM, &board->status); - if (gpiod_get_value(_ATN)) /* ATN asserted low */ - clear_bit(ATN_NUM, &board->status); - else - set_bit(ATN_NUM, &board->status); - if (priv->talker_state == talker_active || - priv->talker_state == talker_addressed) - set_bit(TACS_NUM, &board->status); - else - clear_bit(TACS_NUM, &board->status); - - if (priv->listener_state == listener_active || - priv->listener_state == listener_addressed) - set_bit(LACS_NUM, &board->status); - else - clear_bit(LACS_NUM, &board->status); - - dbg_printk(2, "0x%lx mask 0x%x\n", board->status, clear_mask); - - return board->status; -} - -static int bb_primary_address(struct gpib_board *board, unsigned int address) -{ - dbg_printk(2, "%d\n", address); - board->pad = address; - return 0; -} - -static int bb_secondary_address(struct gpib_board *board, unsigned int address, int enable) -{ - dbg_printk(2, "%d %d\n", address, enable); - if (enable) - board->sad = address; - return 0; -} - -static int bb_parallel_poll(struct gpib_board *board, u8 *result) -{ - return -ENOENT; -} - -static void bb_parallel_poll_configure(struct gpib_board *board, u8 config) -{ -} - -static void bb_parallel_poll_response(struct gpib_board *board, int ist) -{ -} - -static void bb_serial_poll_response(struct gpib_board *board, u8 status) -{ -} - -static u8 bb_serial_poll_status(struct gpib_board *board) -{ - return 0; // -ENOENT; -} - -static int bb_t1_delay(struct gpib_board *board, unsigned int nano_sec) -{ - struct bb_priv *priv = board->private_data; - - if (nano_sec <= 350) - priv->t1_delay = 350; - else if (nano_sec <= 1100) - priv->t1_delay = 1100; - else - priv->t1_delay = 2000; - - dbg_printk(2, "t1 delay set to %d nanosec\n", priv->t1_delay); - - return priv->t1_delay; -} - -static void bb_return_to_local(struct gpib_board *board) -{ -} - -static int bb_line_status(const struct gpib_board *board) -{ - int line_status = VALID_ALL; - - if (gpiod_get_value(REN) == 0) - line_status |= BUS_REN; - if (gpiod_get_value(IFC) == 0) - line_status |= BUS_IFC; - if (gpiod_get_value(NDAC) == 0) - line_status |= BUS_NDAC; - if (gpiod_get_value(NRFD) == 0) - line_status |= BUS_NRFD; - if (gpiod_get_value(DAV) == 0) - line_status |= BUS_DAV; - if (gpiod_get_value(EOI) == 0) - line_status |= BUS_EOI; - if (gpiod_get_value(_ATN) == 0) - line_status |= BUS_ATN; - if (gpiod_get_value(SRQ) == 0) - line_status |= BUS_SRQ; - - dbg_printk(2, "status lines: %4x\n", line_status); - - return line_status; -} - -/*************************************************************************** - * * - * Module Management * - * * - ***************************************************************************/ - -static int allocate_private(struct gpib_board *board) -{ - board->private_data = kzalloc(sizeof(struct bb_priv), GFP_KERNEL); - if (!board->private_data) - return -1; - return 0; -} - -static void free_private(struct gpib_board *board) -{ - kfree(board->private_data); - board->private_data = NULL; -} - -static int bb_get_irq(struct gpib_board *board, char *name, - struct gpio_desc *gpio, int *irq, - irq_handler_t handler, irq_handler_t thread_fn, unsigned long flags) -{ - if (!gpio) - return -1; - gpiod_direction_input(gpio); - *irq = gpiod_to_irq(gpio); - dbg_printk(2, "IRQ %s: %d\n", name, *irq); - if (*irq < 0) { - dev_err(board->gpib_dev, "can't get IRQ for %s\n", name); - return -1; - } - if (request_threaded_irq(*irq, handler, thread_fn, flags, name, board)) { - dev_err(board->gpib_dev, "can't request IRQ for %s %d\n", name, *irq); - *irq = 0; - return -1; - } - DISABLE_IRQ(*irq); - return 0; -} - -static void bb_free_irq(struct gpib_board *board, int *irq, char *name) -{ - if (*irq) { - free_irq(*irq, board); - dbg_printk(2, "IRQ %d(%s) freed\n", *irq, name); - *irq = 0; - } -} - -static void release_gpios(void) -{ - int j; - - for (j = 0 ; j < NUM_PINS ; j++) { - if (all_descriptors[j]) { - gpiod_put(all_descriptors[j]); - all_descriptors[j] = NULL; - } - } -} - -static int allocate_gpios(struct gpib_board *board) -{ - int j; - int table_index = 0; - char name[256]; - struct gpio_desc *desc; - struct gpiod_lookup_table *lookup_table; - - if (!board->gpib_dev) { - pr_err("NULL gpib dev for board\n"); - return -ENOENT; - } - - lookup_table = lookup_tables[table_index]; - lookup_table->dev_id = dev_name(board->gpib_dev); - gpiod_add_lookup_table(lookup_table); - dbg_printk(1, "Allocating gpios using table index %d\n", table_index); - - for (j = 0 ; j < NUM_PINS ; j++) { - if (gpios_vector[j] < 0) - continue; - /* name not really used in gpiod_get_index() */ - sprintf(name, "GPIO%d", gpios_vector[j]); -try_again: - dbg_printk(1, "Allocating gpio %s pin no %d\n", name, gpios_vector[j]); - desc = gpiod_get_index(board->gpib_dev, name, gpios_vector[j], GPIOD_IN); - - if (IS_ERR(desc)) { - gpiod_remove_lookup_table(lookup_table); - table_index++; - lookup_table = lookup_tables[table_index]; - if (!lookup_table) { - dev_err(board->gpib_dev, "Unable to obtain gpio descriptor for pin %d error %ld\n", - gpios_vector[j], PTR_ERR(desc)); - goto alloc_gpios_fail; - } - dbg_printk(1, "Allocation failed, now using table_index %d\n", table_index); - lookup_table->dev_id = dev_name(board->gpib_dev); - gpiod_add_lookup_table(lookup_table); - goto try_again; - } - all_descriptors[j] = desc; - } - - gpiod_remove_lookup_table(lookup_table); - - return 0; - -alloc_gpios_fail: - release_gpios(); - return -1; -} - -static void bb_detach(struct gpib_board *board) -{ - struct bb_priv *priv = board->private_data; - - dbg_printk(2, "Enter with data %p\n", board->private_data); - if (!board->private_data) - return; - - bb_free_irq(board, &priv->irq_DAV, NAME "_DAV"); - bb_free_irq(board, &priv->irq_NRFD, NAME "_NRFD"); - bb_free_irq(board, &priv->irq_NDAC, NAME "_NDAC"); - bb_free_irq(board, &priv->irq_SRQ, NAME "_SRQ"); - - if (strcmp(PINMAP_2, pin_map) == 0) { /* YOGA */ - gpiod_set_value(YOGA_ENABLE, 0); - } - - release_gpios(); - - dbg_printk(2, "detached board: %d\n", board->minor); - dbg_printk(0, "NRFD: idle %d, seq %d, NDAC: idle %d, seq %d DAV: idle %d seq: %d all: %ld", - priv->nrfd_idle, priv->nrfd_seq, - priv->ndac_idle, priv->ndac_seq, - priv->dav_idle, priv->dav_seq, priv->all_irqs); - - free_private(board); -} - -static int bb_attach(struct gpib_board *board, const struct gpib_board_config *config) -{ - struct bb_priv *priv; - int retval = 0; - - dbg_printk(2, "%s\n", "Enter ..."); - - board->status = 0; - - if (allocate_private(board)) - return -ENOMEM; - priv = board->private_data; - priv->direction = -1; - priv->t1_delay = 2000; - priv->listener_state = listener_idle; - priv->talker_state = talker_idle; - - sn7516x = sn7516x_used; - if (strcmp(PINMAP_0, pin_map) == 0) { - if (!sn7516x) { - gpios_vector[&(PE) - &all_descriptors[0]] = -1; - gpios_vector[&(DC) - &all_descriptors[0]] = -1; - gpios_vector[&(TE) - &all_descriptors[0]] = -1; - } - } else if (strcmp(PINMAP_1, pin_map) == 0) { - if (!sn7516x) { - gpios_vector[&(PE) - &all_descriptors[0]] = -1; - gpios_vector[&(DC) - &all_descriptors[0]] = -1; - gpios_vector[&(TE) - &all_descriptors[0]] = -1; - } - gpios_vector[&(REN) - &all_descriptors[0]] = 0; /* 27 -> 0 REN on GPIB pin 0 */ - } else if (strcmp(PINMAP_2, pin_map) == 0) { /* YOGA */ - sn7516x = 0; - gpios_vector[&(D03) - &all_descriptors[0]] = YOGA_D03_pin_nr; - gpios_vector[&(D04) - &all_descriptors[0]] = YOGA_D04_pin_nr; - gpios_vector[&(D05) - &all_descriptors[0]] = YOGA_D05_pin_nr; - gpios_vector[&(D06) - &all_descriptors[0]] = YOGA_D06_pin_nr; - gpios_vector[&(PE) - &all_descriptors[0]] = -1; - gpios_vector[&(DC) - &all_descriptors[0]] = -1; - } else { - dev_err(board->gpib_dev, "Unrecognized pin map %s\n", pin_map); - goto bb_attach_fail; - } - dbg_printk(0, "Using pin map \"%s\" %s\n", pin_map, (sn7516x) ? - " with SN7516x driver support" : ""); - - if (allocate_gpios(board)) - goto bb_attach_fail; - -/* - * Configure SN7516X control lines. - * drive ATN, IFC and REN as outputs only when master - * i.e. system controller. In this mode can only be the CIC - * When not master then enable device mode ATN, IFC & REN as inputs - */ - if (sn7516x) { - gpiod_direction_output(DC, 0); - gpiod_direction_output(TE, 1); - gpiod_direction_output(PE, 1); - } -/* Set main control lines to a known state */ - gpiod_direction_output(IFC, 1); - gpiod_direction_output(REN, 1); - gpiod_direction_output(_ATN, 1); - - if (strcmp(PINMAP_2, pin_map) == 0) { /* YOGA: enable level shifters */ - gpiod_direction_output(YOGA_ENABLE, 1); - } - - spin_lock_init(&priv->rw_lock); - - /* request DAV interrupt for read */ - if (bb_get_irq(board, NAME "_DAV", DAV, &priv->irq_DAV, bb_DAV_interrupt, NULL, - IRQF_TRIGGER_NONE)) - goto bb_attach_fail_r; - - /* request NRFD interrupt for write */ - if (bb_get_irq(board, NAME "_NRFD", NRFD, &priv->irq_NRFD, bb_NRFD_interrupt, NULL, - IRQF_TRIGGER_NONE)) - goto bb_attach_fail_r; - - /* request NDAC interrupt for write */ - if (bb_get_irq(board, NAME "_NDAC", NDAC, &priv->irq_NDAC, bb_NDAC_interrupt, NULL, - IRQF_TRIGGER_NONE)) - goto bb_attach_fail_r; - - /* request SRQ interrupt for Service Request */ - if (bb_get_irq(board, NAME "_SRQ", SRQ, &priv->irq_SRQ, bb_SRQ_interrupt, NULL, - IRQF_TRIGGER_NONE)) - goto bb_attach_fail_r; - - dbg_printk(0, "attached board %d\n", board->minor); - goto bb_attach_out; - -bb_attach_fail_r: - release_gpios(); -bb_attach_fail: - retval = -1; -bb_attach_out: - return retval; -} - -static struct gpib_interface bb_interface = { - .name = NAME, - .attach = bb_attach, - .detach = bb_detach, - .read = bb_read, - .write = bb_write, - .command = bb_command, - .take_control = bb_take_control, - .go_to_standby = bb_go_to_standby, - .request_system_control = bb_request_system_control, - .interface_clear = bb_interface_clear, - .remote_enable = bb_remote_enable, - .enable_eos = bb_enable_eos, - .disable_eos = bb_disable_eos, - .parallel_poll = bb_parallel_poll, - .parallel_poll_configure = bb_parallel_poll_configure, - .parallel_poll_response = bb_parallel_poll_response, - .line_status = bb_line_status, - .update_status = bb_update_status, - .primary_address = bb_primary_address, - .secondary_address = bb_secondary_address, - .serial_poll_response = bb_serial_poll_response, - .serial_poll_status = bb_serial_poll_status, - .t1_delay = bb_t1_delay, - .return_to_local = bb_return_to_local, -}; - -static int __init bb_init_module(void) -{ - int result = gpib_register_driver(&bb_interface, THIS_MODULE); - - if (result) { - pr_err("gpib_register_driver failed: error = %d\n", result); - return result; - } - - return 0; -} - -static void __exit bb_exit_module(void) -{ - gpib_unregister_driver(&bb_interface); -} - -module_init(bb_init_module); -module_exit(bb_exit_module); - -/*************************************************************************** - * * - * UTILITY Functions * - * * - ***************************************************************************/ -inline long usec_diff(struct timespec64 *a, struct timespec64 *b) -{ - return ((a->tv_sec - b->tv_sec) * 1000000 + - (a->tv_nsec - b->tv_nsec) / 1000); -} - -static inline int check_for_eos(struct bb_priv *priv, u8 byte) -{ - if (priv->eos_check) - return 0; - - if (priv->eos_check_8) { - if (priv->eos == byte) - return 1; - } else { - if (priv->eos_mask_7 == (byte & 0x7f)) - return 1; - } - return 0; -} - -static void set_data_lines_output(void) -{ - gpiod_direction_output(D01, 1); - gpiod_direction_output(D02, 1); - gpiod_direction_output(D03, 1); - gpiod_direction_output(D04, 1); - gpiod_direction_output(D05, 1); - gpiod_direction_output(D06, 1); - gpiod_direction_output(D07, 1); - gpiod_direction_output(D08, 1); -} - -static void set_data_lines(u8 byte) -{ - gpiod_set_value(D01, !(byte & 0x01)); - gpiod_set_value(D02, !(byte & 0x02)); - gpiod_set_value(D03, !(byte & 0x04)); - gpiod_set_value(D04, !(byte & 0x08)); - gpiod_set_value(D05, !(byte & 0x10)); - gpiod_set_value(D06, !(byte & 0x20)); - gpiod_set_value(D07, !(byte & 0x40)); - gpiod_set_value(D08, !(byte & 0x80)); -} - -static u8 get_data_lines(void) -{ - u8 ret; - - ret = gpiod_get_value(D01); - ret |= gpiod_get_value(D02) << 1; - ret |= gpiod_get_value(D03) << 2; - ret |= gpiod_get_value(D04) << 3; - ret |= gpiod_get_value(D05) << 4; - ret |= gpiod_get_value(D06) << 5; - ret |= gpiod_get_value(D07) << 6; - ret |= gpiod_get_value(D08) << 7; - return ~ret; -} - -static void set_data_lines_input(void) -{ - gpiod_direction_input(D01); - gpiod_direction_input(D02); - gpiod_direction_input(D03); - gpiod_direction_input(D04); - gpiod_direction_input(D05); - gpiod_direction_input(D06); - gpiod_direction_input(D07); - gpiod_direction_input(D08); -} - -static inline void SET_DIR_WRITE(struct bb_priv *priv) -{ - if (priv->direction == DIR_WRITE) - return; - - gpiod_direction_input(NRFD); - gpiod_direction_input(NDAC); - set_data_lines_output(); - gpiod_direction_output(DAV, 1); - gpiod_direction_output(EOI, 1); - - if (sn7516x) { - gpiod_set_value(PE, 1); /* set data lines to transmit on sn75160b */ - gpiod_set_value(TE, 1); /* set NDAC and NRFD to receive and DAV to transmit */ - } - - priv->direction = DIR_WRITE; -} - -static inline void SET_DIR_READ(struct bb_priv *priv) -{ - if (priv->direction == DIR_READ) - return; - - gpiod_direction_input(DAV); - gpiod_direction_input(EOI); - - set_data_lines_input(); - - if (sn7516x) { - gpiod_set_value(PE, 0); /* set data lines to receive on sn75160b */ - gpiod_set_value(TE, 0); /* set NDAC and NRFD to transmit and DAV to receive */ - } - - gpiod_direction_output(NRFD, 0); /* hold off the talker */ - gpiod_direction_output(NDAC, 0); /* data not accepted */ - - priv->direction = DIR_READ; -} diff --git a/drivers/staging/gpib/hp_82335/Makefile b/drivers/staging/gpib/hp_82335/Makefile deleted file mode 100644 index 305ce44ee48a..000000000000 --- a/drivers/staging/gpib/hp_82335/Makefile +++ /dev/null @@ -1,4 +0,0 @@ - -obj-$(CONFIG_GPIB_HP82335) += hp82335.o - - diff --git a/drivers/staging/gpib/hp_82335/hp82335.c b/drivers/staging/gpib/hp_82335/hp82335.c deleted file mode 100644 index d0e47ef77c87..000000000000 --- a/drivers/staging/gpib/hp_82335/hp82335.c +++ /dev/null @@ -1,371 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 - -/*************************************************************************** - * copyright : (C) 2002 by Frank Mori Hess * - ***************************************************************************/ - -/* - * should enable ATN interrupts (and update board->status on occurrence), - * implement recovery from bus errors (if necessary) - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -#define dev_fmt pr_fmt -#define DRV_NAME KBUILD_MODNAME - -#include "hp82335.h" -#include -#include -#include -#include -#include -#include -#include - -MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("GPIB driver for HP 82335 interface cards"); - -static int hp82335_attach(struct gpib_board *board, const struct gpib_board_config *config); -static void hp82335_detach(struct gpib_board *board); -static irqreturn_t hp82335_interrupt(int irq, void *arg); - -// wrappers for interface functions -static int hp82335_read(struct gpib_board *board, u8 *buffer, size_t length, - int *end, size_t *bytes_read) -{ - struct hp82335_priv *priv = board->private_data; - - return tms9914_read(board, &priv->tms9914_priv, buffer, length, end, bytes_read); -} - -static int hp82335_write(struct gpib_board *board, u8 *buffer, size_t length, int send_eoi, - size_t *bytes_written) -{ - struct hp82335_priv *priv = board->private_data; - - return tms9914_write(board, &priv->tms9914_priv, buffer, length, send_eoi, bytes_written); -} - -static int hp82335_command(struct gpib_board *board, u8 *buffer, size_t length, - size_t *bytes_written) -{ - struct hp82335_priv *priv = board->private_data; - - return tms9914_command(board, &priv->tms9914_priv, buffer, length, bytes_written); -} - -static int hp82335_take_control(struct gpib_board *board, int synchronous) -{ - struct hp82335_priv *priv = board->private_data; - - return tms9914_take_control(board, &priv->tms9914_priv, synchronous); -} - -static int hp82335_go_to_standby(struct gpib_board *board) -{ - struct hp82335_priv *priv = board->private_data; - - return tms9914_go_to_standby(board, &priv->tms9914_priv); -} - -static int hp82335_request_system_control(struct gpib_board *board, int request_control) -{ - struct hp82335_priv *priv = board->private_data; - - return tms9914_request_system_control(board, &priv->tms9914_priv, request_control); -} - -static void hp82335_interface_clear(struct gpib_board *board, int assert) -{ - struct hp82335_priv *priv = board->private_data; - - tms9914_interface_clear(board, &priv->tms9914_priv, assert); -} - -static void hp82335_remote_enable(struct gpib_board *board, int enable) -{ - struct hp82335_priv *priv = board->private_data; - - tms9914_remote_enable(board, &priv->tms9914_priv, enable); -} - -static int hp82335_enable_eos(struct gpib_board *board, u8 eos_byte, int compare_8_bits) -{ - struct hp82335_priv *priv = board->private_data; - - return tms9914_enable_eos(board, &priv->tms9914_priv, eos_byte, compare_8_bits); -} - -static void hp82335_disable_eos(struct gpib_board *board) -{ - struct hp82335_priv *priv = board->private_data; - - tms9914_disable_eos(board, &priv->tms9914_priv); -} - -static unsigned int hp82335_update_status(struct gpib_board *board, unsigned int clear_mask) -{ - struct hp82335_priv *priv = board->private_data; - - return tms9914_update_status(board, &priv->tms9914_priv, clear_mask); -} - -static int hp82335_primary_address(struct gpib_board *board, unsigned int address) -{ - struct hp82335_priv *priv = board->private_data; - - return tms9914_primary_address(board, &priv->tms9914_priv, address); -} - -static int hp82335_secondary_address(struct gpib_board *board, unsigned int address, int enable) -{ - struct hp82335_priv *priv = board->private_data; - - return tms9914_secondary_address(board, &priv->tms9914_priv, address, enable); -} - -static int hp82335_parallel_poll(struct gpib_board *board, u8 *result) -{ - struct hp82335_priv *priv = board->private_data; - - return tms9914_parallel_poll(board, &priv->tms9914_priv, result); -} - -static void hp82335_parallel_poll_configure(struct gpib_board *board, u8 config) -{ - struct hp82335_priv *priv = board->private_data; - - tms9914_parallel_poll_configure(board, &priv->tms9914_priv, config); -} - -static void hp82335_parallel_poll_response(struct gpib_board *board, int ist) -{ - struct hp82335_priv *priv = board->private_data; - - tms9914_parallel_poll_response(board, &priv->tms9914_priv, ist); -} - -static void hp82335_serial_poll_response(struct gpib_board *board, u8 status) -{ - struct hp82335_priv *priv = board->private_data; - - tms9914_serial_poll_response(board, &priv->tms9914_priv, status); -} - -static u8 hp82335_serial_poll_status(struct gpib_board *board) -{ - struct hp82335_priv *priv = board->private_data; - - return tms9914_serial_poll_status(board, &priv->tms9914_priv); -} - -static int hp82335_line_status(const struct gpib_board *board) -{ - struct hp82335_priv *priv = board->private_data; - - return tms9914_line_status(board, &priv->tms9914_priv); -} - -static int hp82335_t1_delay(struct gpib_board *board, unsigned int nano_sec) -{ - struct hp82335_priv *priv = board->private_data; - - return tms9914_t1_delay(board, &priv->tms9914_priv, nano_sec); -} - -static void hp82335_return_to_local(struct gpib_board *board) -{ - struct hp82335_priv *priv = board->private_data; - - tms9914_return_to_local(board, &priv->tms9914_priv); -} - -static struct gpib_interface hp82335_interface = { - .name = "hp82335", - .attach = hp82335_attach, - .detach = hp82335_detach, - .read = hp82335_read, - .write = hp82335_write, - .command = hp82335_command, - .request_system_control = hp82335_request_system_control, - .take_control = hp82335_take_control, - .go_to_standby = hp82335_go_to_standby, - .interface_clear = hp82335_interface_clear, - .remote_enable = hp82335_remote_enable, - .enable_eos = hp82335_enable_eos, - .disable_eos = hp82335_disable_eos, - .parallel_poll = hp82335_parallel_poll, - .parallel_poll_configure = hp82335_parallel_poll_configure, - .parallel_poll_response = hp82335_parallel_poll_response, - .local_parallel_poll_mode = NULL, // XXX - .line_status = hp82335_line_status, - .update_status = hp82335_update_status, - .primary_address = hp82335_primary_address, - .secondary_address = hp82335_secondary_address, - .serial_poll_response = hp82335_serial_poll_response, - .serial_poll_status = hp82335_serial_poll_status, - .t1_delay = hp82335_t1_delay, - .return_to_local = hp82335_return_to_local, -}; - -static int hp82335_allocate_private(struct gpib_board *board) -{ - board->private_data = kzalloc(sizeof(struct hp82335_priv), GFP_KERNEL); - if (!board->private_data) - return -1; - return 0; -} - -static void hp82335_free_private(struct gpib_board *board) -{ - kfree(board->private_data); - board->private_data = NULL; -} - -static inline unsigned int tms9914_to_hp82335_offset(unsigned int register_num) -{ - return 0x1ff8 + register_num; -} - -static u8 hp82335_read_byte(struct tms9914_priv *priv, unsigned int register_num) -{ - return tms9914_iomem_read_byte(priv, tms9914_to_hp82335_offset(register_num)); -} - -static void hp82335_write_byte(struct tms9914_priv *priv, u8 data, unsigned int register_num) -{ - tms9914_iomem_write_byte(priv, data, tms9914_to_hp82335_offset(register_num)); -} - -static void hp82335_clear_interrupt(struct hp82335_priv *hp_priv) -{ - struct tms9914_priv *tms_priv = &hp_priv->tms9914_priv; - - writeb(0, tms_priv->mmiobase + HPREG_INTR_CLEAR); -} - -static int hp82335_attach(struct gpib_board *board, const struct gpib_board_config *config) -{ - struct hp82335_priv *hp_priv; - struct tms9914_priv *tms_priv; - int retval; - const unsigned long upper_iomem_base = config->ibbase + hp82335_rom_size; - - board->status = 0; - - if (hp82335_allocate_private(board)) - return -ENOMEM; - hp_priv = board->private_data; - tms_priv = &hp_priv->tms9914_priv; - tms_priv->read_byte = hp82335_read_byte; - tms_priv->write_byte = hp82335_write_byte; - tms_priv->offset = 1; - - switch (config->ibbase) { - case 0xc4000: - case 0xc8000: - case 0xcc000: - case 0xd0000: - case 0xd4000: - case 0xd8000: - case 0xdc000: - case 0xe0000: - case 0xe4000: - case 0xe8000: - case 0xec000: - case 0xf0000: - case 0xf4000: - case 0xf8000: - case 0xfc000: - break; - default: - dev_err(board->gpib_dev, "invalid base io address 0x%x\n", config->ibbase); - return -EINVAL; - } - if (!request_mem_region(upper_iomem_base, hp82335_upper_iomem_size, "hp82335")) { - dev_err(board->gpib_dev, "failed to allocate io memory region 0x%lx-0x%lx\n", - upper_iomem_base, upper_iomem_base + hp82335_upper_iomem_size - 1); - return -EBUSY; - } - hp_priv->raw_iobase = upper_iomem_base; - tms_priv->mmiobase = ioremap(upper_iomem_base, hp82335_upper_iomem_size); - - retval = request_irq(config->ibirq, hp82335_interrupt, 0, DRV_NAME, board); - if (retval) { - dev_err(board->gpib_dev, "can't request IRQ %d\n", config->ibirq); - return retval; - } - hp_priv->irq = config->ibirq; - - tms9914_board_reset(tms_priv); - - hp82335_clear_interrupt(hp_priv); - - writeb(INTR_ENABLE, tms_priv->mmiobase + HPREG_CCR); - - tms9914_online(board, tms_priv); - - return 0; -} - -static void hp82335_detach(struct gpib_board *board) -{ - struct hp82335_priv *hp_priv = board->private_data; - struct tms9914_priv *tms_priv; - - if (hp_priv) { - tms_priv = &hp_priv->tms9914_priv; - if (hp_priv->irq) - free_irq(hp_priv->irq, board); - if (tms_priv->mmiobase) { - writeb(0, tms_priv->mmiobase + HPREG_CCR); - tms9914_board_reset(tms_priv); - iounmap(tms_priv->mmiobase); - } - if (hp_priv->raw_iobase) - release_mem_region(hp_priv->raw_iobase, hp82335_upper_iomem_size); - } - hp82335_free_private(board); -} - -static int __init hp82335_init_module(void) -{ - int result = gpib_register_driver(&hp82335_interface, THIS_MODULE); - - if (result) { - pr_err("gpib_register_driver failed: error = %d\n", result); - return result; - } - - return 0; -} - -static void __exit hp82335_exit_module(void) -{ - gpib_unregister_driver(&hp82335_interface); -} - -module_init(hp82335_init_module); -module_exit(hp82335_exit_module); - -/* - * GPIB interrupt service routines - */ - -static irqreturn_t hp82335_interrupt(int irq, void *arg) -{ - int status1, status2; - struct gpib_board *board = arg; - struct hp82335_priv *priv = board->private_data; - unsigned long flags; - irqreturn_t retval; - - spin_lock_irqsave(&board->spinlock, flags); - status1 = read_byte(&priv->tms9914_priv, ISR0); - status2 = read_byte(&priv->tms9914_priv, ISR1); - hp82335_clear_interrupt(priv); - retval = tms9914_interrupt_have_status(board, &priv->tms9914_priv, status1, status2); - spin_unlock_irqrestore(&board->spinlock, flags); - return retval; -} - diff --git a/drivers/staging/gpib/hp_82335/hp82335.h b/drivers/staging/gpib/hp_82335/hp82335.h deleted file mode 100644 index 0c252a712ec9..000000000000 --- a/drivers/staging/gpib/hp_82335/hp82335.h +++ /dev/null @@ -1,52 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -/*************************************************************************** - * copyright : (C) 2002 by Frank Mori Hess * - ***************************************************************************/ - -#ifndef _HP82335_H -#define _HP82335_H - -#include "tms9914.h" -#include "gpibP.h" - -// struct which defines private_data for board -struct hp82335_priv { - struct tms9914_priv tms9914_priv; - unsigned int irq; - unsigned long raw_iobase; -}; - -// size of io memory region used -static const int hp82335_rom_size = 0x2000; -static const int hp82335_upper_iomem_size = 0x2000; - -// hp82335 register offsets -enum hp_read_regs { - HPREG_CSR = 0x17f8, - HPREG_STATUS = 0x1ffc, -}; - -enum hp_write_regs { - HPREG_INTR_CLEAR = 0x17f7, - HPREG_CCR = HPREG_CSR, -}; - -enum ccr_bits { - DMA_ENABLE = (1 << 0), /* DMA enable */ - DMA_CHAN_SELECT = (1 << 1), /* DMA channel select O=3,1=2 */ - INTR_ENABLE = (1 << 2), /* interrupt enable */ - SYS_DISABLE = (1 << 3), /* system controller disable */ -}; - -enum csr_bits { - SWITCH6 = (1 << 0), /* switch 6 position */ - SWITCH5 = (1 << 1), /* switch 5 position */ - SYS_CONTROLLER = (1 << 2), /* system controller bit */ - DMA_ENABLE_STATUS = (1 << 4), /* DMA enabled */ - DMA_CHAN_STATUS = (1 << 5), /* DMA channel 0=3,1=2 */ - INTR_ENABLE_STATUS = (1 << 6), /* Interrupt enable */ - INTR_PENDING = (1 << 7), /* Interrupt Pending */ -}; - -#endif // _HP82335_H diff --git a/drivers/staging/gpib/hp_82341/Makefile b/drivers/staging/gpib/hp_82341/Makefile deleted file mode 100644 index 21367310a17e..000000000000 --- a/drivers/staging/gpib/hp_82341/Makefile +++ /dev/null @@ -1,2 +0,0 @@ - -obj-$(CONFIG_GPIB_HP82341) += hp_82341.o diff --git a/drivers/staging/gpib/hp_82341/hp_82341.c b/drivers/staging/gpib/hp_82341/hp_82341.c deleted file mode 100644 index 1a2ad0560e14..000000000000 --- a/drivers/staging/gpib/hp_82341/hp_82341.c +++ /dev/null @@ -1,907 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 - -/*************************************************************************** - * Driver for hp 82341a/b/c/d boards. * - * Might be worth merging with Agilent 82350b driver. * - * copyright : (C) 2002, 2005 by Frank Mori Hess * - ***************************************************************************/ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -#define dev_fmt pr_fmt -#define DRV_NAME KBUILD_MODNAME - -#include "hp_82341.h" -#include -#include -#include -#include -#include -#include -#include - -MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("GPIB driver for hp 82341a/b/c/d boards"); - -static unsigned short read_and_clear_event_status(struct gpib_board *board); -static void set_transfer_counter(struct hp_82341_priv *hp_priv, int count); -static int read_transfer_counter(struct hp_82341_priv *hp_priv); -static int hp_82341_write(struct gpib_board *board, u8 *buffer, size_t length, int send_eoi, - size_t *bytes_written); -static irqreturn_t hp_82341_interrupt(int irq, void *arg); - -static int hp_82341_accel_read(struct gpib_board *board, u8 *buffer, size_t length, int *end, - size_t *bytes_read) -{ - struct hp_82341_priv *hp_priv = board->private_data; - struct tms9914_priv *tms_priv = &hp_priv->tms9914_priv; - int retval = 0; - unsigned short event_status; - int i; - int num_fifo_bytes; - // hardware doesn't support checking for end-of-string character when using fifo - if (tms_priv->eos_flags & REOS) - return tms9914_read(board, tms_priv, buffer, length, end, bytes_read); - - clear_bit(DEV_CLEAR_BN, &tms_priv->state); - - read_and_clear_event_status(board); - *end = 0; - *bytes_read = 0; - if (length == 0) - return 0; - // disable fifo for the moment - outb(DIRECTION_GPIB_TO_HOST_BIT, hp_priv->iobase[3] + BUFFER_CONTROL_REG); - /* - * Handle corner case of board not in holdoff and one byte has slipped in already. - * Also, board sometimes has problems (spurious 1 byte reads) when read fifo is - * started up with board in TACS under certain data holdoff conditions. - * Doing a 1 byte tms9914-style read avoids these problems. - */ - if (/*tms_priv->holdoff_active == 0 && */length > 1) { - size_t num_bytes; - - retval = tms9914_read(board, tms_priv, buffer, 1, end, &num_bytes); - *bytes_read += num_bytes; - if (retval < 0) - dev_err(board->gpib_dev, "tms9914_read failed retval=%i\n", retval); - if (retval < 0 || *end) - return retval; - ++buffer; - --length; - } - tms9914_set_holdoff_mode(tms_priv, TMS9914_HOLDOFF_EOI); - tms9914_release_holdoff(tms_priv); - outb(0x00, hp_priv->iobase[3] + BUFFER_FLUSH_REG); - i = 0; - num_fifo_bytes = length - 1; - while (i < num_fifo_bytes && *end == 0) { - int block_size; - int j; - int count; - - block_size = min(num_fifo_bytes - i, hp_82341_fifo_size); - set_transfer_counter(hp_priv, block_size); - outb(ENABLE_TI_BUFFER_BIT | DIRECTION_GPIB_TO_HOST_BIT, hp_priv->iobase[3] + - BUFFER_CONTROL_REG); - if (inb(hp_priv->iobase[0] + STREAM_STATUS_REG) & HALTED_STATUS_BIT) - outb(RESTART_STREAM_BIT, hp_priv->iobase[0] + STREAM_STATUS_REG); - - clear_bit(READ_READY_BN, &tms_priv->state); - - retval = wait_event_interruptible(board->wait, - ((event_status = - read_and_clear_event_status(board)) & - (TERMINAL_COUNT_EVENT_BIT | - BUFFER_END_EVENT_BIT)) || - test_bit(DEV_CLEAR_BN, &tms_priv->state) || - test_bit(TIMO_NUM, &board->status)); - if (retval) { - retval = -ERESTARTSYS; - break; - } - // have to disable buffer before we can read from buffer port - outb(DIRECTION_GPIB_TO_HOST_BIT, hp_priv->iobase[3] + BUFFER_CONTROL_REG); - count = block_size - read_transfer_counter(hp_priv); - j = 0; - while (j < count && i < num_fifo_bytes) { - unsigned short data_word = inw(hp_priv->iobase[3] + BUFFER_PORT_LOW_REG); - - buffer[i++] = data_word & 0xff; - ++j; - if (j < count && i < num_fifo_bytes) { - buffer[i++] = (data_word >> 8) & 0xff; - ++j; - } - } - if (event_status & BUFFER_END_EVENT_BIT) { - clear_bit(RECEIVED_END_BN, &tms_priv->state); - - *end = 1; - tms_priv->holdoff_active = 1; - } - if (test_bit(TIMO_NUM, &board->status)) { - retval = -ETIMEDOUT; - break; - } - if (test_bit(DEV_CLEAR_BN, &tms_priv->state)) { - retval = -EINTR; - break; - } - } - *bytes_read += i; - buffer += i; - length -= i; - if (retval < 0) - return retval; - // read last byte if we havn't received an END yet - if (*end == 0) { - size_t num_bytes; - // try to make sure we holdoff after last byte read - retval = tms9914_read(board, tms_priv, buffer, length, end, &num_bytes); - *bytes_read += num_bytes; - if (retval < 0) - return retval; - } - return 0; -} - -static int restart_write_fifo(struct gpib_board *board, struct hp_82341_priv *hp_priv) -{ - struct tms9914_priv *tms_priv = &hp_priv->tms9914_priv; - - if ((inb(hp_priv->iobase[0] + STREAM_STATUS_REG) & HALTED_STATUS_BIT) == 0) - return 0; - while (1) { - int status; - - // restart doesn't work if data holdoff is in effect - status = tms9914_line_status(board, tms_priv); - if ((status & BUS_NRFD) == 0) { - outb(RESTART_STREAM_BIT, hp_priv->iobase[0] + STREAM_STATUS_REG); - return 0; - } - if (test_bit(DEV_CLEAR_BN, &tms_priv->state)) - return -EINTR; - if (test_bit(TIMO_NUM, &board->status)) - return -ETIMEDOUT; - if (msleep_interruptible(1)) - return -EINTR; - } - return 0; -} - -static int hp_82341_accel_write(struct gpib_board *board, u8 *buffer, size_t length, - int send_eoi, size_t *bytes_written) -{ - struct hp_82341_priv *hp_priv = board->private_data; - struct tms9914_priv *tms_priv = &hp_priv->tms9914_priv; - int i, j; - unsigned short event_status; - int retval = 0; - int fifo_xfer_len = length; - - *bytes_written = 0; - if (send_eoi) - --fifo_xfer_len; - - clear_bit(DEV_CLEAR_BN, &tms_priv->state); - - read_and_clear_event_status(board); - outb(0, hp_priv->iobase[3] + BUFFER_CONTROL_REG); - outb(0x00, hp_priv->iobase[3] + BUFFER_FLUSH_REG); - for (i = 0; i < fifo_xfer_len;) { - int block_size; - - block_size = min(fifo_xfer_len - i, hp_82341_fifo_size); - set_transfer_counter(hp_priv, block_size); - // load data into board's fifo - for (j = 0; j < block_size;) { - unsigned short data_word = buffer[i++]; - ++j; - if (j < block_size) { - data_word |= buffer[i++] << 8; - ++j; - } - outw(data_word, hp_priv->iobase[3] + BUFFER_PORT_LOW_REG); - } - clear_bit(WRITE_READY_BN, &tms_priv->state); - outb(ENABLE_TI_BUFFER_BIT, hp_priv->iobase[3] + BUFFER_CONTROL_REG); - retval = restart_write_fifo(board, hp_priv); - if (retval < 0) { - dev_err(board->gpib_dev, "failed to restart write stream\n"); - break; - } - retval = wait_event_interruptible(board->wait, - ((event_status = - read_and_clear_event_status(board)) & - TERMINAL_COUNT_EVENT_BIT) || - test_bit(DEV_CLEAR_BN, &tms_priv->state) || - test_bit(TIMO_NUM, &board->status)); - outb(0, hp_priv->iobase[3] + BUFFER_CONTROL_REG); - *bytes_written += block_size - read_transfer_counter(hp_priv); - if (retval) { - retval = -ERESTARTSYS; - break; - } - if (test_bit(TIMO_NUM, &board->status)) { - retval = -ETIMEDOUT; - break; - } - if (test_bit(DEV_CLEAR_BN, &tms_priv->state)) { - retval = -EINTR; - break; - } - } - if (retval) - return retval; - if (send_eoi) { - size_t num_bytes; - - retval = hp_82341_write(board, buffer + fifo_xfer_len, 1, 1, &num_bytes); - *bytes_written += num_bytes; - if (retval < 0) - return retval; - } - return 0; -} - -static int hp_82341_attach(struct gpib_board *board, const struct gpib_board_config *config); - -static void hp_82341_detach(struct gpib_board *board); - -// wrappers for interface functions -static int hp_82341_read(struct gpib_board *board, u8 *buffer, size_t length, int *end, - size_t *bytes_read) -{ - struct hp_82341_priv *priv = board->private_data; - - return tms9914_read(board, &priv->tms9914_priv, buffer, length, end, bytes_read); -} - -static int hp_82341_write(struct gpib_board *board, u8 *buffer, size_t length, int send_eoi, - size_t *bytes_written) -{ - struct hp_82341_priv *priv = board->private_data; - - return tms9914_write(board, &priv->tms9914_priv, buffer, length, send_eoi, bytes_written); -} - -static int hp_82341_command(struct gpib_board *board, u8 *buffer, size_t length, - size_t *bytes_written) -{ - struct hp_82341_priv *priv = board->private_data; - - return tms9914_command(board, &priv->tms9914_priv, buffer, length, bytes_written); -} - -static int hp_82341_take_control(struct gpib_board *board, int synchronous) -{ - struct hp_82341_priv *priv = board->private_data; - - return tms9914_take_control(board, &priv->tms9914_priv, synchronous); -} - -static int hp_82341_go_to_standby(struct gpib_board *board) -{ - struct hp_82341_priv *priv = board->private_data; - - return tms9914_go_to_standby(board, &priv->tms9914_priv); -} - -static int hp_82341_request_system_control(struct gpib_board *board, int request_control) -{ - struct hp_82341_priv *priv = board->private_data; - - if (request_control) - priv->mode_control_bits |= SYSTEM_CONTROLLER_BIT; - else - priv->mode_control_bits &= ~SYSTEM_CONTROLLER_BIT; - outb(priv->mode_control_bits, priv->iobase[0] + MODE_CONTROL_STATUS_REG); - return tms9914_request_system_control(board, &priv->tms9914_priv, request_control); -} - -static void hp_82341_interface_clear(struct gpib_board *board, int assert) -{ - struct hp_82341_priv *priv = board->private_data; - - tms9914_interface_clear(board, &priv->tms9914_priv, assert); -} - -static void hp_82341_remote_enable(struct gpib_board *board, int enable) -{ - struct hp_82341_priv *priv = board->private_data; - - tms9914_remote_enable(board, &priv->tms9914_priv, enable); -} - -static int hp_82341_enable_eos(struct gpib_board *board, u8 eos_byte, int compare_8_bits) -{ - struct hp_82341_priv *priv = board->private_data; - - return tms9914_enable_eos(board, &priv->tms9914_priv, eos_byte, compare_8_bits); -} - -static void hp_82341_disable_eos(struct gpib_board *board) -{ - struct hp_82341_priv *priv = board->private_data; - - tms9914_disable_eos(board, &priv->tms9914_priv); -} - -static unsigned int hp_82341_update_status(struct gpib_board *board, unsigned int clear_mask) -{ - struct hp_82341_priv *priv = board->private_data; - - return tms9914_update_status(board, &priv->tms9914_priv, clear_mask); -} - -static int hp_82341_primary_address(struct gpib_board *board, unsigned int address) -{ - struct hp_82341_priv *priv = board->private_data; - - return tms9914_primary_address(board, &priv->tms9914_priv, address); -} - -static int hp_82341_secondary_address(struct gpib_board *board, unsigned int address, int enable) -{ - struct hp_82341_priv *priv = board->private_data; - - return tms9914_secondary_address(board, &priv->tms9914_priv, address, enable); -} - -static int hp_82341_parallel_poll(struct gpib_board *board, u8 *result) -{ - struct hp_82341_priv *priv = board->private_data; - - return tms9914_parallel_poll(board, &priv->tms9914_priv, result); -} - -static void hp_82341_parallel_poll_configure(struct gpib_board *board, u8 config) -{ - struct hp_82341_priv *priv = board->private_data; - - tms9914_parallel_poll_configure(board, &priv->tms9914_priv, config); -} - -static void hp_82341_parallel_poll_response(struct gpib_board *board, int ist) -{ - struct hp_82341_priv *priv = board->private_data; - - tms9914_parallel_poll_response(board, &priv->tms9914_priv, ist); -} - -static void hp_82341_serial_poll_response(struct gpib_board *board, u8 status) -{ - struct hp_82341_priv *priv = board->private_data; - - tms9914_serial_poll_response(board, &priv->tms9914_priv, status); -} - -static u8 hp_82341_serial_poll_status(struct gpib_board *board) -{ - struct hp_82341_priv *priv = board->private_data; - - return tms9914_serial_poll_status(board, &priv->tms9914_priv); -} - -static int hp_82341_line_status(const struct gpib_board *board) -{ - struct hp_82341_priv *priv = board->private_data; - - return tms9914_line_status(board, &priv->tms9914_priv); -} - -static int hp_82341_t1_delay(struct gpib_board *board, unsigned int nano_sec) -{ - struct hp_82341_priv *priv = board->private_data; - - return tms9914_t1_delay(board, &priv->tms9914_priv, nano_sec); -} - -static void hp_82341_return_to_local(struct gpib_board *board) -{ - struct hp_82341_priv *priv = board->private_data; - - tms9914_return_to_local(board, &priv->tms9914_priv); -} - -static struct gpib_interface hp_82341_unaccel_interface = { - .name = "hp_82341_unaccel", - .attach = hp_82341_attach, - .detach = hp_82341_detach, - .read = hp_82341_read, - .write = hp_82341_write, - .command = hp_82341_command, - .request_system_control = hp_82341_request_system_control, - .take_control = hp_82341_take_control, - .go_to_standby = hp_82341_go_to_standby, - .interface_clear = hp_82341_interface_clear, - .remote_enable = hp_82341_remote_enable, - .enable_eos = hp_82341_enable_eos, - .disable_eos = hp_82341_disable_eos, - .parallel_poll = hp_82341_parallel_poll, - .parallel_poll_configure = hp_82341_parallel_poll_configure, - .parallel_poll_response = hp_82341_parallel_poll_response, - .local_parallel_poll_mode = NULL, // XXX - .line_status = hp_82341_line_status, - .update_status = hp_82341_update_status, - .primary_address = hp_82341_primary_address, - .secondary_address = hp_82341_secondary_address, - .serial_poll_response = hp_82341_serial_poll_response, - .serial_poll_status = hp_82341_serial_poll_status, - .t1_delay = hp_82341_t1_delay, - .return_to_local = hp_82341_return_to_local, -}; - -static struct gpib_interface hp_82341_interface = { - .name = "hp_82341", - .attach = hp_82341_attach, - .detach = hp_82341_detach, - .read = hp_82341_accel_read, - .write = hp_82341_accel_write, - .command = hp_82341_command, - .request_system_control = hp_82341_request_system_control, - .take_control = hp_82341_take_control, - .go_to_standby = hp_82341_go_to_standby, - .interface_clear = hp_82341_interface_clear, - .remote_enable = hp_82341_remote_enable, - .enable_eos = hp_82341_enable_eos, - .disable_eos = hp_82341_disable_eos, - .parallel_poll = hp_82341_parallel_poll, - .parallel_poll_configure = hp_82341_parallel_poll_configure, - .parallel_poll_response = hp_82341_parallel_poll_response, - .local_parallel_poll_mode = NULL, // XXX - .line_status = hp_82341_line_status, - .update_status = hp_82341_update_status, - .primary_address = hp_82341_primary_address, - .secondary_address = hp_82341_secondary_address, - .serial_poll_response = hp_82341_serial_poll_response, - .t1_delay = hp_82341_t1_delay, - .return_to_local = hp_82341_return_to_local, -}; - -static int hp_82341_allocate_private(struct gpib_board *board) -{ - board->private_data = kzalloc(sizeof(struct hp_82341_priv), GFP_KERNEL); - if (!board->private_data) - return -ENOMEM; - return 0; -} - -static void hp_82341_free_private(struct gpib_board *board) -{ - kfree(board->private_data); - board->private_data = NULL; -} - -static u8 hp_82341_read_byte(struct tms9914_priv *priv, unsigned int register_num) -{ - return inb(priv->iobase + register_num); -} - -static void hp_82341_write_byte(struct tms9914_priv *priv, u8 data, unsigned int register_num) -{ - outb(data, priv->iobase + register_num); -} - -static int hp_82341_find_isapnp_board(struct pnp_dev **dev) -{ - *dev = pnp_find_dev(NULL, ISAPNP_VENDOR('H', 'W', 'P'), - ISAPNP_FUNCTION(0x1411), NULL); - if (!*dev || !(*dev)->card) { - pr_err("failed to find isapnp board\n"); - return -ENODEV; - } - if (pnp_device_attach(*dev) < 0) { - pr_err("board already active, skipping\n"); - return -EBUSY; - } - if (pnp_activate_dev(*dev) < 0) { - pnp_device_detach(*dev); - pr_err("failed to activate(), aborting\n"); - return -EAGAIN; - } - if (!pnp_port_valid(*dev, 0) || !pnp_irq_valid(*dev, 0)) { - pnp_device_detach(*dev); - pr_err("invalid port or irq, aborting\n"); - return -ENOMEM; - } - return 0; -} - -static int xilinx_ready(struct hp_82341_priv *hp_priv) -{ - switch (hp_priv->hw_version) { - case HW_VERSION_82341C: - if (inb(hp_priv->iobase[0] + CONFIG_CONTROL_STATUS_REG) & XILINX_READY_BIT) - return 1; - else - return 0; - break; - case HW_VERSION_82341D: - if (isapnp_read_byte(PIO_DATA_REG) & HP_82341D_XILINX_READY_BIT) - return 1; - else - return 0; - default: - pr_err("bug! unknown hw_version\n"); - break; - } - return 0; -} - -static int xilinx_done(struct hp_82341_priv *hp_priv) -{ - switch (hp_priv->hw_version) { - case HW_VERSION_82341C: - if (inb(hp_priv->iobase[0] + CONFIG_CONTROL_STATUS_REG) & DONE_PGL_BIT) - return 1; - else - return 0; - case HW_VERSION_82341D: - if (isapnp_read_byte(PIO_DATA_REG) & HP_82341D_XILINX_DONE_BIT) - return 1; - else - return 0; - default: - pr_err("bug! unknown hw_version\n"); - break; - } - return 0; -} - -static int irq_valid(struct hp_82341_priv *hp_priv, int irq) -{ - switch (hp_priv->hw_version) { - case HW_VERSION_82341C: - switch (irq) { - case 3: - case 5: - case 7: - case 9: - case 10: - case 11: - case 12: - case 15: - return 1; - default: - pr_err("invalid irq=%i for 82341C, irq must be 3, 5, 7, 9, 10, 11, 12, or 15.\n", - irq); - return 0; - } - break; - case HW_VERSION_82341D: - return 1; - default: - pr_err("bug! unknown hw_version\n"); - break; - } - return 0; -} - -static int hp_82341_load_firmware_array(struct hp_82341_priv *hp_priv, - const unsigned char *firmware_data, - unsigned int firmware_length) -{ - int i, j; - static const int timeout = 100; - - for (i = 0; i < firmware_length; ++i) { - for (j = 0; j < timeout; ++j) { - if (need_resched()) - schedule(); - if (xilinx_ready(hp_priv)) - break; - usleep_range(10, 15); - } - if (j == timeout) { - pr_err("timed out waiting for Xilinx ready.\n"); - return -ETIMEDOUT; - } - outb(firmware_data[i], hp_priv->iobase[0] + XILINX_DATA_REG); - } - for (j = 0; j < timeout; ++j) { - if (xilinx_done(hp_priv)) - break; - if (need_resched()) - schedule(); - usleep_range(10, 15); - } - if (j == timeout) { - pr_err("timed out waiting for Xilinx done.\n"); - return -ETIMEDOUT; - } - return 0; -} - -static int hp_82341_load_firmware(struct hp_82341_priv *hp_priv, - const struct gpib_board_config *config) -{ - if (config->init_data_length == 0) { - if (xilinx_done(hp_priv)) - return 0; - pr_err("board needs be initialized with firmware upload.\n" - "\tUse the --init-data option of gpib_config.\n"); - return -EINVAL; - } - switch (hp_priv->hw_version) { - case HW_VERSION_82341C: - if (config->init_data_length != hp_82341c_firmware_length) { - pr_err("bad firmware length=%i for 82341c (expected %i).\n", - config->init_data_length, hp_82341c_firmware_length); - return -EINVAL; - } - break; - case HW_VERSION_82341D: - if (config->init_data_length != hp_82341d_firmware_length) { - pr_err("bad firmware length=%i for 82341d (expected %i).\n", - config->init_data_length, hp_82341d_firmware_length); - return -EINVAL; - } - break; - default: - pr_err("bug! unknown hw_version\n"); - break; - } - return hp_82341_load_firmware_array(hp_priv, config->init_data, config->init_data_length); -} - -static void set_xilinx_not_prog(struct hp_82341_priv *hp_priv, int assert) -{ - switch (hp_priv->hw_version) { - case HW_VERSION_82341C: - if (assert) - hp_priv->config_control_bits |= DONE_PGL_BIT; - else - hp_priv->config_control_bits &= ~DONE_PGL_BIT; - outb(hp_priv->config_control_bits, hp_priv->iobase[0] + CONFIG_CONTROL_STATUS_REG); - break; - case HW_VERSION_82341D: - if (assert) - isapnp_write_byte(PIO_DATA_REG, HP_82341D_NOT_PROG_BIT); - else - isapnp_write_byte(PIO_DATA_REG, 0x0); - break; - default: - break; - } -} - -// clear xilinx firmware -static int clear_xilinx(struct hp_82341_priv *hp_priv) -{ - set_xilinx_not_prog(hp_priv, 1); - if (msleep_interruptible(1)) - return -EINTR; - set_xilinx_not_prog(hp_priv, 0); - if (msleep_interruptible(1)) - return -EINTR; - set_xilinx_not_prog(hp_priv, 1); - if (msleep_interruptible(1)) - return -EINTR; - return 0; -} - -static int hp_82341_attach(struct gpib_board *board, const struct gpib_board_config *config) -{ - struct hp_82341_priv *hp_priv; - struct tms9914_priv *tms_priv; - u32 start_addr; - u32 iobase; - int irq; - int i; - int retval; - - board->status = 0; - if (hp_82341_allocate_private(board)) - return -ENOMEM; - hp_priv = board->private_data; - tms_priv = &hp_priv->tms9914_priv; - tms_priv->read_byte = hp_82341_read_byte; - tms_priv->write_byte = hp_82341_write_byte; - tms_priv->offset = 1; - - if (config->ibbase == 0) { - struct pnp_dev *dev; - int retval = hp_82341_find_isapnp_board(&dev); - - if (retval < 0) - return retval; - hp_priv->pnp_dev = dev; - iobase = pnp_port_start(dev, 0); - irq = pnp_irq(dev, 0); - hp_priv->hw_version = HW_VERSION_82341D; - hp_priv->io_region_offset = 0x8; - } else { - iobase = config->ibbase; - irq = config->ibirq; - hp_priv->hw_version = HW_VERSION_82341C; - hp_priv->io_region_offset = 0x400; - } - for (i = 0; i < hp_82341_num_io_regions; ++i) { - start_addr = iobase + i * hp_priv->io_region_offset; - if (!request_region(start_addr, hp_82341_region_iosize, DRV_NAME)) { - dev_err(board->gpib_dev, "failed to allocate io ports 0x%x-0x%x\n", - start_addr, - start_addr + hp_82341_region_iosize - 1); - return -EIO; - } - hp_priv->iobase[i] = start_addr; - } - tms_priv->iobase = hp_priv->iobase[2]; - if (hp_priv->hw_version == HW_VERSION_82341D) { - retval = isapnp_cfg_begin(hp_priv->pnp_dev->card->number, - hp_priv->pnp_dev->number); - if (retval < 0) { - dev_err(board->gpib_dev, "isapnp_cfg_begin returned error\n"); - return retval; - } - isapnp_write_byte(PIO_DIRECTION_REG, HP_82341D_XILINX_READY_BIT | - HP_82341D_XILINX_DONE_BIT); - } - retval = clear_xilinx(hp_priv); - if (retval < 0) - return retval; - retval = hp_82341_load_firmware(hp_priv, config); - if (hp_priv->hw_version == HW_VERSION_82341D) - isapnp_cfg_end(); - if (retval < 0) - return retval; - if (irq_valid(hp_priv, irq) == 0) - return -EINVAL; - if (request_irq(irq, hp_82341_interrupt, 0, DRV_NAME, board)) { - dev_err(board->gpib_dev, "failed to allocate IRQ %d\n", irq); - return -EIO; - } - hp_priv->irq = irq; - hp_priv->config_control_bits &= ~IRQ_SELECT_MASK; - hp_priv->config_control_bits |= IRQ_SELECT_BITS(irq); - outb(hp_priv->config_control_bits, hp_priv->iobase[0] + CONFIG_CONTROL_STATUS_REG); - hp_priv->mode_control_bits |= ENABLE_IRQ_CONFIG_BIT; - outb(hp_priv->mode_control_bits, hp_priv->iobase[0] + MODE_CONTROL_STATUS_REG); - tms9914_board_reset(tms_priv); - outb(ENABLE_BUFFER_END_EVENT_BIT | ENABLE_TERMINAL_COUNT_EVENT_BIT | - ENABLE_TI_INTERRUPT_EVENT_BIT, hp_priv->iobase[0] + EVENT_ENABLE_REG); - outb(ENABLE_BUFFER_END_INTERRUPT_BIT | ENABLE_TERMINAL_COUNT_INTERRUPT_BIT | - ENABLE_TI_INTERRUPT_BIT, hp_priv->iobase[0] + INTERRUPT_ENABLE_REG); - // write clear event register - outb((TI_INTERRUPT_EVENT_BIT | POINTERS_EQUAL_EVENT_BIT | - BUFFER_END_EVENT_BIT | TERMINAL_COUNT_EVENT_BIT), - hp_priv->iobase[0] + EVENT_STATUS_REG); - - tms9914_online(board, tms_priv); - - return 0; -} - -static void hp_82341_detach(struct gpib_board *board) -{ - struct hp_82341_priv *hp_priv = board->private_data; - struct tms9914_priv *tms_priv; - int i; - - if (hp_priv) { - tms_priv = &hp_priv->tms9914_priv; - if (hp_priv->iobase[0]) { - outb(0, hp_priv->iobase[0] + INTERRUPT_ENABLE_REG); - if (tms_priv->iobase) - tms9914_board_reset(tms_priv); - if (hp_priv->irq) - free_irq(hp_priv->irq, board); - } - for (i = 0; i < hp_82341_num_io_regions; ++i) { - if (hp_priv->iobase[i]) - release_region(hp_priv->iobase[i], hp_82341_region_iosize); - } - if (hp_priv->pnp_dev) - pnp_device_detach(hp_priv->pnp_dev); - } - hp_82341_free_private(board); -} - -#if 0 -/* unused, will be needed when the driver is turned into a pnp_driver */ -static const struct pnp_device_id hp_82341_pnp_table[] = { - {.id = "HWP1411"}, - {.id = ""} -}; -MODULE_DEVICE_TABLE(pnp, hp_82341_pnp_table); -#endif - -static int __init hp_82341_init_module(void) -{ - int ret; - - ret = gpib_register_driver(&hp_82341_unaccel_interface, THIS_MODULE); - if (ret) { - pr_err("gpib_register_driver failed: error = %d\n", ret); - return ret; - } - - ret = gpib_register_driver(&hp_82341_interface, THIS_MODULE); - if (ret) { - pr_err("gpib_register_driver failed: error = %d\n", ret); - gpib_unregister_driver(&hp_82341_unaccel_interface); - return ret; - } - - return 0; -} - -static void __exit hp_82341_exit_module(void) -{ - gpib_unregister_driver(&hp_82341_interface); - gpib_unregister_driver(&hp_82341_unaccel_interface); -} - -module_init(hp_82341_init_module); -module_exit(hp_82341_exit_module); - -/* - * GPIB interrupt service routines - */ -static unsigned short read_and_clear_event_status(struct gpib_board *board) -{ - struct hp_82341_priv *hp_priv = board->private_data; - unsigned long flags; - unsigned short status; - - spin_lock_irqsave(&board->spinlock, flags); - status = hp_priv->event_status_bits; - hp_priv->event_status_bits = 0; - spin_unlock_irqrestore(&board->spinlock, flags); - return status; -} - -static irqreturn_t hp_82341_interrupt(int irq, void *arg) -{ - int status1, status2; - struct gpib_board *board = arg; - struct hp_82341_priv *hp_priv = board->private_data; - struct tms9914_priv *tms_priv = &hp_priv->tms9914_priv; - unsigned long flags; - irqreturn_t retval = IRQ_NONE; - int event_status; - - spin_lock_irqsave(&board->spinlock, flags); - event_status = inb(hp_priv->iobase[0] + EVENT_STATUS_REG); - if (event_status & INTERRUPT_PENDING_EVENT_BIT) - retval = IRQ_HANDLED; - // write-clear status bits - if (event_status & (TI_INTERRUPT_EVENT_BIT | POINTERS_EQUAL_EVENT_BIT | - BUFFER_END_EVENT_BIT | TERMINAL_COUNT_EVENT_BIT)) { - outb(event_status & (TI_INTERRUPT_EVENT_BIT | POINTERS_EQUAL_EVENT_BIT | - BUFFER_END_EVENT_BIT | TERMINAL_COUNT_EVENT_BIT), - hp_priv->iobase[0] + EVENT_STATUS_REG); - hp_priv->event_status_bits |= event_status; - } - if (event_status & TI_INTERRUPT_EVENT_BIT) { - status1 = read_byte(tms_priv, ISR0); - status2 = read_byte(tms_priv, ISR1); - tms9914_interrupt_have_status(board, tms_priv, status1, status2); - } - spin_unlock_irqrestore(&board->spinlock, flags); - return retval; -} - -static int read_transfer_counter(struct hp_82341_priv *hp_priv) -{ - int lo, mid, value; - - lo = inb(hp_priv->iobase[1] + TRANSFER_COUNT_LOW_REG); - mid = inb(hp_priv->iobase[1] + TRANSFER_COUNT_MID_REG); - value = (lo & 0xff) | ((mid << 8) & 0x7f00); - value = ~(value - 1) & 0x7fff; - return value; -} - -static void set_transfer_counter(struct hp_82341_priv *hp_priv, int count) -{ - int complement = -count; - - outb(complement & 0xff, hp_priv->iobase[1] + TRANSFER_COUNT_LOW_REG); - outb((complement >> 8) & 0xff, hp_priv->iobase[1] + TRANSFER_COUNT_MID_REG); - // I don't think the hi count reg is even used, but oh well - outb((complement >> 16) & 0xf, hp_priv->iobase[1] + TRANSFER_COUNT_HIGH_REG); -} - diff --git a/drivers/staging/gpib/hp_82341/hp_82341.h b/drivers/staging/gpib/hp_82341/hp_82341.h deleted file mode 100644 index 859ef2899acb..000000000000 --- a/drivers/staging/gpib/hp_82341/hp_82341.h +++ /dev/null @@ -1,165 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -/*************************************************************************** - * copyright : (C) 2002, 2005 by Frank Mori Hess * - ***************************************************************************/ - -#include "tms9914.h" -#include "gpibP.h" - -enum hp_82341_hardware_version { - HW_VERSION_UNKNOWN, - HW_VERSION_82341C, - HW_VERSION_82341D, -}; - -// struct which defines private_data for board -struct hp_82341_priv { - struct tms9914_priv tms9914_priv; - unsigned int irq; - unsigned short config_control_bits; - unsigned short mode_control_bits; - unsigned short event_status_bits; - struct pnp_dev *pnp_dev; - unsigned long iobase[4]; - unsigned long io_region_offset; - enum hp_82341_hardware_version hw_version; -}; - -static const int hp_82341_region_iosize = 0x8; -static const int hp_82341_num_io_regions = 4; -static const int hp_82341_fifo_size = 0xffe; -static const int hp_82341c_firmware_length = 5764; -static const int hp_82341d_firmware_length = 5302; - -// hp 82341 register offsets -enum hp_82341_region_0_registers { - CONFIG_CONTROL_STATUS_REG = 0x0, - MODE_CONTROL_STATUS_REG = 0x1, - MONITOR_REG = 0x2, // after initialization - XILINX_DATA_REG = 0x2, // before initialization, write only - INTERRUPT_ENABLE_REG = 0x3, - EVENT_STATUS_REG = 0x4, - EVENT_ENABLE_REG = 0x5, - STREAM_STATUS_REG = 0x7, -}; - -enum hp_82341_region_1_registers { - ID0_REG = 0x2, - ID1_REG = 0x3, - TRANSFER_COUNT_LOW_REG = 0x4, - TRANSFER_COUNT_MID_REG = 0x5, - TRANSFER_COUNT_HIGH_REG = 0x6, -}; - -enum hp_82341_region_3_registers { - BUFFER_PORT_LOW_REG = 0x0, - BUFFER_PORT_HIGH_REG = 0x1, - ID2_REG = 0x2, - ID3_REG = 0x3, - BUFFER_FLUSH_REG = 0x4, - BUFFER_CONTROL_REG = 0x7 -}; - -enum config_control_status_bits { - IRQ_SELECT_MASK = 0x7, - DMA_CONFIG_MASK = 0x18, - ENABLE_DMA_CONFIG_BIT = 0x20, - XILINX_READY_BIT = 0x40, // read only - DONE_PGL_BIT = 0x80 -}; - -static inline unsigned int IRQ_SELECT_BITS(int irq) -{ - switch (irq) { - case 3: - return 0x3; - case 5: - return 0x2; - case 7: - return 0x1; - case 9: - return 0x0; - case 10: - return 0x7; - case 11: - return 0x6; - case 12: - return 0x5; - case 15: - return 0x4; - default: - return 0x0; - } -}; - -enum mode_control_status_bits { - SLOT8_BIT = 0x1, // read only - ACTIVE_CONTROLLER_BIT = 0x2, // read only - ENABLE_DMA_BIT = 0x4, - SYSTEM_CONTROLLER_BIT = 0x8, - MONITOR_BIT = 0x10, - ENABLE_IRQ_CONFIG_BIT = 0x20, - ENABLE_TI_STREAM_BIT = 0x40 -}; - -enum monitor_bits { - MONITOR_INTERRUPT_PENDING_BIT = 0x1, // read only - MONITOR_CLEAR_HOLDOFF_BIT = 0x2, // write only - MONITOR_PPOLL_BIT = 0x4, // write clear - MONITOR_SRQ_BIT = 0x8, // write clear - MONITOR_IFC_BIT = 0x10, // write clear - MONITOR_REN_BIT = 0x20, // write clear - MONITOR_END_BIT = 0x40, // write clear - MONITOR_DAV_BIT = 0x80 // write clear -}; - -enum interrupt_enable_bits { - ENABLE_TI_INTERRUPT_BIT = 0x1, - ENABLE_POINTERS_EQUAL_INTERRUPT_BIT = 0x4, - ENABLE_BUFFER_END_INTERRUPT_BIT = 0x10, - ENABLE_TERMINAL_COUNT_INTERRUPT_BIT = 0x20, - ENABLE_DMA_TERMINAL_COUNT_INTERRUPT_BIT = 0x80, -}; - -enum event_status_bits { - TI_INTERRUPT_EVENT_BIT = 0x1, // write clear - INTERRUPT_PENDING_EVENT_BIT = 0x2, // read only - POINTERS_EQUAL_EVENT_BIT = 0x4, // write clear - BUFFER_END_EVENT_BIT = 0x10, // write clear - TERMINAL_COUNT_EVENT_BIT = 0x20, // write clear - DMA_TERMINAL_COUNT_EVENT_BIT = 0x80, // write clear -}; - -enum event_enable_bits { - ENABLE_TI_INTERRUPT_EVENT_BIT = 0x1, // write clear - ENABLE_POINTERS_EQUAL_EVENT_BIT = 0x4, // write clear - ENABLE_BUFFER_END_EVENT_BIT = 0x10, // write clear - ENABLE_TERMINAL_COUNT_EVENT_BIT = 0x20, // write clear - ENABLE_DMA_TERMINAL_COUNT_EVENT_BIT = 0x80, // write clear -}; - -enum stream_status_bits { - HALTED_STATUS_BIT = 0x1, // read - RESTART_STREAM_BIT = 0x1 // write -}; - -enum buffer_control_bits { - DIRECTION_GPIB_TO_HOST_BIT = 0x20, // transfer direction (set for gpib to host) - ENABLE_TI_BUFFER_BIT = 0x40, // enable fifo - FAST_WR_EN_BIT = 0x80, // 350 ns t1 delay? -}; - -// registers accessible through isapnp chip on 82341d -enum hp_82341d_pnp_registers { - PIO_DATA_REG = 0x20, // read/write pio data lines - PIO_DIRECTION_REG = 0x21, // set pio data line directions (set for input) -}; - -enum hp_82341d_pnp_pio_bits { - HP_82341D_XILINX_READY_BIT = 0x1, - HP_82341D_XILINX_DONE_BIT = 0x2, - // use register layout compatible with C and older versions instead of 32 contiguous ioports - HP_82341D_LEGACY_MODE_BIT = 0x4, - HP_82341D_NOT_PROG_BIT = 0x8, // clear to reinitialize xilinx -}; diff --git a/drivers/staging/gpib/include/amcc5920.h b/drivers/staging/gpib/include/amcc5920.h deleted file mode 100644 index 7a88bd282feb..000000000000 --- a/drivers/staging/gpib/include/amcc5920.h +++ /dev/null @@ -1,49 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -/*************************************************************************** - * Header for amcc5920 pci chip - * - * copyright : (C) 2002 by Frank Mori Hess - ***************************************************************************/ - -// plx pci chip registers and bits -enum amcc_registers { - AMCC_INTCS_REG = 0x38, - AMCC_PASS_THRU_REG = 0x60, -}; - -enum amcc_incsr_bits { - AMCC_ADDON_INTR_ENABLE_BIT = 0x2000, - AMCC_ADDON_INTR_ACTIVE_BIT = 0x400000, - AMCC_INTR_ACTIVE_BIT = 0x800000, -}; - -static const int bits_per_region = 8; - -static inline uint32_t amcc_wait_state_bits(unsigned int region, unsigned int num_wait_states) -{ - return (num_wait_states & 0x7) << (--region * bits_per_region); -}; - -enum amcc_prefetch_bits { - PREFETCH_DISABLED = 0x0, - PREFETCH_SMALL = 0x8, - PREFETCH_MEDIUM = 0x10, - PREFETCH_LARGE = 0x18, -}; - -static inline uint32_t amcc_prefetch_bits(unsigned int region, enum amcc_prefetch_bits prefetch) -{ - return prefetch << (--region * bits_per_region); -}; - -static inline uint32_t amcc_PTADR_mode_bit(unsigned int region) -{ - return 0x80 << (--region * bits_per_region); -}; - -static inline uint32_t amcc_disable_write_fifo_bit(unsigned int region) -{ - return 0x20 << (--region * bits_per_region); -}; - diff --git a/drivers/staging/gpib/include/amccs5933.h b/drivers/staging/gpib/include/amccs5933.h deleted file mode 100644 index d7f63c795096..000000000000 --- a/drivers/staging/gpib/include/amccs5933.h +++ /dev/null @@ -1,59 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -/*************************************************************************** - * Registers and bits for amccs5933 pci chip - * copyright : (C) 2002 by Frank Mori Hess - ***************************************************************************/ - -// register offsets -enum { - MBEF_REG = 0x34, // mailbux empty/full - INTCSR_REG = 0x38, // interrupt control and status - BMCSR_REG = 0x3c, // bus master control and status -}; - -// incoming mailbox 0-3 register offsets -extern inline int INCOMING_MAILBOX_REG(unsigned int mailbox) -{ - return (0x10 + 4 * mailbox); -}; - -// bit definitions - -// INTCSR bits -enum { - OUTBOX_EMPTY_INTR_BIT = 0x10, // enable outbox empty interrupt - INBOX_FULL_INTR_BIT = 0x1000, // enable inbox full interrupt - INBOX_INTR_CS_BIT = 0x20000, // read, or write clear inbox full interrupt - INTR_ASSERTED_BIT = 0x800000, // read only, interrupt asserted -}; - -// select byte 0 to 3 of incoming mailbox -extern inline int INBOX_BYTE_BITS(unsigned int byte) -{ - return (byte & 0x3) << 8; -}; - -// select incoming mailbox 0 to 3 -extern inline int INBOX_SELECT_BITS(unsigned int mailbox) -{ - return (mailbox & 0x3) << 10; -}; - -// select byte 0 to 3 of outgoing mailbox -extern inline int OUTBOX_BYTE_BITS(unsigned int byte) -{ - return (byte & 0x3); -}; - -// select outgoing mailbox 0 to 3 -extern inline int OUTBOX_SELECT_BITS(unsigned int mailbox) -{ - return (mailbox & 0x3) << 2; -}; - -// BMCSR bits -enum { - MBOX_FLAGS_RESET_BIT = 0x08000000, // resets mailbox empty/full flags -}; - diff --git a/drivers/staging/gpib/include/gpibP.h b/drivers/staging/gpib/include/gpibP.h deleted file mode 100644 index 1b27f37e0ba0..000000000000 --- a/drivers/staging/gpib/include/gpibP.h +++ /dev/null @@ -1,41 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -/*************************************************************************** - * copyright : (C) 2002,2003 by Frank Mori Hess - ***************************************************************************/ - -#ifndef _GPIB_P_H -#define _GPIB_P_H - -#include - -#include "gpib_types.h" -#include "gpib_proto.h" -#include "gpib_cmd.h" -#include "gpib.h" -#include "gpib_ioctl.h" - -#include -#include -#include - -int gpib_register_driver(struct gpib_interface *interface, struct module *mod); -void gpib_unregister_driver(struct gpib_interface *interface); -struct pci_dev *gpib_pci_get_device(const struct gpib_board_config *config, unsigned int vendor_id, - unsigned int device_id, struct pci_dev *from); -struct pci_dev *gpib_pci_get_subsys(const struct gpib_board_config *config, unsigned int vendor_id, - unsigned int device_id, unsigned int ss_vendor, - unsigned int ss_device, struct pci_dev *from); -unsigned int num_gpib_events(const struct gpib_event_queue *queue); -int push_gpib_event(struct gpib_board *board, short event_type); -int pop_gpib_event(struct gpib_board *board, struct gpib_event_queue *queue, short *event_type); -int gpib_request_pseudo_irq(struct gpib_board *board, irqreturn_t (*handler)(int, void *)); -void gpib_free_pseudo_irq(struct gpib_board *board); -int gpib_match_device_path(struct device *dev, const char *device_path_in); - -extern struct gpib_board board_array[GPIB_MAX_NUM_BOARDS]; - -extern struct list_head registered_drivers; - -#endif // _GPIB_P_H - diff --git a/drivers/staging/gpib/include/gpib_cmd.h b/drivers/staging/gpib/include/gpib_cmd.h deleted file mode 100644 index 9e96a3bfa22d..000000000000 --- a/drivers/staging/gpib/include/gpib_cmd.h +++ /dev/null @@ -1,112 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -#ifndef _GPIB_CMD_H -#define _GPIB_CMD_H - -#include - -/* Command byte definitions tests and functions */ - -/* mask of bits that actually matter in a command byte */ -enum { - gpib_command_mask = 0x7f, -}; - -/* Possible GPIB command messages */ - -enum cmd_byte { - GTL = 0x1, /* go to local */ - SDC = 0x4, /* selected device clear */ - PP_CONFIG = 0x5, - GET = 0x8, /* group execute trigger */ - TCT = 0x9, /* take control */ - LLO = 0x11, /* local lockout */ - DCL = 0x14, /* device clear */ - PPU = 0x15, /* parallel poll unconfigure */ - SPE = 0x18, /* serial poll enable */ - SPD = 0x19, /* serial poll disable */ - CFE = 0x1f, /* configure enable */ - LAD = 0x20, /* value to be 'ored' in to obtain listen address */ - UNL = 0x3F, /* unlisten */ - TAD = 0x40, /* value to be 'ored' in to obtain talk address */ - UNT = 0x5F, /* untalk */ - SAD = 0x60, /* my secondary address (base) */ - PPE = 0x60, /* parallel poll enable (base) */ - PPD = 0x70 /* parallel poll disable */ -}; - -/* confine address to range 0 to 30. */ -static inline unsigned int gpib_address_restrict(u32 addr) -{ - addr &= 0x1f; - if (addr == 0x1f) - addr = 0; - return addr; -} - -static inline u8 MLA(u32 addr) -{ - return gpib_address_restrict(addr) | LAD; -} - -static inline u8 MTA(u32 addr) -{ - return gpib_address_restrict(addr) | TAD; -} - -static inline u8 MSA(u32 addr) -{ - return (addr & 0x1f) | SAD; -} - -static inline s32 gpib_address_equal(u32 pad1, s32 sad1, u32 pad2, s32 sad2) -{ - if (pad1 == pad2) { - if (sad1 == sad2) - return 1; - if (sad1 < 0 && sad2 < 0) - return 1; - } - - return 0; -} - -static inline s32 is_PPE(u8 command) -{ - return (command & 0x70) == 0x60; -} - -static inline s32 is_PPD(u8 command) -{ - return (command & 0x70) == 0x70; -} - -static inline s32 in_addressed_command_group(u8 command) -{ - return (command & 0x70) == 0x0; -} - -static inline s32 in_universal_command_group(u8 command) -{ - return (command & 0x70) == 0x10; -} - -static inline s32 in_listen_address_group(u8 command) -{ - return (command & 0x60) == 0x20; -} - -static inline s32 in_talk_address_group(u8 command) -{ - return (command & 0x60) == 0x40; -} - -static inline s32 in_primary_command_group(u8 command) -{ - return in_addressed_command_group(command) || - in_universal_command_group(command) || - in_listen_address_group(command) || - in_talk_address_group(command); -} - -#endif /* _GPIB_CMD_H */ diff --git a/drivers/staging/gpib/include/gpib_pci_ids.h b/drivers/staging/gpib/include/gpib_pci_ids.h deleted file mode 100644 index 52dcab07a7d1..000000000000 --- a/drivers/staging/gpib/include/gpib_pci_ids.h +++ /dev/null @@ -1,23 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -#ifndef __GPIB_PCI_IDS_H -#define __GPIB_PCI_IDS_H - -#ifndef PCI_VENDOR_ID_AMCC -#define PCI_VENDOR_ID_AMCC 0x10e8 -#endif - -#ifndef PCI_VENDOR_ID_CBOARDS -#define PCI_VENDOR_ID_CBOARDS 0x1307 -#endif - -#ifndef PCI_VENDOR_ID_QUANCOM -#define PCI_VENDOR_ID_QUANCOM 0x8008 -#endif - -#ifndef PCI_DEVICE_ID_QUANCOM_GPIB -#define PCI_DEVICE_ID_QUANCOM_GPIB 0x3302 -#endif - -#endif // __GPIB_PCI_IDS_H - diff --git a/drivers/staging/gpib/include/gpib_proto.h b/drivers/staging/gpib/include/gpib_proto.h deleted file mode 100644 index 42e736e3b7cd..000000000000 --- a/drivers/staging/gpib/include/gpib_proto.h +++ /dev/null @@ -1,49 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -#ifndef GPIB_PROTO_INCLUDED -#define GPIB_PROTO_INCLUDED - -#include - -int ibopen(struct inode *inode, struct file *filep); -int ibclose(struct inode *inode, struct file *file); -long ibioctl(struct file *filep, unsigned int cmd, unsigned long arg); -void os_start_timer(struct gpib_board *board, unsigned int usec_timeout); -void os_remove_timer(struct gpib_board *board); -void init_gpib_board(struct gpib_board *board); -static inline unsigned long usec_to_jiffies(unsigned int usec) -{ - unsigned long usec_per_jiffy = 1000000 / HZ; - - return 1 + (usec + usec_per_jiffy - 1) / usec_per_jiffy; -}; - -int serial_poll_all(struct gpib_board *board, unsigned int usec_timeout); -void init_gpib_descriptor(struct gpib_descriptor *desc); -int dvrsp(struct gpib_board *board, unsigned int pad, int sad, - unsigned int usec_timeout, u8 *result); -int ibcac(struct gpib_board *board, int sync, int fallback_to_async); -int ibcmd(struct gpib_board *board, u8 *buf, size_t length, size_t *bytes_written); -int ibgts(struct gpib_board *board); -int ibonline(struct gpib_board *board); -int iboffline(struct gpib_board *board); -int iblines(const struct gpib_board *board, short *lines); -int ibrd(struct gpib_board *board, u8 *buf, size_t length, int *end_flag, size_t *bytes_read); -int ibrpp(struct gpib_board *board, u8 *buf); -int ibrsv2(struct gpib_board *board, u8 status_byte, int new_reason_for_service); -int ibrsc(struct gpib_board *board, int request_control); -int ibsic(struct gpib_board *board, unsigned int usec_duration); -int ibsre(struct gpib_board *board, int enable); -int ibpad(struct gpib_board *board, unsigned int addr); -int ibsad(struct gpib_board *board, int addr); -int ibeos(struct gpib_board *board, int eos, int eosflags); -int ibwait(struct gpib_board *board, int wait_mask, int clear_mask, int set_mask, - int *status, unsigned long usec_timeout, struct gpib_descriptor *desc); -int ibwrt(struct gpib_board *board, u8 *buf, size_t cnt, int send_eoi, size_t *bytes_written); -int ibstatus(struct gpib_board *board); -int general_ibstatus(struct gpib_board *board, const struct gpib_status_queue *device, - int clear_mask, int set_mask, struct gpib_descriptor *desc); -int io_timed_out(struct gpib_board *board); -int ibppc(struct gpib_board *board, u8 configuration); - -#endif /* GPIB_PROTO_INCLUDED */ diff --git a/drivers/staging/gpib/include/gpib_state_machines.h b/drivers/staging/gpib/include/gpib_state_machines.h deleted file mode 100644 index 7488c00f191e..000000000000 --- a/drivers/staging/gpib/include/gpib_state_machines.h +++ /dev/null @@ -1,23 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -/*************************************************************************** - * copyright : (C) 2006 by Frank Mori Hess - ***************************************************************************/ - -#ifndef _GPIB_STATE_MACHINES_H -#define _GPIB_STATE_MACHINES_H - -enum talker_function_state { - talker_idle, - talker_addressed, - talker_active, - serial_poll_active -}; - -enum listener_function_state { - listener_idle, - listener_addressed, - listener_active -}; - -#endif // _GPIB_STATE_MACHINES_H diff --git a/drivers/staging/gpib/include/gpib_types.h b/drivers/staging/gpib/include/gpib_types.h deleted file mode 100644 index 998abb379749..000000000000 --- a/drivers/staging/gpib/include/gpib_types.h +++ /dev/null @@ -1,381 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -/*************************************************************************** - * copyright : (C) 2002 by Frank Mori Hess - ***************************************************************************/ - -#ifndef _GPIB_TYPES_H -#define _GPIB_TYPES_H - -#ifdef __KERNEL__ -#include "gpib.h" -#include -#include -#include -#include -#include -#include -#include - -struct gpib_board; - -/* config parameters that are only used by driver attach functions */ -struct gpib_board_config { - /* firmware blob */ - void *init_data; - int init_data_length; - /* IO base address to use for non-pnp cards (set by core, driver should make local copy) */ - u32 ibbase; - void __iomem *mmibbase; - /* IRQ to use for non-pnp cards (set by core, driver should make local copy) */ - unsigned int ibirq; - /* dma channel to use for non-pnp cards (set by core, driver should make local copy) */ - unsigned int ibdma; - /* - * pci bus of card, useful for distinguishing multiple identical pci cards - * (negative means don't care) - */ - int pci_bus; - /* - * pci slot of card, useful for distinguishing multiple identical pci cards - * (negative means don't care) - */ - int pci_slot; - /* sysfs device path of hardware to attach */ - char *device_path; - /* serial number of hardware to attach */ - char *serial_number; -}; - -/* - * struct gpib_interface defines the interface - * between the board-specific details dealt with in the drivers - * and generic interface provided by gpib-common. - * This really should be in a different header file. - */ -struct gpib_interface { - /* name of board */ - char *name; - /* attach() initializes board and allocates resources */ - int (*attach)(struct gpib_board *board, const struct gpib_board_config *config); - /* detach() shuts down board and frees resources */ - void (*detach)(struct gpib_board *board); - /* - * read() should read at most 'length' bytes from the bus into - * 'buffer'. It should return when it fills the buffer or - * encounters an END (EOI and or EOS if appropriate). It should set 'end' - * to be nonzero if the read was terminated by an END, otherwise 'end' - * should be zero. - * Ultimately, this will be changed into or replaced by an asynchronous - * read. Zero return value for success, negative - * return indicates error. - * nbytes returns number of bytes read - */ - int (*read)(struct gpib_board *board, u8 *buffer, size_t length, int *end, - size_t *bytes_read); - /* - * write() should write 'length' bytes from buffer to the bus. - * If the boolean value send_eoi is nonzero, then EOI should - * be sent along with the last byte. Returns number of bytes - * written or negative value on error. - */ - int (*write)(struct gpib_board *board, u8 *buffer, size_t length, int send_eoi, - size_t *bytes_written); - /* - * command() writes the command bytes in 'buffer' to the bus - * Returns zero on success or negative value on error. - */ - int (*command)(struct gpib_board *board, u8 *buffer, size_t length, - size_t *bytes_written); - /* - * Take control (assert ATN). If 'asyncronous' is nonzero, take - * control asyncronously (assert ATN immediately without waiting - * for other processes to complete first). Should not return - * until board becomes controller in charge. Returns zero no success, - * nonzero on error. - */ - int (*take_control)(struct gpib_board *board, int asyncronous); - /* - * De-assert ATN. Returns zero on success, nonzer on error. - */ - int (*go_to_standby)(struct gpib_board *board); - /* request/release control of the IFC and REN lines (system controller) */ - int (*request_system_control)(struct gpib_board *board, int request_control); - /* - * Asserts or de-asserts 'interface clear' (IFC) depending on - * boolean value of 'assert' - */ - void (*interface_clear)(struct gpib_board *board, int assert); - /* - * Sends remote enable command if 'enable' is nonzero, disables remote mode - * if 'enable' is zero - */ - void (*remote_enable)(struct gpib_board *board, int enable); - /* - * enable END for reads, when byte 'eos' is received. If - * 'compare_8_bits' is nonzero, then all 8 bits are compared - * with the eos bytes. Otherwise only the 7 least significant - * bits are compared. - */ - int (*enable_eos)(struct gpib_board *board, u8 eos, int compare_8_bits); - /* disable END on eos byte (END on EOI only)*/ - void (*disable_eos)(struct gpib_board *board); - /* configure parallel poll */ - void (*parallel_poll_configure)(struct gpib_board *board, u8 configuration); - /* conduct parallel poll */ - int (*parallel_poll)(struct gpib_board *board, u8 *result); - /* set/clear ist (individual status bit) */ - void (*parallel_poll_response)(struct gpib_board *board, int ist); - /* select local parallel poll configuration mode PP2 versus remote PP1 */ - void (*local_parallel_poll_mode)(struct gpib_board *board, int local); - /* - * Returns current status of the bus lines. Should be set to - * NULL if your board does not have the ability to query the - * state of the bus lines. - */ - int (*line_status)(const struct gpib_board *board); - /* - * updates and returns the board's current status. - * The meaning of the bits are specified in gpib_user.h - * in the IBSTA section. The driver does not need to - * worry about setting the CMPL, END, TIMO, or ERR bits. - */ - unsigned int (*update_status)(struct gpib_board *board, unsigned int clear_mask); - /* - * Sets primary address 0-30 for gpib interface card. - */ - int (*primary_address)(struct gpib_board *board, unsigned int address); - /* - * Sets and enables, or disables secondary address 0-30 - * for gpib interface card. - */ - int (*secondary_address)(struct gpib_board *board, unsigned int address, - int enable); - /* - * Sets the byte the board should send in response to a serial poll. - * This function should also start or stop requests for service via - * IEEE 488.2 reqt/reqf, based on MSS (bit 6 of the status_byte). - * If the more flexible serial_poll_response2 is implemented by the - * driver, then this method should be left NULL since it will not - * be used. This method can generate spurious service requests - * which are allowed by IEEE 488.2, but not ideal. - * - * This method should implement the serial poll response method described - * by IEEE 488.2 section 11.3.3.4.3 "Allowed Coupled Control of - * STB, reqt, and reqf". - */ - void (*serial_poll_response)(struct gpib_board *board, u8 status_byte); - /* - * Sets the byte the board should send in response to a serial poll. - * This function should also request service via IEEE 488.2 reqt/reqf - * based on MSS (bit 6 of the status_byte) and new_reason_for_service. - * reqt should be set true if new_reason_for_service is true, - * and reqf should be set true if MSS is false. This function - * will never be called with MSS false and new_reason_for_service - * true simultaneously, so don't worry about that case. - * - * This method implements the serial poll response method described - * by IEEE 488.2 section 11.3.3.4.1 "Preferred Implementation". - * - * If this method is left NULL by the driver, then the user library - * function ibrsv2 will not work. - */ - void (*serial_poll_response2)(struct gpib_board *board, u8 status_byte, - int new_reason_for_service); - /* - * returns the byte the board will send in response to a serial poll. - */ - u8 (*serial_poll_status)(struct gpib_board *board); - /* adjust T1 delay */ - int (*t1_delay)(struct gpib_board *board, unsigned int nano_sec); - /* go to local mode */ - void (*return_to_local)(struct gpib_board *board); - /* board does not support 7 bit eos comparisons */ - unsigned no_7_bit_eos : 1; - /* skip check for listeners before trying to send command bytes */ - unsigned skip_check_for_command_acceptors : 1; -}; - -struct gpib_event_queue { - struct list_head event_head; - spinlock_t lock; // for access to event list - unsigned int num_events; - unsigned dropped_event : 1; -}; - -static inline void init_event_queue(struct gpib_event_queue *queue) -{ - INIT_LIST_HEAD(&queue->event_head); - queue->num_events = 0; - queue->dropped_event = 0; - spin_lock_init(&queue->lock); -} - -/* struct for supporting polling operation when irq is not available */ -struct gpib_pseudo_irq { - struct timer_list timer; - irqreturn_t (*handler)(int irq, void *arg); - struct gpib_board *board; - atomic_t active; -}; - -static inline void init_gpib_pseudo_irq(struct gpib_pseudo_irq *pseudo_irq) -{ - pseudo_irq->handler = NULL; - timer_setup(&pseudo_irq->timer, NULL, 0); - atomic_set(&pseudo_irq->active, 0); -} - -/* list so we can make a linked list of drivers */ -struct gpib_interface_list { - struct list_head list; - struct gpib_interface *interface; - struct module *module; -}; - -/* - * One struct gpib_board is allocated for each physical board in the computer. - * It provides storage for variables local to each board, and interface - * functions for performing operations on the board - */ -struct gpib_board { - /* functions used by this board */ - struct gpib_interface *interface; - /* - * Pointer to module whose use count we should increment when - * interface is in use - */ - struct module *provider_module; - /* buffer used to store read/write data for this board */ - u8 *buffer; - /* length of buffer */ - unsigned int buffer_length; - /* - * Used to hold the board's current status (see update_status() above) - */ - unsigned long status; - /* - * Driver should only sleep on this wait queue. It is special in that the - * core will wake this queue and set the TIMO bit in 'status' when the - * watchdog timer times out. - */ - wait_queue_head_t wait; - /* - * Lock that only allows one process to access this board at a time. - * Has to be first in any locking order, since it can be locked over - * multiple ioctls. - */ - struct mutex user_mutex; - /* - * Mutex which compensates for removal of "big kernel lock" from kernel. - * Should not be held for extended waits. - */ - struct mutex big_gpib_mutex; - /* pid of last process to lock the board mutex */ - pid_t locking_pid; - /* lock for setting locking pid */ - spinlock_t locking_pid_spinlock; - /* Spin lock for dealing with races with the interrupt handler */ - spinlock_t spinlock; - /* Watchdog timer to enable timeouts */ - struct timer_list timer; - /* device of attached driver if any */ - struct device *dev; - /* gpib_common device gpibN */ - struct device *gpib_dev; - /* - * 'private_data' can be used as seen fit by the driver to - * store additional variables for this board - */ - void *private_data; - /* Number of open file descriptors using this board */ - unsigned int use_count; - /* list of open devices connected to this board */ - struct list_head device_list; - /* primary address */ - unsigned int pad; - /* secondary address */ - int sad; - /* timeout for io operations, in microseconds */ - unsigned int usec_timeout; - /* board's parallel poll configuration byte */ - u8 parallel_poll_configuration; - /* t1 delay we are using */ - unsigned int t1_nano_sec; - /* Count that keeps track of whether board is up and running or not */ - unsigned int online; - /* number of processes trying to autopoll */ - int autospollers; - /* autospoll kernel thread */ - struct task_struct *autospoll_task; - /* queue for recording received trigger/clear/ifc events */ - struct gpib_event_queue event_queue; - /* minor number for this board's device file */ - int minor; - /* struct to deal with polling mode*/ - struct gpib_pseudo_irq pseudo_irq; - /* error dong autopoll */ - atomic_t stuck_srq; - struct gpib_board_config config; - /* Flag that indicates whether board is system controller of the bus */ - unsigned master : 1; - /* individual status bit */ - unsigned ist : 1; - /* - * one means local parallel poll mode ieee 488.1 PP2 (or no parallel poll PP0), - * zero means remote parallel poll configuration mode ieee 488.1 PP1 - */ - unsigned local_ppoll_mode : 1; -}; - -/* element of event queue */ -struct gpib_event { - struct list_head list; - short event_type; -}; - -/* - * Each board has a list of gpib_status_queue to keep track of all open devices - * on the bus, so we know what address to poll when we get a service request - */ -struct gpib_status_queue { - /* list_head so we can make a linked list of devices */ - struct list_head list; - unsigned int pad; /* primary gpib address */ - int sad; /* secondary gpib address (negative means disabled) */ - /* stores serial poll bytes for this device */ - struct list_head status_bytes; - unsigned int num_status_bytes; - /* number of times this address is opened */ - unsigned int reference_count; - /* flags loss of status byte error due to limit on size of queue */ - unsigned dropped_byte : 1; -}; - -struct gpib_status_byte { - struct list_head list; - u8 poll_byte; -}; - -void init_gpib_status_queue(struct gpib_status_queue *device); - -/* Used to store device-descriptor-specific information */ -struct gpib_descriptor { - unsigned int pad; /* primary gpib address */ - int sad; /* secondary gpib address (negative means disabled) */ - atomic_t io_in_progress; - unsigned is_board : 1; - unsigned autopoll_enabled : 1; -}; - -struct gpib_file_private { - atomic_t holding_mutex; - struct gpib_descriptor *descriptors[GPIB_MAX_NUM_DESCRIPTORS]; - /* locked while descriptors are being allocated/deallocated */ - struct mutex descriptors_mutex; - unsigned got_module : 1; -}; - -#endif /* __KERNEL__ */ - -#endif /* _GPIB_TYPES_H */ diff --git a/drivers/staging/gpib/include/nec7210.h b/drivers/staging/gpib/include/nec7210.h deleted file mode 100644 index 9835aa5ef4ff..000000000000 --- a/drivers/staging/gpib/include/nec7210.h +++ /dev/null @@ -1,141 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -/*************************************************************************** - * copyright : (C) 2002 by Frank Mori Hess - ***************************************************************************/ - -#ifndef _NEC7210_H -#define _NEC7210_H - -#include "gpib_state_machines.h" -#include -#include -#include -#include - -#include "gpib_types.h" -#include "nec7210_registers.h" - -/* struct used to provide variables local to a nec7210 chip */ -struct nec7210_priv { -#ifdef CONFIG_HAS_IOPORT - u32 iobase; -#endif - void __iomem *mmiobase; - unsigned int offset; // offset between successive nec7210 io addresses - unsigned int dma_channel; - u8 *dma_buffer; - unsigned int dma_buffer_length; // length of dma buffer - dma_addr_t dma_buffer_addr; // bus address of board->buffer for use with dma - // software copy of bits written to registers - u8 reg_bits[8]; - u8 auxa_bits; // bits written to auxiliary register A - u8 auxb_bits; // bits written to auxiliary register B - // used to keep track of board's state, bit definitions given below - unsigned long state; - // lock for chips that extend the nec7210 registers by paging in alternate regs - spinlock_t register_page_lock; - // wrappers for outb, inb, readb, or writeb - u8 (*read_byte)(struct nec7210_priv *priv, unsigned int register_number); - void (*write_byte)(struct nec7210_priv *priv, u8 byte, unsigned int register_number); - enum nec7210_chipset type; - enum talker_function_state talker_state; - enum listener_function_state listener_state; - void *private; - unsigned srq_pending : 1; -}; - -static inline void init_nec7210_private(struct nec7210_priv *priv) -{ - memset(priv, 0, sizeof(struct nec7210_priv)); - spin_lock_init(&priv->register_page_lock); -} - -// slightly shorter way to access read_byte and write_byte -static inline u8 read_byte(struct nec7210_priv *priv, unsigned int register_number) -{ - return priv->read_byte(priv, register_number); -} - -static inline void write_byte(struct nec7210_priv *priv, u8 byte, unsigned int register_number) -{ - priv->write_byte(priv, byte, register_number); -} - -// struct nec7210_priv.state bit numbers -enum { - PIO_IN_PROGRESS_BN, // pio transfer in progress - DMA_READ_IN_PROGRESS_BN, // dma read transfer in progress - DMA_WRITE_IN_PROGRESS_BN, // dma write transfer in progress - READ_READY_BN, // board has data byte available to read - WRITE_READY_BN, // board is ready to send a data byte - COMMAND_READY_BN, // board is ready to send a command byte - RECEIVED_END_BN, // received END - BUS_ERROR_BN, // output error has occurred - RFD_HOLDOFF_BN, // rfd holdoff in effect - DEV_CLEAR_BN, // device clear received - ADR_CHANGE_BN, // address state change occurred -}; - -// interface functions -int nec7210_read(struct gpib_board *board, struct nec7210_priv *priv, u8 *buffer, - size_t length, int *end, size_t *bytes_read); -int nec7210_write(struct gpib_board *board, struct nec7210_priv *priv, u8 *buffer, - size_t length, int send_eoi, size_t *bytes_written); -int nec7210_command(struct gpib_board *board, struct nec7210_priv *priv, u8 *buffer, - size_t length, size_t *bytes_written); -int nec7210_take_control(struct gpib_board *board, struct nec7210_priv *priv, int syncronous); -int nec7210_go_to_standby(struct gpib_board *board, struct nec7210_priv *priv); -int nec7210_request_system_control(struct gpib_board *board, - struct nec7210_priv *priv, int request_control); -void nec7210_interface_clear(struct gpib_board *board, struct nec7210_priv *priv, int assert); -void nec7210_remote_enable(struct gpib_board *board, struct nec7210_priv *priv, int enable); -int nec7210_enable_eos(struct gpib_board *board, struct nec7210_priv *priv, u8 eos_bytes, - int compare_8_bits); -void nec7210_disable_eos(struct gpib_board *board, struct nec7210_priv *priv); -unsigned int nec7210_update_status(struct gpib_board *board, struct nec7210_priv *priv, - unsigned int clear_mask); -unsigned int nec7210_update_status_nolock(struct gpib_board *board, struct nec7210_priv *priv); -int nec7210_primary_address(const struct gpib_board *board, - struct nec7210_priv *priv, unsigned int address); -int nec7210_secondary_address(const struct gpib_board *board, struct nec7210_priv *priv, - unsigned int address, int enable); -int nec7210_parallel_poll(struct gpib_board *board, struct nec7210_priv *priv, u8 *result); -void nec7210_serial_poll_response(struct gpib_board *board, - struct nec7210_priv *priv, u8 status); -void nec7210_parallel_poll_configure(struct gpib_board *board, - struct nec7210_priv *priv, unsigned int configuration); -void nec7210_parallel_poll_response(struct gpib_board *board, - struct nec7210_priv *priv, int ist); -u8 nec7210_serial_poll_status(struct gpib_board *board, struct nec7210_priv *priv); -int nec7210_t1_delay(struct gpib_board *board, - struct nec7210_priv *priv, unsigned int nano_sec); -void nec7210_return_to_local(const struct gpib_board *board, struct nec7210_priv *priv); - -// utility functions -void nec7210_board_reset(struct nec7210_priv *priv, const struct gpib_board *board); -void nec7210_board_online(struct nec7210_priv *priv, const struct gpib_board *board); -unsigned int nec7210_set_reg_bits(struct nec7210_priv *priv, unsigned int reg, - unsigned int mask, unsigned int bits); -void nec7210_set_handshake_mode(struct gpib_board *board, struct nec7210_priv *priv, int mode); -void nec7210_release_rfd_holdoff(struct gpib_board *board, struct nec7210_priv *priv); -u8 nec7210_read_data_in(struct gpib_board *board, struct nec7210_priv *priv, int *end); - -// wrappers for io functions -u8 nec7210_ioport_read_byte(struct nec7210_priv *priv, unsigned int register_num); -void nec7210_ioport_write_byte(struct nec7210_priv *priv, u8 data, unsigned int register_num); -u8 nec7210_iomem_read_byte(struct nec7210_priv *priv, unsigned int register_num); -void nec7210_iomem_write_byte(struct nec7210_priv *priv, u8 data, unsigned int register_num); -u8 nec7210_locking_ioport_read_byte(struct nec7210_priv *priv, unsigned int register_num); -void nec7210_locking_ioport_write_byte(struct nec7210_priv *priv, u8 data, - unsigned int register_num); -u8 nec7210_locking_iomem_read_byte(struct nec7210_priv *priv, unsigned int register_num); -void nec7210_locking_iomem_write_byte(struct nec7210_priv *priv, u8 data, - unsigned int register_num); - -// interrupt service routine -irqreturn_t nec7210_interrupt(struct gpib_board *board, struct nec7210_priv *priv); -irqreturn_t nec7210_interrupt_have_status(struct gpib_board *board, - struct nec7210_priv *priv, int status1, int status2); - -#endif //_NEC7210_H diff --git a/drivers/staging/gpib/include/nec7210_registers.h b/drivers/staging/gpib/include/nec7210_registers.h deleted file mode 100644 index 067983d7a07f..000000000000 --- a/drivers/staging/gpib/include/nec7210_registers.h +++ /dev/null @@ -1,218 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -/*************************************************************************** - * copyright : (C) 2002 by Frank Mori Hess - ***************************************************************************/ - -#ifndef _NEC7210_REGISTERS_H -#define _NEC7210_REGISTERS_H - -enum nec7210_chipset { - NEC7210, // The original - TNT4882, // NI - NAT4882, // NI - CB7210, // measurement computing - IOT7210, // iotech - IGPIB7210, // Ines - TNT5004, // NI (minor differences to TNT4882) -}; - -/* - * nec7210 register numbers (might need to be multiplied by - * a board-dependent offset to get actually io address offset) - */ -// write registers -enum nec7210_write_regs { - CDOR, // command/data out - IMR1, // interrupt mask 1 - IMR2, // interrupt mask 2 - SPMR, // serial poll mode - ADMR, // address mode - AUXMR, // auxiliary mode - ADR, // address - EOSR, // end-of-string - - // nec7210 has 8 registers - nec7210_num_registers = 8, -}; - -// read registers -enum nec7210_read_regs { - DIR, // data in - ISR1, // interrupt status 1 - ISR2, // interrupt status 2 - SPSR, // serial poll status - ADSR, // address status - CPTR, // command pass though - ADR0, // address 1 - ADR1, // address 2 -}; - -// bit definitions common to nec-7210 compatible registers - -// ISR1: interrupt status register 1 -enum isr1_bits { - HR_DI = (1 << 0), - HR_DO = (1 << 1), - HR_ERR = (1 << 2), - HR_DEC = (1 << 3), - HR_END = (1 << 4), - HR_DET = (1 << 5), - HR_APT = (1 << 6), - HR_CPT = (1 << 7), -}; - -// IMR1: interrupt mask register 1 -enum imr1_bits { - HR_DIIE = (1 << 0), - HR_DOIE = (1 << 1), - HR_ERRIE = (1 << 2), - HR_DECIE = (1 << 3), - HR_ENDIE = (1 << 4), - HR_DETIE = (1 << 5), - HR_APTIE = (1 << 6), - HR_CPTIE = (1 << 7), -}; - -// ISR2, interrupt status register 2 -enum isr2_bits { - HR_ADSC = (1 << 0), - HR_REMC = (1 << 1), - HR_LOKC = (1 << 2), - HR_CO = (1 << 3), - HR_REM = (1 << 4), - HR_LOK = (1 << 5), - HR_SRQI = (1 << 6), - HR_INT = (1 << 7), -}; - -// IMR2, interrupt mask register 2 -enum imr2_bits { - // all the bits in this register that enable interrupts - IMR2_ENABLE_INTR_MASK = 0x4f, - HR_ACIE = (1 << 0), - HR_REMIE = (1 << 1), - HR_LOKIE = (1 << 2), - HR_COIE = (1 << 3), - HR_DMAI = (1 << 4), - HR_DMAO = (1 << 5), - HR_SRQIE = (1 << 6), -}; - -// SPSR, serial poll status register -enum spsr_bits { - HR_PEND = (1 << 6), -}; - -// SPMR, serial poll mode register -enum spmr_bits { - HR_RSV = (1 << 6), -}; - -// ADSR, address status register -enum adsr_bits { - HR_MJMN = (1 << 0), - HR_TA = (1 << 1), - HR_LA = (1 << 2), - HR_TPAS = (1 << 3), - HR_LPAS = (1 << 4), - HR_SPMS = (1 << 5), - HR_NATN = (1 << 6), - HR_CIC = (1 << 7), -}; - -// ADMR, address mode register -enum admr_bits { - HR_ADM0 = (1 << 0), - HR_ADM1 = (1 << 1), - HR_TRM0 = (1 << 4), - HR_TRM1 = (1 << 5), - HR_TRM_EOIOE_TRIG = 0, - HR_TRM_CIC_TRIG = HR_TRM0, - HR_TRM_CIC_EOIOE = HR_TRM1, - HR_TRM_CIC_PE = HR_TRM0 | HR_TRM1, - HR_LON = (1 << 6), - HR_TON = (1 << 7), -}; - -// ADR, bits used in address0, address1 and address0/1 registers -enum adr_bits { - ADDRESS_MASK = 0x1f, /* mask to specify lower 5 bits */ - HR_DL = (1 << 5), - HR_DT = (1 << 6), - HR_ARS = (1 << 7), -}; - -// ADR1, address1 register -enum adr1_bits { - HR_EOI = (1 << 7), -}; - -// AUXMR, auxiliary mode register -enum auxmr_bits { - ICR = 0x20, - PPR = 0x60, - AUXRA = 0x80, - AUXRB = 0xa0, - AUXRE = 0xc0, -}; - -// auxra, auxiliary register A -enum auxra_bits { - HR_HANDSHAKE_MASK = 0x3, - HR_HLDA = 0x1, - HR_HLDE = 0x2, - HR_LCM = 0x3, /* auxra listen continuous */ - HR_REOS = 0x4, - HR_XEOS = 0x8, - HR_BIN = 0x10, -}; - -// auxrb, auxiliary register B -enum auxrb_bits { - HR_CPTE = (1 << 0), - HR_SPEOI = (1 << 1), - HR_TRI = (1 << 2), - HR_INV = (1 << 3), - HR_ISS = (1 << 4), -}; - -enum auxre_bits { - HR_DAC_HLD_DCAS = 0x1, /* perform DAC holdoff on receiving clear */ - HR_DAC_HLD_DTAS = 0x2, /* perform DAC holdoff on receiving trigger */ -}; - -// parallel poll register -enum ppr_bits { - HR_PPS = (1 << 3), - HR_PPU = (1 << 4), -}; - -/* 7210 Auxiliary Commands */ -enum aux_cmds { - AUX_PON = 0x0, /* Immediate Execute pon */ - AUX_CPPF = 0x1, /* Clear Parallel Poll Flag */ - AUX_CR = 0x2, /* Chip Reset */ - AUX_FH = 0x3, /* Finish Handshake */ - AUX_TRIG = 0x4, /* Trigger */ - AUX_RTL = 0x5, /* Return to local */ - AUX_SEOI = 0x6, /* Send EOI */ - AUX_NVAL = 0x7, /* Non-Valid Secondary Command or Address */ - AUX_SPPF = 0x9, /* Set Parallel Poll Flag */ - AUX_VAL = 0xf, /* Valid Secondary Command or Address */ - AUX_GTS = 0x10, /* Go To Standby */ - AUX_TCA = 0x11, /* Take Control Asynchronously */ - AUX_TCS = 0x12, /* Take Control Synchronously */ - AUX_LTN = 0x13, /* Listen */ - AUX_DSC = 0x14, /* Disable System Control */ - AUX_CIFC = 0x16, /* Clear IFC */ - AUX_CREN = 0x17, /* Clear REN */ - AUX_TCSE = 0x1a, /* Take Control Synchronously on End */ - AUX_LTNC = 0x1b, /* Listen in Continuous Mode */ - AUX_LUN = 0x1c, /* Local Unlisten */ - AUX_EPP = 0x1d, /* Execute Parallel Poll */ - AUX_SIFC = 0x1e, /* Set IFC */ - AUX_SREN = 0x1f, /* Set REN */ -}; - -#endif //_NEC7210_REGISTERS_H diff --git a/drivers/staging/gpib/include/plx9050.h b/drivers/staging/gpib/include/plx9050.h deleted file mode 100644 index c911b285a0ca..000000000000 --- a/drivers/staging/gpib/include/plx9050.h +++ /dev/null @@ -1,72 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -/*************************************************************************** - * Header for plx9050 pci chip - * copyright : (C) 2002 by Frank Mori Hess - ***************************************************************************/ - -#ifndef _PLX9050_GPIB_H -#define _PLX9050_GPIB_H - -// plx pci chip registers and bits -enum { - PLX9050_INTCSR_REG = 0x4c, - PLX9050_CNTRL_REG = 0x50 -}; - -enum plx9050_intcsr_bits { - PLX9050_LINTR1_EN_BIT = 0x1, - PLX9050_LINTR1_POLARITY_BIT = 0x2, - PLX9050_LINTR1_STATUS_BIT = 0x4, - PLX9050_LINTR2_EN_BIT = 0x8, - PLX9050_LINTR2_POLARITY_BIT = 0x10, - PLX9050_LINTR2_STATUS_BIT = 0x20, - PLX9050_PCI_INTR_EN_BIT = 0x40, - PLX9050_SOFT_INTR_BIT = 0x80, - PLX9050_LINTR1_SELECT_ENABLE_BIT = 0x100, // 9052 extension - PLX9050_LINTR2_SELECT_ENABLE_BIT = 0x200, // 9052 extension - PLX9050_LINTR1_EDGE_CLEAR_BIT = 0x400, // 9052 extension - PLX9050_LINTR2_EDGE_CLEAR_BIT = 0x800, // 9052 extension -}; - -enum plx9050_cntrl_bits { - PLX9050_WAITO_NOT_USER0_SELECT_BIT = 0x1, - PLX9050_USER0_OUTPUT_BIT = 0x2, - PLX9050_USER0_DATA_BIT = 0x4, - PLX9050_LLOCK_NOT_USER1_SELECT_BIT = 0x8, - PLX9050_USER1_OUTPUT_BIT = 0x10, - PLX9050_USER1_DATA_BIT = 0x20, - PLX9050_CS2_NOT_USER2_SELECT_BIT = 0x40, - PLX9050_USER2_OUTPUT_BIT = 0x80, - PLX9050_USER2_DATA_BIT = 0x100, - PLX9050_CS3_NOT_USER3_SELECT_BIT = 0x200, - PLX9050_USER3_OUTPUT_BIT = 0x400, - PLX9050_USER3_DATA_BIT = 0x800, - PLX9050_PCIBAR_ENABLE_MASK = 0x3000, - PLX9050_PCIBAR_MEMORY_AND_IO_ENABLE_BITS = 0x0, - PLX9050_PCIBAR_MEMORY_NO_IO_ENABLE_BITS = 0x1000, - PLX9050_PCIBAR_IO_NO_MEMORY_ENABLE_BITS = 0x2000, - PLX9050_PCIBAR_MEMORY_AND_IO_TOO_ENABLE_BITS = 0x3000, - PLX9050_PCI_READ_MODE_BIT = 0x4000, - PLX9050_PCI_READ_WITH_WRITE_FLUSH_MODE_BIT = 0x8000, - PLX9050_PCI_READ_NO_FLUSH_MODE_BIT = 0x10000, - PLX9050_PCI_READ_NO_WRITE_MODE_BIT = 0x20000, - PLX9050_PCI_WRITE_MODE_BIT = 0x40000, - PLX9050_PCI_RETRY_DELAY_MASK = 0x780000, - PLX9050_DIRECT_SLAVE_LOCK_ENABLE_BIT = 0x800000, - PLX9050_EEPROM_CLOCK_BIT = 0x1000000, - PLX9050_EEPROM_CHIP_SELECT_BIT = 0x2000000, - PLX9050_WRITE_TO_EEPROM_BIT = 0x4000000, - PLX9050_READ_EEPROM_DATA_BIT = 0x8000000, - PLX9050_EEPROM_VALID_BIT = 0x10000000, - PLX9050_RELOAD_CONFIG_REGISTERS_BIT = 0x20000000, - PLX9050_PCI_SOFTWARE_RESET_BIT = 0x40000000, - PLX9050_MASK_REVISION_BIT = 0x80000000 -}; - -static inline unsigned int PLX9050_PCI_RETRY_DELAY_BITS(unsigned int clocks) -{ - return ((clocks / 8) << 19) & PLX9050_PCI_RETRY_DELAY_MASK; -} - -#endif // _PLX9050_GPIB_H diff --git a/drivers/staging/gpib/include/quancom_pci.h b/drivers/staging/gpib/include/quancom_pci.h deleted file mode 100644 index cdaf0d056be9..000000000000 --- a/drivers/staging/gpib/include/quancom_pci.h +++ /dev/null @@ -1,22 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -/*************************************************************************** - * Quancom pci stuff - * copyright (C) 2005 by Frank Mori Hess - ***************************************************************************/ - -#ifndef _QUANCOM_PCI_H -#define _QUANCOM_PCI_H - -/* quancom registers */ -enum quancom_regs { - QUANCOM_IRQ_CONTROL_STATUS_REG = 0xfc, -}; - -enum quancom_irq_control_status_bits { - QUANCOM_IRQ_ASSERTED_BIT = 0x1, /* readable */ - /* (any write to the register clears the interrupt)*/ - QUANCOM_IRQ_ENABLE_BIT = 0x4, /* writeable */ -}; - -#endif // _QUANCOM_PCI_H diff --git a/drivers/staging/gpib/include/tms9914.h b/drivers/staging/gpib/include/tms9914.h deleted file mode 100644 index e66b75e0fda8..000000000000 --- a/drivers/staging/gpib/include/tms9914.h +++ /dev/null @@ -1,280 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -/*************************************************************************** - * copyright : (C) 2002 by Frank Mori Hess - ***************************************************************************/ - -#ifndef _TMS9914_H -#define _TMS9914_H - -#include -#include -#include "gpib_state_machines.h" -#include "gpib_types.h" - -enum tms9914_holdoff_mode { - TMS9914_HOLDOFF_NONE, - TMS9914_HOLDOFF_EOI, - TMS9914_HOLDOFF_ALL, -}; - -/* struct used to provide variables local to a tms9914 chip */ -struct tms9914_priv { -#ifdef CONFIG_HAS_IOPORT - u32 iobase; -#endif - void __iomem *mmiobase; - unsigned int offset; // offset between successive tms9914 io addresses - unsigned int dma_channel; - // software copy of bits written to interrupt mask registers - u8 imr0_bits, imr1_bits; - // bits written to address mode register - u8 admr_bits; - u8 auxa_bits; // bits written to auxiliary register A - // used to keep track of board's state, bit definitions given below - unsigned long state; - u8 eos; // eos character - short eos_flags; - u8 spoll_status; - enum tms9914_holdoff_mode holdoff_mode; - unsigned int ppoll_line; - enum talker_function_state talker_state; - enum listener_function_state listener_state; - unsigned ppoll_sense : 1; - unsigned ppoll_enable : 1; - unsigned ppoll_configure_state : 1; - unsigned primary_listen_addressed : 1; - unsigned primary_talk_addressed : 1; - unsigned holdoff_on_end : 1; - unsigned holdoff_on_all : 1; - unsigned holdoff_active : 1; - // wrappers for outb, inb, readb, or writeb - u8 (*read_byte)(struct tms9914_priv *priv, unsigned int register_number); - void (*write_byte)(struct tms9914_priv *priv, u8 byte, unsigned int - register_number); -}; - -// slightly shorter way to access read_byte and write_byte -static inline u8 read_byte(struct tms9914_priv *priv, unsigned int register_number) -{ - return priv->read_byte(priv, register_number); -} - -static inline void write_byte(struct tms9914_priv *priv, u8 byte, unsigned int register_number) -{ - priv->write_byte(priv, byte, register_number); -} - -// struct tms9914_priv.state bit numbers -enum { - PIO_IN_PROGRESS_BN, // pio transfer in progress - DMA_READ_IN_PROGRESS_BN, // dma read transfer in progress - DMA_WRITE_IN_PROGRESS_BN, // dma write transfer in progress - READ_READY_BN, // board has data byte available to read - WRITE_READY_BN, // board is ready to send a data byte - COMMAND_READY_BN, // board is ready to send a command byte - RECEIVED_END_BN, // received END - BUS_ERROR_BN, // bus error - DEV_CLEAR_BN, // device clear received -}; - -// interface functions -int tms9914_read(struct gpib_board *board, struct tms9914_priv *priv, u8 *buffer, - size_t length, int *end, size_t *bytes_read); -int tms9914_write(struct gpib_board *board, struct tms9914_priv *priv, u8 *buffer, - size_t length, int send_eoi, size_t *bytes_written); -int tms9914_command(struct gpib_board *board, struct tms9914_priv *priv, u8 *buffer, - size_t length, size_t *bytes_written); -int tms9914_take_control(struct gpib_board *board, struct tms9914_priv *priv, int syncronous); -/* - * alternate version of tms9914_take_control which works around buggy tcs - * implementation. - */ -int tms9914_take_control_workaround(struct gpib_board *board, struct tms9914_priv *priv, - int syncronous); -int tms9914_go_to_standby(struct gpib_board *board, struct tms9914_priv *priv); -int tms9914_request_system_control(struct gpib_board *board, struct tms9914_priv *priv, - int request_control); -void tms9914_interface_clear(struct gpib_board *board, struct tms9914_priv *priv, int assert); -void tms9914_remote_enable(struct gpib_board *board, struct tms9914_priv *priv, int enable); -int tms9914_enable_eos(struct gpib_board *board, struct tms9914_priv *priv, u8 eos_bytes, - int compare_8_bits); -void tms9914_disable_eos(struct gpib_board *board, struct tms9914_priv *priv); -unsigned int tms9914_update_status(struct gpib_board *board, struct tms9914_priv *priv, - unsigned int clear_mask); -int tms9914_primary_address(struct gpib_board *board, - struct tms9914_priv *priv, unsigned int address); -int tms9914_secondary_address(struct gpib_board *board, struct tms9914_priv *priv, - unsigned int address, int enable); -int tms9914_parallel_poll(struct gpib_board *board, struct tms9914_priv *priv, u8 *result); -void tms9914_parallel_poll_configure(struct gpib_board *board, - struct tms9914_priv *priv, u8 config); -void tms9914_parallel_poll_response(struct gpib_board *board, - struct tms9914_priv *priv, int ist); -void tms9914_serial_poll_response(struct gpib_board *board, - struct tms9914_priv *priv, u8 status); -u8 tms9914_serial_poll_status(struct gpib_board *board, struct tms9914_priv *priv); -int tms9914_line_status(const struct gpib_board *board, struct tms9914_priv *priv); -unsigned int tms9914_t1_delay(struct gpib_board *board, struct tms9914_priv *priv, - unsigned int nano_sec); -void tms9914_return_to_local(const struct gpib_board *board, struct tms9914_priv *priv); - -// utility functions -void tms9914_board_reset(struct tms9914_priv *priv); -void tms9914_online(struct gpib_board *board, struct tms9914_priv *priv); -void tms9914_release_holdoff(struct tms9914_priv *priv); -void tms9914_set_holdoff_mode(struct tms9914_priv *priv, enum tms9914_holdoff_mode mode); - -// wrappers for io functions -u8 tms9914_ioport_read_byte(struct tms9914_priv *priv, unsigned int register_num); -void tms9914_ioport_write_byte(struct tms9914_priv *priv, u8 data, unsigned int register_num); -u8 tms9914_iomem_read_byte(struct tms9914_priv *priv, unsigned int register_num); -void tms9914_iomem_write_byte(struct tms9914_priv *priv, u8 data, unsigned int register_num); - -// interrupt service routine -irqreturn_t tms9914_interrupt(struct gpib_board *board, struct tms9914_priv *priv); -irqreturn_t tms9914_interrupt_have_status(struct gpib_board *board, struct tms9914_priv *priv, - int status1, int status2); - -// tms9914 has 8 registers -enum { - ms9914_num_registers = 8, -}; - -/* - * tms9914 register numbers (might need to be multiplied by - * a board-dependent offset to get actually io address offset) - */ -// write registers -enum { - IMR0 = 0, /* interrupt mask 0 */ - IMR1 = 1, /* interrupt mask 1 */ - AUXCR = 3, /* auxiliary command */ - ADR = 4, /* address register */ - SPMR = 5, /* serial poll mode register */ - PPR = 6, /* parallel poll */ - CDOR = 7, /* data out register */ -}; - -// read registers -enum { - ISR0 = 0, /* interrupt status 0 */ - ISR1 = 1, /* interrupt status 1 */ - ADSR = 2, /* address status */ - BSR = 3, /* bus status */ - CPTR = 6, /* command pass thru */ - DIR = 7, /* data in register */ -}; - -// bit definitions common to tms9914 compatible registers - -/* ISR0 - Register bits */ -enum isr0_bits { - HR_MAC = (1 << 0), /* My Address Change */ - HR_RLC = (1 << 1), /* Remote/Local change */ - HR_SPAS = (1 << 2), /* Serial Poll active State */ - HR_END = (1 << 3), /* END (EOI or EOS) */ - HR_BO = (1 << 4), /* Byte Out */ - HR_BI = (1 << 5), /* Byte In */ -}; - -/* IMR0 - Register bits */ -enum imr0_bits { - HR_MACIE = (1 << 0), /* */ - HR_RLCIE = (1 << 1), /* */ - HR_SPASIE = (1 << 2), /* */ - HR_ENDIE = (1 << 3), /* */ - HR_BOIE = (1 << 4), /* */ - HR_BIIE = (1 << 5), /* */ -}; - -/* ISR1 - Register bits */ -enum isr1_bits { - HR_IFC = (1 << 0), /* IFC asserted */ - HR_SRQ = (1 << 1), /* SRQ asserted */ - HR_MA = (1 << 2), /* My Address */ - HR_DCAS = (1 << 3), /* Device Clear active State */ - HR_APT = (1 << 4), /* Address pass Through */ - HR_UNC = (1 << 5), /* Unrecognized Command */ - HR_ERR = (1 << 6), /* Data Transmission Error */ - HR_GET = (1 << 7), /* Group execute Trigger */ -}; - -/* IMR1 - Register bits */ -enum imr1_bits { - HR_IFCIE = (1 << 0), /* */ - HR_SRQIE = (1 << 1), /* */ - HR_MAIE = (1 << 2), /* */ - HR_DCASIE = (1 << 3), /* */ - HR_APTIE = (1 << 4), /* */ - HR_UNCIE = (1 << 5), /* */ - HR_ERRIE = (1 << 6), /* */ - HR_GETIE = (1 << 7), /* */ -}; - -/* ADSR - Register bits */ -enum adsr_bits { - HR_ULPA = (1 << 0), /* Store last address LSB */ - HR_TA = (1 << 1), /* Talker Adressed */ - HR_LA = (1 << 2), /* Listener adressed */ - HR_TPAS = (1 << 3), /* talker primary address state */ - HR_LPAS = (1 << 4), /* listener " */ - HR_ATN = (1 << 5), /* ATN active */ - HR_LLO = (1 << 6), /* LLO active */ - HR_REM = (1 << 7), /* REM active */ -}; - -/* ADR - Register bits */ -enum adr_bits { - ADDRESS_MASK = 0x1f, /* mask to specify lower 5 bits for ADR */ - HR_DAT = (1 << 5), /* disable talker */ - HR_DAL = (1 << 6), /* disable listener */ - HR_EDPA = (1 << 7), /* enable dual primary addressing */ -}; - -enum bus_status_bits { - BSR_REN_BIT = 0x1, - BSR_IFC_BIT = 0x2, - BSR_SRQ_BIT = 0x4, - BSR_EOI_BIT = 0x8, - BSR_NRFD_BIT = 0x10, - BSR_NDAC_BIT = 0x20, - BSR_DAV_BIT = 0x40, - BSR_ATN_BIT = 0x80, -}; - -/*---------------------------------------------------------*/ -/* TMS 9914 Auxiliary Commands */ -/*---------------------------------------------------------*/ - -enum aux_cmd_bits { - AUX_CS = 0x80, /* set bit instead of clearing it, used with commands marked 'd' below */ - AUX_CHIP_RESET = 0x0, /* d Chip reset */ - AUX_INVAL = 0x1, /* release dac holdoff, invalid command byte */ - AUX_VAL = (AUX_INVAL | AUX_CS), /* release dac holdoff, valid command byte */ - AUX_RHDF = 0x2, /* X Release RFD holdoff */ - AUX_HLDA = 0x3, /* d holdoff on all data */ - AUX_HLDE = 0x4, /* d holdoff on EOI only */ - AUX_NBAF = 0x5, /* X Set new byte available false */ - AUX_FGET = 0x6, /* d force GET */ - AUX_RTL = 0x7, /* d return to local */ - AUX_SEOI = 0x8, /* X send EOI with next byte */ - AUX_LON = 0x9, /* d Listen only */ - AUX_TON = 0xa, /* d Talk only */ - AUX_GTS = 0xb, /* X goto standby */ - AUX_TCA = 0xc, /* X take control asynchronously */ - AUX_TCS = 0xd, /* X take " synchronously */ - AUX_RPP = 0xe, /* d Request parallel poll */ - AUX_SIC = 0xf, /* d send interface clear */ - AUX_SRE = 0x10, /* d send remote enable */ - AUX_RQC = 0x11, /* X request control */ - AUX_RLC = 0x12, /* X release control */ - AUX_DAI = 0x13, /* d disable all interrupts */ - AUX_PTS = 0x14, /* X pass through next secondary */ - AUX_STDL = 0x15, /* d short T1 delay */ - AUX_SHDW = 0x16, /* d shadow handshake */ - AUX_VSTDL = 0x17, /* d very short T1 delay (smj9914 extension) */ - AUX_RSV2 = 0x18, /* d request service bit 2 (smj9914 extension) */ -}; - -#endif //_TMS9914_H diff --git a/drivers/staging/gpib/include/tnt4882_registers.h b/drivers/staging/gpib/include/tnt4882_registers.h deleted file mode 100644 index d54c4cc61168..000000000000 --- a/drivers/staging/gpib/include/tnt4882_registers.h +++ /dev/null @@ -1,192 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -/*************************************************************************** - * copyright : (C) 2002, 2004 by Frank Mori Hess - ***************************************************************************/ - -#ifndef _TNT4882_REGISTERS_H -#define _TNT4882_REGISTERS_H - -// tnt4882 register offsets -enum { - ACCWR = 0x5, - // offset of auxiliary command register in 9914 mode - AUXCR = 0x6, - INTRT = 0x7, - // register number for auxiliary command register when swap bit is set (9914 mode) - SWAPPED_AUXCR = 0xa, - HSSEL = 0xd, // handshake select register - CNT2 = 0x9, - CNT3 = 0xb, - CFG = 0x10, - SASR = 0x1b, - IMR0 = 0x1d, - IMR3 = 0x12, - CNT0 = 0x14, - CNT1 = 0x16, - KEYREG = 0x17, // key control register (7210 mode only) - CSR = KEYREG, - FIFOB = 0x18, - FIFOA = 0x19, - CCR = 0x1a, // carry cycle register - CMDR = 0x1c, // command register - TIMER = 0x1e, // timer register - - STS1 = 0x10, // T488 Status Register 1 - STS2 = 0x1c, // T488 Status Register 2 - ISR0 = IMR0, - ISR3 = 0x1a, // T488 Interrupt Status Register 3 - BCR = 0x1f, // bus control/status register - BSR = BCR, -}; - -enum { - tnt_pagein_offset = 0x11, -}; - -/*============================================================*/ - -/* TURBO-488 registers bit definitions */ - -enum bus_control_status_bits { - BCSR_REN_BIT = 0x1, - BCSR_IFC_BIT = 0x2, - BCSR_SRQ_BIT = 0x4, - BCSR_EOI_BIT = 0x8, - BCSR_NRFD_BIT = 0x10, - BCSR_NDAC_BIT = 0x20, - BCSR_DAV_BIT = 0x40, - BCSR_ATN_BIT = 0x80, -}; - -/* CFG -- Configuration Register (write only) */ -enum cfg_bits { - TNT_COMMAND = 0x80, /* bytes are command bytes instead of data bytes - * (tnt4882 one-chip and newer only?) - */ - TNT_TLCHE = (1 << 6), /* halt transfer on imr0, imr1, or imr2 interrupt */ - TNT_IN = (1 << 5), /* transfer is GPIB read */ - TNT_A_B = (1 << 4), /* order to use fifos 1=fifo A first(big endian), - * 0=fifo b first(little endian) - */ - TNT_CCEN = (1 << 3), /* enable carry cycle */ - TNT_TMOE = (1 << 2), /* enable CPU bus time limit */ - TNT_TIM_BYTN = (1 << 1), /* tmot reg is: 1=125ns clocks, 0=num bytes */ - TNT_B_16BIT = (1 << 0), /* 1=FIFO is 16-bit register, 0=8-bit */ -}; - -/* CMDR -- Command Register */ -enum cmdr_bits { - CLRSC = 0x2, /* clear the system controller bit */ - SETSC = 0x3, /* set the system controller bit */ - GO = 0x4, /* start fifos */ - STOP = 0x8, /* stop fifos */ - RESET_FIFO = 0x10, /* reset the FIFOs */ - SOFT_RESET = 0x22, /* issue a software reset */ - HARD_RESET = 0x40 /* 500x only? */ -}; - -/* HSSEL -- handshake select register (write only) */ -enum hssel_bits { - TNT_ONE_CHIP_BIT = 0x1, - NODMA = 0x10, - TNT_GO2SIDS_BIT = 0x20, -}; - -/* IMR0 -- Interrupt Mode Register 0 */ -enum imr0_bits { - TNT_SYNCIE_BIT = 0x1, /* handshake sync */ - TNT_TOIE_BIT = 0x2, /* timeout */ - TNT_ATNIE_BIT = 0x4, /* ATN interrupt */ - TNT_IFCIE_BIT = 0x8, /* interface clear interrupt */ - TNT_BTO_BIT = 0x10, /* byte timeout */ - TNT_NLEN_BIT = 0x20, /* treat new line as EOS char */ - TNT_STBOIE_BIT = 0x40, /* status byte out */ - TNT_IMR0_ALWAYS_BITS = 0x80, /* always set this bit on write */ -}; - -/* ISR0 -- Interrupt Status Register 0 */ -enum isr0_bits { - TNT_SYNC_BIT = 0x1, /* handshake sync */ - TNT_TO_BIT = 0x2, /* timeout */ - TNT_ATNI_BIT = 0x4, /* ATN interrupt */ - TNT_IFCI_BIT = 0x8, /* interface clear interrupt */ - TNT_EOS_BIT = 0x10, /* end of string */ - TNT_NL_BIT = 0x20, /* new line receive */ - TNT_STBO_BIT = 0x40, /* status byte out */ - TNT_NBA_BIT = 0x80, /* new byte available */ -}; - -/* ISR3 -- Interrupt Status Register 3 (read only) */ -enum isr3_bits { - HR_DONE = (1 << 0), /* transfer done */ - HR_TLCI = (1 << 1), /* isr0, isr1, or isr2 interrupt asserted */ - HR_NEF = (1 << 2), /* NOT empty fifo */ - HR_NFF = (1 << 3), /* NOT full fifo */ - HR_STOP = (1 << 4), /* fifo empty or STOP command issued */ - HR_SRQI_CIC = (1 << 5), /* SRQ asserted and we are CIC (500x only?)*/ - HR_INTR = (1 << 7), /* isr3 interrupt active */ -}; - -enum keyreg_bits { - MSTD = 0x20, /* enable 350ns T1 delay */ -}; - -/* STS1 -- Status Register 1 (read only) */ -enum sts1_bits { - S_DONE = 0x80, /* DMA done */ - S_SC = 0x40, /* is system controller */ - S_IN = 0x20, /* DMA in (to memory) */ - S_DRQ = 0x10, /* DRQ line (for diagnostics) */ - S_STOP = 0x08, /* DMA stopped */ - S_NDAV = 0x04, /* inverse of DAV */ - S_HALT = 0x02, /* status of transfer machine */ - S_GSYNC = 0x01, /* indicates if GPIB is in sync w I/O */ -}; - -/* STS2 -- Status Register 2 */ -enum sts2_bits { - AFFN = (1 << 3), /* "A full FIFO NOT" (0=FIFO full) */ - AEFN = (1 << 2), /* "A empty FIFO NOT" (0=FIFO empty) */ - BFFN = (1 << 1), /* "B full FIFO NOT" (0=FIFO full) */ - BEFN = (1 << 0), /* "B empty FIFO NOT" (0=FIFO empty) */ -}; - -// Auxiliary commands -enum tnt4882_aux_cmds { - AUX_9914 = 0x15, // switch to 9914 mode - AUX_REQT = 0x18, - AUX_REQF = 0x19, - AUX_PAGEIN = 0x50, // page in alternate registers - AUX_HLDI = 0x51, // rfd holdoff immediately - AUX_CLEAR_END = 0x55, - AUX_7210 = 0x99, // switch to 7210 mode -}; - -enum tnt4882_aux_regs { - AUXRG = 0x40, - AUXRI = 0xe0, -}; - -enum auxg_bits { - /* no talking when no listeners bit (prevents bus errors when data written at wrong time) */ - NTNL_BIT = 0x8, - RPP2_BIT = 0x4, /* set/clear local rpp message */ - CHES_BIT = 0x1, /*clear holdoff on end select bit*/ -}; - -enum auxi_bits { - SISB = 0x1, // static interrupt bits (don't clear isr1, isr2 on read) - PP2 = 0x4, // ignore remote parallel poll configuration - USTD = 0x8, // ultra short (1100 nanosec) T1 delay -}; - -enum sasr_bits { - ACRDY_BIT = 0x4, /* acceptor ready state */ - ADHS_BIT = 0x8, /* acceptor data holdoff state */ - ANHS2_BIT = 0x10, /* acceptor not ready holdoff immediately state */ - ANHS1_BIT = 0x20, /* acceptor not ready holdoff state */ - AEHS_BIT = 0x40, /* acceptor end holdoff state */ -}; - -#endif // _TNT4882_REGISTERS_H diff --git a/drivers/staging/gpib/ines/Makefile b/drivers/staging/gpib/ines/Makefile deleted file mode 100644 index 88241f15ecea..000000000000 --- a/drivers/staging/gpib/ines/Makefile +++ /dev/null @@ -1,3 +0,0 @@ -obj-$(CONFIG_GPIB_INES) += ines_gpib.o - - diff --git a/drivers/staging/gpib/ines/ines.h b/drivers/staging/gpib/ines/ines.h deleted file mode 100644 index 6ad57e9a1216..000000000000 --- a/drivers/staging/gpib/ines/ines.h +++ /dev/null @@ -1,165 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -/*************************************************************************** - * Header for ines GPIB boards - * copyright : (C) 2002 by Frank Mori Hess - ***************************************************************************/ - -#ifndef _INES_GPIB_H -#define _INES_GPIB_H - -#include "nec7210.h" -#include "gpibP.h" -#include "plx9050.h" -#include "amcc5920.h" -#include "quancom_pci.h" -#include - -enum ines_pci_chip { - PCI_CHIP_NONE, - PCI_CHIP_PLX9050, - PCI_CHIP_AMCC5920, - PCI_CHIP_QUANCOM, - PCI_CHIP_QUICKLOGIC5030, -}; - -struct ines_priv { - struct nec7210_priv nec7210_priv; - struct pci_dev *pci_device; - // base address for plx9052 pci chip - unsigned long plx_iobase; - // base address for amcc5920 pci chip - unsigned long amcc_iobase; - unsigned int irq; - enum ines_pci_chip pci_chip_type; - u8 extend_mode_bits; -}; - -/* inb/outb wrappers */ -static inline unsigned int ines_inb(struct ines_priv *priv, unsigned int register_number) -{ - return inb(priv->nec7210_priv.iobase + - register_number * priv->nec7210_priv.offset); -} - -static inline void ines_outb(struct ines_priv *priv, unsigned int value, - unsigned int register_number) -{ - outb(value, priv->nec7210_priv.iobase + - register_number * priv->nec7210_priv.offset); -} - -enum ines_regs { - // read - FIFO_STATUS = 0x8, - ISR3 = 0x9, - ISR4 = 0xa, - IN_FIFO_COUNT = 0x10, - OUT_FIFO_COUNT = 0x11, - EXTEND_STATUS = 0xf, - - // write - XDMA_CONTROL = 0x8, - IMR3 = ISR3, - IMR4 = ISR4, - IN_FIFO_WATERMARK = IN_FIFO_COUNT, - OUT_FIFO_WATERMARK = OUT_FIFO_COUNT, - EXTEND_MODE = 0xf, - - // read-write - XFER_COUNT_LOWER = 0xb, - XFER_COUNT_UPPER = 0xc, - BUS_CONTROL_MONITOR = 0x13, -}; - -enum isr3_imr3_bits { - HW_TIMEOUT_BIT = 0x1, - XFER_COUNT_BIT = 0x2, - CMD_RECEIVED_BIT = 0x4, - TCT_RECEIVED_BIT = 0x8, - IFC_ACTIVE_BIT = 0x10, - ATN_ACTIVE_BIT = 0x20, - FIFO_ERROR_BIT = 0x40, -}; - -enum isr4_imr4_bits { - IN_FIFO_WATERMARK_BIT = 0x1, - OUT_FIFO_WATERMARK_BIT = 0x2, - IN_FIFO_FULL_BIT = 0x4, - OUT_FIFO_EMPTY_BIT = 0x8, - IN_FIFO_READY_BIT = 0x10, - OUT_FIFO_READY_BIT = 0x20, - IN_FIFO_EXIT_WATERMARK_BIT = 0x40, - OUT_FIFO_EXIT_WATERMARK_BIT = 0x80, -}; - -enum extend_mode_bits { - TR3_TRIG_ENABLE_BIT = 0x1, // enable generation of trigger pulse T/R3 pin - // clear message available status bit when chip writes byte with EOI true - MAV_ENABLE_BIT = 0x2, - EOS1_ENABLE_BIT = 0x4, // enable eos register 1 - EOS2_ENABLE_BIT = 0x8, // enable eos register 2 - EOIDIS_BIT = 0x10, // disable EOI interrupt when doing rfd holdoff on end? - XFER_COUNTER_ENABLE_BIT = 0x20, - XFER_COUNTER_OUTPUT_BIT = 0x40, // use counter for output, clear for input - // when xfer counter hits 0, assert EOI on write or RFD holdoff on read - LAST_BYTE_HANDLING_BIT = 0x80, -}; - -enum extend_status_bits { - OUTPUT_MESSAGE_IN_PROGRESS_BIT = 0x1, - SCSEL_BIT = 0x2, // statue of SCSEL pin - LISTEN_DISABLED = 0x4, - IN_FIFO_EMPTY_BIT = 0x8, - OUT_FIFO_FULL_BIT = 0x10, -}; - -// ines adds fifo enable bits to address mode register -enum ines_admr_bits { - IN_FIFO_ENABLE_BIT = 0x8, - OUT_FIFO_ENABLE_BIT = 0x4, -}; - -enum xdma_control_bits { - DMA_OUTPUT_BIT = 0x1, // use dma for output, clear for input - ENABLE_SYNC_DMA_BIT = 0x2, - DMA_ACCESS_EVERY_CYCLE = 0x4, // dma accesses fifo every cycle, clear for every other cycle - DMA_16BIT = 0x8, // clear for 8 bit transfers -}; - -enum bus_control_monitor_bits { - BCM_DAV_BIT = 0x1, - BCM_NRFD_BIT = 0x2, - BCM_NDAC_BIT = 0x4, - BCM_IFC_BIT = 0x8, - BCM_ATN_BIT = 0x10, - BCM_SRQ_BIT = 0x20, - BCM_REN_BIT = 0x40, - BCM_EOI_BIT = 0x80, -}; - -enum ines_aux_reg_bits { - INES_AUXD = 0x40, -}; - -enum ines_aux_cmds { - INES_RFD_HLD_IMMEDIATE = 0x4, - INES_AUX_CLR_OUT_FIFO = 0x5, - INES_AUX_CLR_IN_FIFO = 0x6, - INES_AUX_XMODE = 0xa, -}; - -enum ines_auxd_bits { - INES_FOLLOWING_T1_MASK = 0x3, - INES_FOLLOWING_T1_500ns = 0x0, - INES_FOLLOWING_T1_350ns = 0x1, - INES_FOLLOWING_T1_250ns = 0x2, - INES_INITIAL_TI_MASK = 0xc, - INES_INITIAL_T1_2000ns = 0x0, - INES_INITIAL_T1_1100ns = 0x4, - INES_INITIAL_T1_700ns = 0x8, - INES_T6_2us = 0x0, - INES_T6_50us = 0x10, -}; - -#endif // _INES_GPIB_H diff --git a/drivers/staging/gpib/ines/ines_gpib.c b/drivers/staging/gpib/ines/ines_gpib.c deleted file mode 100644 index a3cf846fd0f9..000000000000 --- a/drivers/staging/gpib/ines/ines_gpib.c +++ /dev/null @@ -1,1500 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 - -/*************************************************************************** - * copyright : (C) 1999 Axel Dziemba (axel.dziemba@ines.de) - * (C) 2002 by Frank Mori Hess - ***************************************************************************/ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -#define dev_fmt pr_fmt -#define DRV_NAME KBUILD_MODNAME - -#include "ines.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "gpib_pci_ids.h" - -MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("GPIB driver for Ines iGPIB 72010"); - -static irqreturn_t ines_interrupt(struct gpib_board *board); - -static int ines_line_status(const struct gpib_board *board) -{ - int status = VALID_ALL; - int bcm_bits; - struct ines_priv *ines_priv; - - ines_priv = board->private_data; - - bcm_bits = ines_inb(ines_priv, BUS_CONTROL_MONITOR); - - if (bcm_bits & BCM_REN_BIT) - status |= BUS_REN; - if (bcm_bits & BCM_IFC_BIT) - status |= BUS_IFC; - if (bcm_bits & BCM_SRQ_BIT) - status |= BUS_SRQ; - if (bcm_bits & BCM_EOI_BIT) - status |= BUS_EOI; - if (bcm_bits & BCM_NRFD_BIT) - status |= BUS_NRFD; - if (bcm_bits & BCM_NDAC_BIT) - status |= BUS_NDAC; - if (bcm_bits & BCM_DAV_BIT) - status |= BUS_DAV; - if (bcm_bits & BCM_ATN_BIT) - status |= BUS_ATN; - - return status; -} - -static void ines_set_xfer_counter(struct ines_priv *priv, unsigned int count) -{ - if (count > 0xffff) { - pr_err("bug! tried to set xfer counter > 0xffff\n"); - return; - } - ines_outb(priv, (count >> 8) & 0xff, XFER_COUNT_UPPER); - ines_outb(priv, count & 0xff, XFER_COUNT_LOWER); -} - -static int ines_t1_delay(struct gpib_board *board, unsigned int nano_sec) -{ - struct ines_priv *ines_priv = board->private_data; - struct nec7210_priv *nec_priv = &ines_priv->nec7210_priv; - unsigned int retval; - - retval = nec7210_t1_delay(board, nec_priv, nano_sec); - - if (nano_sec <= 250) { - write_byte(nec_priv, INES_AUXD | INES_FOLLOWING_T1_250ns | - INES_INITIAL_T1_2000ns, AUXMR); - retval = 250; - } else if (nano_sec <= 350) { - write_byte(nec_priv, INES_AUXD | INES_FOLLOWING_T1_350ns | - INES_INITIAL_T1_2000ns, AUXMR); - retval = 350; - } else { - write_byte(nec_priv, INES_AUXD | INES_FOLLOWING_T1_500ns | - INES_INITIAL_T1_2000ns, AUXMR); - retval = 500; - } - - return retval; -} - -static inline unsigned short num_in_fifo_bytes(struct ines_priv *ines_priv) -{ - return ines_inb(ines_priv, IN_FIFO_COUNT); -} - -static ssize_t pio_read(struct gpib_board *board, struct ines_priv *ines_priv, u8 *buffer, - size_t length, size_t *nbytes) -{ - ssize_t retval = 0; - unsigned int num_fifo_bytes, i; - struct nec7210_priv *nec_priv = &ines_priv->nec7210_priv; - - *nbytes = 0; - while (*nbytes < length) { - if (wait_event_interruptible(board->wait, - num_in_fifo_bytes(ines_priv) || - test_bit(RECEIVED_END_BN, &nec_priv->state) || - test_bit(DEV_CLEAR_BN, &nec_priv->state) || - test_bit(TIMO_NUM, &board->status))) - return -ERESTARTSYS; - - if (test_bit(TIMO_NUM, &board->status)) - return -ETIMEDOUT; - if (test_bit(DEV_CLEAR_BN, &nec_priv->state)) - return -EINTR; - - num_fifo_bytes = num_in_fifo_bytes(ines_priv); - if (num_fifo_bytes + *nbytes > length) - num_fifo_bytes = length - *nbytes; - - for (i = 0; i < num_fifo_bytes; i++) - buffer[(*nbytes)++] = read_byte(nec_priv, DIR); - if (test_bit(RECEIVED_END_BN, &nec_priv->state) && - num_in_fifo_bytes(ines_priv) == 0) - break; - if (need_resched()) - schedule(); - } - /* make sure RECEIVED_END is in sync */ - ines_interrupt(board); - return retval; -} - -static int ines_accel_read(struct gpib_board *board, u8 *buffer, - size_t length, int *end, size_t *bytes_read) -{ - ssize_t retval = 0; - struct ines_priv *ines_priv = board->private_data; - struct nec7210_priv *nec_priv = &ines_priv->nec7210_priv; - int counter_setting; - - *end = 0; - *bytes_read = 0; - if (length == 0) - return 0; - - clear_bit(DEV_CLEAR_BN, &nec_priv->state); - - write_byte(nec_priv, INES_RFD_HLD_IMMEDIATE, AUXMR); - - // clear in fifo - nec7210_set_reg_bits(nec_priv, ADMR, IN_FIFO_ENABLE_BIT, 0); - nec7210_set_reg_bits(nec_priv, ADMR, IN_FIFO_ENABLE_BIT, IN_FIFO_ENABLE_BIT); - - ines_priv->extend_mode_bits |= LAST_BYTE_HANDLING_BIT; - ines_priv->extend_mode_bits &= ~XFER_COUNTER_OUTPUT_BIT & ~XFER_COUNTER_ENABLE_BIT; - ines_outb(ines_priv, ines_priv->extend_mode_bits, EXTEND_MODE); - - counter_setting = length - num_in_fifo_bytes(ines_priv); - if (counter_setting > 0) { - ines_set_xfer_counter(ines_priv, length); - ines_priv->extend_mode_bits |= XFER_COUNTER_ENABLE_BIT; - ines_outb(ines_priv, ines_priv->extend_mode_bits, EXTEND_MODE); - - // holdoff on END - nec7210_set_handshake_mode(board, nec_priv, HR_HLDE); - /* release rfd holdoff */ - write_byte(nec_priv, AUX_FH, AUXMR); - } - - retval = pio_read(board, ines_priv, buffer, length, bytes_read); - ines_priv->extend_mode_bits &= ~XFER_COUNTER_ENABLE_BIT; - ines_outb(ines_priv, ines_priv->extend_mode_bits, EXTEND_MODE); - if (retval < 0) { - write_byte(nec_priv, INES_RFD_HLD_IMMEDIATE, AUXMR); - return retval; - } - if (test_and_clear_bit(RECEIVED_END_BN, &nec_priv->state)) - *end = 1; - - return retval; -} - -static const int out_fifo_size = 0xff; - -static inline unsigned short num_out_fifo_bytes(struct ines_priv *ines_priv) -{ - return ines_inb(ines_priv, OUT_FIFO_COUNT); -} - -static int ines_write_wait(struct gpib_board *board, struct ines_priv *ines_priv, - unsigned int fifo_threshold) -{ - struct nec7210_priv *nec_priv = &ines_priv->nec7210_priv; - - // wait until byte is ready to be sent - if (wait_event_interruptible(board->wait, - num_out_fifo_bytes(ines_priv) < fifo_threshold || - test_bit(BUS_ERROR_BN, &nec_priv->state) || - test_bit(DEV_CLEAR_BN, &nec_priv->state) || - test_bit(TIMO_NUM, &board->status))) - return -ERESTARTSYS; - - if (test_bit(BUS_ERROR_BN, &nec_priv->state)) - return -EIO; - if (test_bit(DEV_CLEAR_BN, &nec_priv->state)) - return -EINTR; - if (test_bit(TIMO_NUM, &board->status)) - return -ETIMEDOUT; - - return 0; -} - -static int ines_accel_write(struct gpib_board *board, u8 *buffer, size_t length, - int send_eoi, size_t *bytes_written) -{ - size_t count = 0; - ssize_t retval = 0; - struct ines_priv *ines_priv = board->private_data; - struct nec7210_priv *nec_priv = &ines_priv->nec7210_priv; - unsigned int num_bytes, i; - - *bytes_written = 0; - // clear out fifo - nec7210_set_reg_bits(nec_priv, ADMR, OUT_FIFO_ENABLE_BIT, 0); - nec7210_set_reg_bits(nec_priv, ADMR, OUT_FIFO_ENABLE_BIT, OUT_FIFO_ENABLE_BIT); - - ines_priv->extend_mode_bits |= XFER_COUNTER_OUTPUT_BIT; - ines_priv->extend_mode_bits &= ~XFER_COUNTER_ENABLE_BIT; - ines_priv->extend_mode_bits &= ~LAST_BYTE_HANDLING_BIT; - ines_outb(ines_priv, ines_priv->extend_mode_bits, EXTEND_MODE); - - ines_set_xfer_counter(ines_priv, length); - if (send_eoi) - ines_priv->extend_mode_bits |= LAST_BYTE_HANDLING_BIT; - ines_priv->extend_mode_bits |= XFER_COUNTER_ENABLE_BIT; - ines_outb(ines_priv, ines_priv->extend_mode_bits, EXTEND_MODE); - - while (count < length) { - retval = ines_write_wait(board, ines_priv, out_fifo_size); - if (retval < 0) - break; - - num_bytes = out_fifo_size - num_out_fifo_bytes(ines_priv); - if (num_bytes + count > length) - num_bytes = length - count; - for (i = 0; i < num_bytes; i++) - write_byte(nec_priv, buffer[count++], CDOR); - } - if (retval < 0) { - ines_priv->extend_mode_bits &= ~XFER_COUNTER_ENABLE_BIT; - ines_outb(ines_priv, ines_priv->extend_mode_bits, EXTEND_MODE); - *bytes_written = length - num_out_fifo_bytes(ines_priv); - return retval; - } - // wait last byte has been sent - retval = ines_write_wait(board, ines_priv, 1); - ines_priv->extend_mode_bits &= ~XFER_COUNTER_ENABLE_BIT; - ines_outb(ines_priv, ines_priv->extend_mode_bits, EXTEND_MODE); - *bytes_written = length - num_out_fifo_bytes(ines_priv); - - return retval; -} - -static irqreturn_t ines_pci_interrupt(int irq, void *arg) -{ - struct gpib_board *board = arg; - struct ines_priv *priv = board->private_data; - struct nec7210_priv *nec_priv = &priv->nec7210_priv; - - if (priv->pci_chip_type == PCI_CHIP_QUANCOM) { - if ((inb(nec_priv->iobase + - QUANCOM_IRQ_CONTROL_STATUS_REG) & - QUANCOM_IRQ_ASSERTED_BIT)) - outb(QUANCOM_IRQ_ENABLE_BIT, nec_priv->iobase + - QUANCOM_IRQ_CONTROL_STATUS_REG); - } - - return ines_interrupt(board); -} - -static irqreturn_t ines_interrupt(struct gpib_board *board) -{ - struct ines_priv *priv = board->private_data; - struct nec7210_priv *nec_priv = &priv->nec7210_priv; - unsigned int isr3_bits, isr4_bits; - unsigned long flags; - int wake = 0; - - spin_lock_irqsave(&board->spinlock, flags); - - nec7210_interrupt(board, nec_priv); - isr3_bits = ines_inb(priv, ISR3); - isr4_bits = ines_inb(priv, ISR4); - if (isr3_bits & IFC_ACTIVE_BIT) { - push_gpib_event(board, EVENT_IFC); - wake++; - } - if (isr3_bits & FIFO_ERROR_BIT) - dev_err(board->gpib_dev, "fifo error\n"); - if (isr3_bits & XFER_COUNT_BIT) - wake++; - - if (isr4_bits & (IN_FIFO_WATERMARK_BIT | IN_FIFO_FULL_BIT | OUT_FIFO_WATERMARK_BIT | - OUT_FIFO_EMPTY_BIT)) - wake++; - - if (wake) - wake_up_interruptible(&board->wait); - spin_unlock_irqrestore(&board->spinlock, flags); - return IRQ_HANDLED; -} - -static int ines_pci_attach(struct gpib_board *board, const struct gpib_board_config *config); -static int ines_pci_accel_attach(struct gpib_board *board, const struct gpib_board_config *config); -static int ines_isa_attach(struct gpib_board *board, const struct gpib_board_config *config); - -static void ines_pci_detach(struct gpib_board *board); -static void ines_isa_detach(struct gpib_board *board); - -enum ines_pci_vendor_ids { - PCI_VENDOR_ID_INES_QUICKLOGIC = 0x16da -}; - -enum ines_pci_device_ids { - PCI_DEVICE_ID_INES_GPIB_AMCC = 0x8507, - PCI_DEVICE_ID_INES_GPIB_QL5030 = 0x11, -}; - -enum ines_pci_subdevice_ids { - PCI_SUBDEVICE_ID_INES_GPIB = 0x1072 -}; - -static struct pci_device_id ines_pci_table[] = { - {PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_9050, PCI_VENDOR_ID_PLX, - PCI_SUBDEVICE_ID_INES_GPIB, 0, 0, 0}, - {PCI_VENDOR_ID_AMCC, PCI_DEVICE_ID_INES_GPIB_AMCC, PCI_VENDOR_ID_AMCC, - PCI_SUBDEVICE_ID_INES_GPIB, 0, 0, 0}, - {PCI_VENDOR_ID_INES_QUICKLOGIC, PCI_DEVICE_ID_INES_GPIB_QL5030, - PCI_VENDOR_ID_INES_QUICKLOGIC, PCI_DEVICE_ID_INES_GPIB_QL5030, 0, 0, 0}, - {PCI_DEVICE(PCI_VENDOR_ID_QUANCOM, PCI_DEVICE_ID_QUANCOM_GPIB)}, - {0} -}; -MODULE_DEVICE_TABLE(pci, ines_pci_table); - -struct ines_pci_id { - unsigned int vendor_id; - unsigned int device_id; - int subsystem_vendor_id; - int subsystem_device_id; - unsigned int gpib_region; - unsigned int io_offset; - enum ines_pci_chip pci_chip_type; -}; - -static struct ines_pci_id pci_ids[] = { - {.vendor_id = PCI_VENDOR_ID_PLX, - .device_id = PCI_DEVICE_ID_PLX_9050, - .subsystem_vendor_id = PCI_VENDOR_ID_PLX, - .subsystem_device_id = PCI_SUBDEVICE_ID_INES_GPIB, - .gpib_region = 2, - .io_offset = 1, - .pci_chip_type = PCI_CHIP_PLX9050, - }, - {.vendor_id = PCI_VENDOR_ID_AMCC, - .device_id = PCI_DEVICE_ID_INES_GPIB_AMCC, - .subsystem_vendor_id = PCI_VENDOR_ID_AMCC, - .subsystem_device_id = PCI_SUBDEVICE_ID_INES_GPIB, - .gpib_region = 1, - .io_offset = 1, - .pci_chip_type = PCI_CHIP_AMCC5920, - }, - {.vendor_id = PCI_VENDOR_ID_INES_QUICKLOGIC, - .device_id = PCI_DEVICE_ID_INES_GPIB_QL5030, - .subsystem_vendor_id = PCI_VENDOR_ID_INES_QUICKLOGIC, - .subsystem_device_id = PCI_DEVICE_ID_INES_GPIB_QL5030, - .gpib_region = 1, - .io_offset = 1, - .pci_chip_type = PCI_CHIP_QUICKLOGIC5030, - }, - {.vendor_id = PCI_VENDOR_ID_QUANCOM, - .device_id = PCI_DEVICE_ID_QUANCOM_GPIB, - .subsystem_vendor_id = -1, - .subsystem_device_id = -1, - .gpib_region = 0, - .io_offset = 4, - .pci_chip_type = PCI_CHIP_QUANCOM, - }, -}; - -static const int num_pci_chips = ARRAY_SIZE(pci_ids); - -// wrappers for interface functions -static int ines_read(struct gpib_board *board, u8 *buffer, size_t length, - int *end, size_t *bytes_read) -{ - struct ines_priv *priv = board->private_data; - struct nec7210_priv *nec_priv = &priv->nec7210_priv; - ssize_t retval; - int dummy; - - retval = nec7210_read(board, &priv->nec7210_priv, buffer, length, end, bytes_read); - if (retval < 0) { - write_byte(nec_priv, INES_RFD_HLD_IMMEDIATE, AUXMR); - - set_bit(RFD_HOLDOFF_BN, &nec_priv->state); - - nec7210_read_data_in(board, nec_priv, &dummy); - } - return retval; -} - -static int ines_write(struct gpib_board *board, u8 *buffer, size_t length, int send_eoi, - size_t *bytes_written) -{ - struct ines_priv *priv = board->private_data; - - return nec7210_write(board, &priv->nec7210_priv, buffer, length, send_eoi, bytes_written); -} - -static int ines_command(struct gpib_board *board, u8 *buffer, size_t length, size_t *bytes_written) -{ - struct ines_priv *priv = board->private_data; - - return nec7210_command(board, &priv->nec7210_priv, buffer, length, bytes_written); -} - -static int ines_take_control(struct gpib_board *board, int synchronous) -{ - struct ines_priv *priv = board->private_data; - - return nec7210_take_control(board, &priv->nec7210_priv, synchronous); -} - -static int ines_go_to_standby(struct gpib_board *board) -{ - struct ines_priv *priv = board->private_data; - - return nec7210_go_to_standby(board, &priv->nec7210_priv); -} - -static int ines_request_system_control(struct gpib_board *board, int request_control) -{ - struct ines_priv *priv = board->private_data; - - return nec7210_request_system_control(board, &priv->nec7210_priv, request_control); -} - -static void ines_interface_clear(struct gpib_board *board, int assert) -{ - struct ines_priv *priv = board->private_data; - - nec7210_interface_clear(board, &priv->nec7210_priv, assert); -} - -static void ines_remote_enable(struct gpib_board *board, int enable) -{ - struct ines_priv *priv = board->private_data; - - nec7210_remote_enable(board, &priv->nec7210_priv, enable); -} - -static int ines_enable_eos(struct gpib_board *board, u8 eos_byte, int compare_8_bits) -{ - struct ines_priv *priv = board->private_data; - - return nec7210_enable_eos(board, &priv->nec7210_priv, eos_byte, compare_8_bits); -} - -static void ines_disable_eos(struct gpib_board *board) -{ - struct ines_priv *priv = board->private_data; - - nec7210_disable_eos(board, &priv->nec7210_priv); -} - -static unsigned int ines_update_status(struct gpib_board *board, unsigned int clear_mask) -{ - struct ines_priv *priv = board->private_data; - - return nec7210_update_status(board, &priv->nec7210_priv, clear_mask); -} - -static int ines_primary_address(struct gpib_board *board, unsigned int address) -{ - struct ines_priv *priv = board->private_data; - - return nec7210_primary_address(board, &priv->nec7210_priv, address); -} - -static int ines_secondary_address(struct gpib_board *board, unsigned int address, int enable) -{ - struct ines_priv *priv = board->private_data; - - return nec7210_secondary_address(board, &priv->nec7210_priv, address, enable); -} - -static int ines_parallel_poll(struct gpib_board *board, u8 *result) -{ - struct ines_priv *priv = board->private_data; - - return nec7210_parallel_poll(board, &priv->nec7210_priv, result); -} - -static void ines_parallel_poll_configure(struct gpib_board *board, u8 config) -{ - struct ines_priv *priv = board->private_data; - - nec7210_parallel_poll_configure(board, &priv->nec7210_priv, config); -} - -static void ines_parallel_poll_response(struct gpib_board *board, int ist) -{ - struct ines_priv *priv = board->private_data; - - nec7210_parallel_poll_response(board, &priv->nec7210_priv, ist); -} - -static void ines_serial_poll_response(struct gpib_board *board, u8 status) -{ - struct ines_priv *priv = board->private_data; - - nec7210_serial_poll_response(board, &priv->nec7210_priv, status); -} - -static u8 ines_serial_poll_status(struct gpib_board *board) -{ - struct ines_priv *priv = board->private_data; - - return nec7210_serial_poll_status(board, &priv->nec7210_priv); -} - -static void ines_return_to_local(struct gpib_board *board) -{ - struct ines_priv *priv = board->private_data; - - nec7210_return_to_local(board, &priv->nec7210_priv); -} - -static struct gpib_interface ines_pci_unaccel_interface = { - .name = "ines_pci_unaccel", - .attach = ines_pci_attach, - .detach = ines_pci_detach, - .read = ines_read, - .write = ines_write, - .command = ines_command, - .take_control = ines_take_control, - .go_to_standby = ines_go_to_standby, - .request_system_control = ines_request_system_control, - .interface_clear = ines_interface_clear, - .remote_enable = ines_remote_enable, - .enable_eos = ines_enable_eos, - .disable_eos = ines_disable_eos, - .parallel_poll = ines_parallel_poll, - .parallel_poll_configure = ines_parallel_poll_configure, - .parallel_poll_response = ines_parallel_poll_response, - .local_parallel_poll_mode = NULL, // XXX - .line_status = ines_line_status, - .update_status = ines_update_status, - .primary_address = ines_primary_address, - .secondary_address = ines_secondary_address, - .serial_poll_response = ines_serial_poll_response, - .serial_poll_status = ines_serial_poll_status, - .t1_delay = ines_t1_delay, - .return_to_local = ines_return_to_local, -}; - -static struct gpib_interface ines_pci_interface = { - .name = "ines_pci", - .attach = ines_pci_accel_attach, - .detach = ines_pci_detach, - .read = ines_accel_read, - .write = ines_accel_write, - .command = ines_command, - .take_control = ines_take_control, - .go_to_standby = ines_go_to_standby, - .request_system_control = ines_request_system_control, - .interface_clear = ines_interface_clear, - .remote_enable = ines_remote_enable, - .enable_eos = ines_enable_eos, - .disable_eos = ines_disable_eos, - .parallel_poll = ines_parallel_poll, - .parallel_poll_configure = ines_parallel_poll_configure, - .parallel_poll_response = ines_parallel_poll_response, - .local_parallel_poll_mode = NULL, // XXX - .line_status = ines_line_status, - .update_status = ines_update_status, - .primary_address = ines_primary_address, - .secondary_address = ines_secondary_address, - .serial_poll_response = ines_serial_poll_response, - .serial_poll_status = ines_serial_poll_status, - .t1_delay = ines_t1_delay, - .return_to_local = ines_return_to_local, -}; - -static struct gpib_interface ines_pci_accel_interface = { - .name = "ines_pci_accel", - .attach = ines_pci_accel_attach, - .detach = ines_pci_detach, - .read = ines_accel_read, - .write = ines_accel_write, - .command = ines_command, - .take_control = ines_take_control, - .go_to_standby = ines_go_to_standby, - .request_system_control = ines_request_system_control, - .interface_clear = ines_interface_clear, - .remote_enable = ines_remote_enable, - .enable_eos = ines_enable_eos, - .disable_eos = ines_disable_eos, - .parallel_poll = ines_parallel_poll, - .parallel_poll_configure = ines_parallel_poll_configure, - .parallel_poll_response = ines_parallel_poll_response, - .local_parallel_poll_mode = NULL, // XXX - .line_status = ines_line_status, - .update_status = ines_update_status, - .primary_address = ines_primary_address, - .secondary_address = ines_secondary_address, - .serial_poll_response = ines_serial_poll_response, - .serial_poll_status = ines_serial_poll_status, - .t1_delay = ines_t1_delay, - .return_to_local = ines_return_to_local, -}; - -static struct gpib_interface ines_isa_interface = { - .name = "ines_isa", - .attach = ines_isa_attach, - .detach = ines_isa_detach, - .read = ines_accel_read, - .write = ines_accel_write, - .command = ines_command, - .take_control = ines_take_control, - .go_to_standby = ines_go_to_standby, - .request_system_control = ines_request_system_control, - .interface_clear = ines_interface_clear, - .remote_enable = ines_remote_enable, - .enable_eos = ines_enable_eos, - .disable_eos = ines_disable_eos, - .parallel_poll = ines_parallel_poll, - .parallel_poll_configure = ines_parallel_poll_configure, - .parallel_poll_response = ines_parallel_poll_response, - .local_parallel_poll_mode = NULL, // XXX - .line_status = ines_line_status, - .update_status = ines_update_status, - .primary_address = ines_primary_address, - .secondary_address = ines_secondary_address, - .serial_poll_response = ines_serial_poll_response, - .serial_poll_status = ines_serial_poll_status, - .t1_delay = ines_t1_delay, - .return_to_local = ines_return_to_local, -}; - -static int ines_allocate_private(struct gpib_board *board) -{ - struct ines_priv *priv; - - board->private_data = kmalloc(sizeof(struct ines_priv), GFP_KERNEL); - if (!board->private_data) - return -1; - priv = board->private_data; - memset(priv, 0, sizeof(struct ines_priv)); - init_nec7210_private(&priv->nec7210_priv); - return 0; -} - -static void ines_free_private(struct gpib_board *board) -{ - kfree(board->private_data); - board->private_data = NULL; -} - -static int ines_generic_attach(struct gpib_board *board) -{ - struct ines_priv *ines_priv; - struct nec7210_priv *nec_priv; - - board->status = 0; - - if (ines_allocate_private(board)) - return -ENOMEM; - ines_priv = board->private_data; - nec_priv = &ines_priv->nec7210_priv; - nec_priv->read_byte = nec7210_ioport_read_byte; - nec_priv->write_byte = nec7210_ioport_write_byte; - nec_priv->offset = 1; - nec_priv->type = IGPIB7210; - ines_priv->pci_chip_type = PCI_CHIP_NONE; - - return 0; -} - -static void ines_online(struct ines_priv *ines_priv, const struct gpib_board *board, int use_accel) -{ - struct nec7210_priv *nec_priv = &ines_priv->nec7210_priv; - - /* ines doesn't seem to use internal count register */ - write_byte(nec_priv, ICR | 0, AUXMR); - - write_byte(nec_priv, INES_AUX_XMODE, AUXMR); - write_byte(nec_priv, INES_RFD_HLD_IMMEDIATE, AUXMR); - - set_bit(RFD_HOLDOFF_BN, &nec_priv->state); - - write_byte(nec_priv, INES_AUXD | 0, AUXMR); - ines_outb(ines_priv, 0, XDMA_CONTROL); - ines_priv->extend_mode_bits = 0; - ines_outb(ines_priv, ines_priv->extend_mode_bits, EXTEND_MODE); - if (use_accel) { - ines_outb(ines_priv, 0x80, OUT_FIFO_WATERMARK); - ines_outb(ines_priv, 0x80, IN_FIFO_WATERMARK); - ines_outb(ines_priv, IFC_ACTIVE_BIT | ATN_ACTIVE_BIT | - FIFO_ERROR_BIT | XFER_COUNT_BIT, IMR3); - ines_outb(ines_priv, IN_FIFO_WATERMARK_BIT | IN_FIFO_FULL_BIT | - OUT_FIFO_WATERMARK_BIT | OUT_FIFO_EMPTY_BIT, IMR4); - } else { - nec7210_set_reg_bits(nec_priv, ADMR, IN_FIFO_ENABLE_BIT | OUT_FIFO_ENABLE_BIT, 0); - ines_outb(ines_priv, IFC_ACTIVE_BIT | FIFO_ERROR_BIT, IMR3); - ines_outb(ines_priv, 0, IMR4); - } - - nec7210_board_online(nec_priv, board); - if (use_accel) - nec7210_set_reg_bits(nec_priv, IMR1, HR_DOIE | HR_DIIE, 0); -} - -static int ines_common_pci_attach(struct gpib_board *board, const struct gpib_board_config *config) -{ - struct ines_priv *ines_priv; - struct nec7210_priv *nec_priv; - int isr_flags = 0; - int retval; - struct ines_pci_id found_id; - unsigned int i; - struct pci_dev *pdev; - - memset(&found_id, 0, sizeof(found_id)); - - retval = ines_generic_attach(board); - if (retval) - return retval; - - ines_priv = board->private_data; - nec_priv = &ines_priv->nec7210_priv; - - // find board - ines_priv->pci_device = NULL; - for (i = 0; i < num_pci_chips && !ines_priv->pci_device; i++) { - pdev = NULL; - do { - if (pci_ids[i].subsystem_vendor_id >= 0 && - pci_ids[i].subsystem_device_id >= 0) - pdev = pci_get_subsys(pci_ids[i].vendor_id, pci_ids[i].device_id, - pci_ids[i].subsystem_vendor_id, - pci_ids[i].subsystem_device_id, pdev); - else - pdev = pci_get_device(pci_ids[i].vendor_id, pci_ids[i].device_id, - pdev); - if (!pdev) - break; - if (config->pci_bus >= 0 && config->pci_bus != pdev->bus->number) - continue; - if (config->pci_slot >= 0 && config->pci_slot != PCI_SLOT(pdev->devfn)) - continue; - found_id = pci_ids[i]; - ines_priv->pci_device = pdev; - break; - } while (1); - } - if (!ines_priv->pci_device) { - dev_err(board->gpib_dev, "could not find ines PCI board\n"); - return -1; - } - - if (pci_enable_device(ines_priv->pci_device)) { - dev_err(board->gpib_dev, "error enabling pci device\n"); - return -1; - } - - if (pci_request_regions(ines_priv->pci_device, DRV_NAME)) - return -1; - nec_priv->iobase = pci_resource_start(ines_priv->pci_device, - found_id.gpib_region); - - ines_priv->pci_chip_type = found_id.pci_chip_type; - nec_priv->offset = found_id.io_offset; - switch (ines_priv->pci_chip_type) { - case PCI_CHIP_PLX9050: - ines_priv->plx_iobase = pci_resource_start(ines_priv->pci_device, 1); - break; - case PCI_CHIP_AMCC5920: - ines_priv->amcc_iobase = pci_resource_start(ines_priv->pci_device, 0); - break; - case PCI_CHIP_QUANCOM: - break; - case PCI_CHIP_QUICKLOGIC5030: - break; - default: - dev_err(board->gpib_dev, "unspecified chip type? (bug)\n"); - nec_priv->iobase = 0; - pci_release_regions(ines_priv->pci_device); - return -1; - } - - nec7210_board_reset(nec_priv, board); -#ifdef QUANCOM_PCI - if (ines_priv->pci_chip_type == PCI_CHIP_QUANCOM) { - /* change interrupt polarity */ - nec_priv->auxb_bits |= HR_INV; - ines_outb(ines_priv, nec_priv->auxb_bits, AUXMR); - } -#endif - isr_flags |= IRQF_SHARED; - if (request_irq(ines_priv->pci_device->irq, ines_pci_interrupt, isr_flags, - DRV_NAME, board)) { - dev_err(board->gpib_dev, "can't request IRQ %d\n", ines_priv->pci_device->irq); - return -1; - } - ines_priv->irq = ines_priv->pci_device->irq; - - // enable interrupts on pci chip - switch (ines_priv->pci_chip_type) { - case PCI_CHIP_PLX9050: - outl(PLX9050_LINTR1_EN_BIT | PLX9050_LINTR1_POLARITY_BIT | PLX9050_PCI_INTR_EN_BIT, - ines_priv->plx_iobase + PLX9050_INTCSR_REG); - break; - case PCI_CHIP_AMCC5920: - { - static const int region = 1; - static const int num_wait_states = 7; - u32 bits; - - bits = amcc_prefetch_bits(region, PREFETCH_DISABLED); - bits |= amcc_PTADR_mode_bit(region); - bits |= amcc_disable_write_fifo_bit(region); - bits |= amcc_wait_state_bits(region, num_wait_states); - outl(bits, ines_priv->amcc_iobase + AMCC_PASS_THRU_REG); - outl(AMCC_ADDON_INTR_ENABLE_BIT, ines_priv->amcc_iobase + AMCC_INTCS_REG); - } - break; - case PCI_CHIP_QUANCOM: - outb(QUANCOM_IRQ_ENABLE_BIT, nec_priv->iobase + - QUANCOM_IRQ_CONTROL_STATUS_REG); - break; - case PCI_CHIP_QUICKLOGIC5030: - break; - default: - dev_err(board->gpib_dev, "unspecified chip type? (bug)\n"); - return -1; - } - - return 0; -} - -static int ines_pci_attach(struct gpib_board *board, const struct gpib_board_config *config) -{ - struct ines_priv *ines_priv; - int retval; - - retval = ines_common_pci_attach(board, config); - if (retval < 0) - return retval; - - ines_priv = board->private_data; - ines_online(ines_priv, board, 0); - - return 0; -} - -static int ines_pci_accel_attach(struct gpib_board *board, const struct gpib_board_config *config) -{ - struct ines_priv *ines_priv; - int retval; - - retval = ines_common_pci_attach(board, config); - if (retval < 0) - return retval; - - ines_priv = board->private_data; - ines_online(ines_priv, board, 1); - - return 0; -} - -static const int ines_isa_iosize = 0x20; - -static int ines_isa_attach(struct gpib_board *board, const struct gpib_board_config *config) -{ - struct ines_priv *ines_priv; - struct nec7210_priv *nec_priv; - int isr_flags = 0; - int retval; - - retval = ines_generic_attach(board); - if (retval) - return retval; - - ines_priv = board->private_data; - nec_priv = &ines_priv->nec7210_priv; - - if (!request_region(config->ibbase, ines_isa_iosize, DRV_NAME)) { - dev_err(board->gpib_dev, "ioports at 0x%x already in use\n", - config->ibbase); - return -EBUSY; - } - nec_priv->iobase = config->ibbase; - nec_priv->offset = 1; - nec7210_board_reset(nec_priv, board); - if (request_irq(config->ibirq, ines_pci_interrupt, isr_flags, DRV_NAME, board)) { - dev_err(board->gpib_dev, "failed to allocate IRQ %d\n", config->ibirq); - return -1; - } - ines_priv->irq = config->ibirq; - ines_online(ines_priv, board, 1); - return 0; -} - -static void ines_pci_detach(struct gpib_board *board) -{ - struct ines_priv *ines_priv = board->private_data; - struct nec7210_priv *nec_priv; - - if (ines_priv) { - nec_priv = &ines_priv->nec7210_priv; - if (ines_priv->irq) { - // disable interrupts - switch (ines_priv->pci_chip_type) { - case PCI_CHIP_AMCC5920: - if (ines_priv->plx_iobase) - outl(0, ines_priv->plx_iobase + PLX9050_INTCSR_REG); - break; - case PCI_CHIP_QUANCOM: - if (nec_priv->iobase) - outb(0, nec_priv->iobase + - QUANCOM_IRQ_CONTROL_STATUS_REG); - break; - default: - break; - } - free_irq(ines_priv->irq, board); - } - if (nec_priv->iobase) { - nec7210_board_reset(nec_priv, board); - pci_release_regions(ines_priv->pci_device); - } - if (ines_priv->pci_device) - pci_dev_put(ines_priv->pci_device); - } - ines_free_private(board); -} - -static void ines_isa_detach(struct gpib_board *board) -{ - struct ines_priv *ines_priv = board->private_data; - struct nec7210_priv *nec_priv; - - if (ines_priv) { - nec_priv = &ines_priv->nec7210_priv; - if (ines_priv->irq) - free_irq(ines_priv->irq, board); - if (nec_priv->iobase) { - nec7210_board_reset(nec_priv, board); - release_region(nec_priv->iobase, ines_isa_iosize); - } - } - ines_free_private(board); -} - -static int ines_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) -{ - return 0; -} - -static struct pci_driver ines_pci_driver = { - .name = "ines_gpib", - .id_table = ines_pci_table, - .probe = &ines_pci_probe -}; - -#ifdef CONFIG_GPIB_PCMCIA - -#include -#include -#include -#include - -#include -#include -#include - -static const int ines_pcmcia_iosize = 0x20; - -/* - * The event() function is this driver's Card Services event handler. - * It will be called by Card Services when an appropriate card status - * event is received. The config() and release() entry points are - * used to configure or release a socket, in response to card insertion - * and ejection events. They are invoked from the gpib event - * handler. - */ - -static int ines_gpib_config(struct pcmcia_device *link); -static void ines_gpib_release(struct pcmcia_device *link); -static int ines_pcmcia_attach(struct gpib_board *board, const struct gpib_board_config *config); -static int ines_pcmcia_accel_attach(struct gpib_board *board, - const struct gpib_board_config *config); -static void ines_pcmcia_detach(struct gpib_board *board); -static int ines_common_pcmcia_attach(struct gpib_board *board); -/* - * A linked list of "instances" of the gpib device. Each actual - * PCMCIA card corresponds to one device instance, and is described - * by one dev_link_t structure (defined in ds.h). - * - * You may not want to use a linked list for this -- for example, the - * memory card driver uses an array of dev_link_t pointers, where minor - * device numbers are used to derive the corresponding array index. - */ - -static struct pcmcia_device *curr_dev; - -/* - * A dev_link_t structure has fields for most things that are needed - * to keep track of a socket, but there will usually be some device - * specific information that also needs to be kept track of. The - * 'priv' pointer in a dev_link_t structure can be used to point to - * a device-specific private data structure, like this. - * - * A driver needs to provide a dev_node_t structure for each device - * on a card. In some cases, there is only one device per card (for - * example, ethernet cards, modems). In other cases, there may be - * many actual or logical devices (SCSI adapters, memory cards with - * multiple partitions). The dev_node_t structures need to be kept - * in a linked list starting at the 'dev' field of a dev_link_t - * structure. We allocate them in the card's private data structure, - * because they generally can't be allocated dynamically. - */ - -struct local_info { - struct pcmcia_device *p_dev; - struct gpib_board *dev; - u_short manfid; - u_short cardid; -}; - -/* - * gpib_attach() creates an "instance" of the driver, allocating - * local data structures for one device. The device is registered - * with Card Services. - * - * The dev_link structure is initialized, but we don't actually - * configure the card at this point -- we wait until we receive a - * card insertion event. - */ -static int ines_gpib_probe(struct pcmcia_device *link) -{ - struct local_info *info; - -// int ret, i; - - /* Allocate space for private device-specific data */ - info = kzalloc(sizeof(*info), GFP_KERNEL); - if (!info) - return -ENOMEM; - - info->p_dev = link; - link->priv = info; - - /* The io structure describes IO port mapping */ - link->resource[0]->end = 32; - link->resource[0]->flags &= ~IO_DATA_PATH_WIDTH; - link->resource[0]->flags |= IO_DATA_PATH_WIDTH_8; - link->io_lines = 5; - - /* General socket configuration */ - link->config_flags = CONF_ENABLE_IRQ | CONF_AUTO_SET_IO; - - /* Register with Card Services */ - curr_dev = link; - return ines_gpib_config(link); -} - -/* - * This deletes a driver "instance". The device is de-registered - * with Card Services. If it has been released, all local data - * structures are freed. Otherwise, the structures will be freed - * when the device is released. - */ -static void ines_gpib_remove(struct pcmcia_device *link) -{ - struct local_info *info = link->priv; - //struct struct gpib_board *dev = info->dev; - - if (info->dev) - ines_pcmcia_detach(info->dev); - ines_gpib_release(link); - - //free_netdev(dev); - kfree(info); -} - -static int ines_gpib_config_iteration(struct pcmcia_device *link, void *priv_data) -{ - return pcmcia_request_io(link); -} - -/* - * gpib_config() is scheduled to run after a CARD_INSERTION event - * is received, to configure the PCMCIA socket, and to make the - * device available to the system. - */ -static int ines_gpib_config(struct pcmcia_device *link) -{ - int retval; - void __iomem *virt; - - retval = pcmcia_loop_config(link, &ines_gpib_config_iteration, NULL); - if (retval) { - dev_warn(&link->dev, "no configuration found\n"); - ines_gpib_release(link); - return -ENODEV; - } - - dev_dbg(&link->dev, "ines_cs: manufacturer: 0x%x card: 0x%x\n", - link->manf_id, link->card_id); - - /* - * for the ines card we have to setup the configuration registers in - * attribute memory here - */ - link->resource[2]->flags |= WIN_MEMORY_TYPE_AM | WIN_DATA_WIDTH_8 | WIN_ENABLE; - link->resource[2]->end = 0x1000; - retval = pcmcia_request_window(link, link->resource[2], 250); - if (retval) { - dev_warn(&link->dev, "pcmcia_request_window failed\n"); - ines_gpib_release(link); - return -ENODEV; - } - retval = pcmcia_map_mem_page(link, link->resource[2], 0); - if (retval) { - dev_warn(&link->dev, "pcmcia_map_mem_page failed\n"); - ines_gpib_release(link); - return -ENODEV; - } - virt = ioremap(link->resource[2]->start, resource_size(link->resource[2])); - writeb((link->resource[2]->start >> 2) & 0xff, virt + 0xf0); // IOWindow base - iounmap(virt); - - /* - * This actually configures the PCMCIA socket -- setting up - * the I/O windows and the interrupt mapping. - */ - retval = pcmcia_enable_device(link); - if (retval) { - ines_gpib_release(link); - return -ENODEV; - } - return 0; -} /* gpib_config */ - -/* - * After a card is removed, gpib_release() will unregister the net - * device, and release the PCMCIA configuration. If the device is - * still open, this will be postponed until it is closed. - */ - -static void ines_gpib_release(struct pcmcia_device *link) -{ - pcmcia_disable_device(link); -} /* gpib_release */ - -static int ines_gpib_suspend(struct pcmcia_device *link) -{ - //struct local_info *info = link->priv; - //struct struct gpib_board *dev = info->dev; - - if (link->open) - dev_err(&link->dev, "Device still open\n"); - //netif_device_detach(dev); - - return 0; -} - -static int ines_gpib_resume(struct pcmcia_device *link) -{ - //struct local_info_t *info = link->priv; - //struct struct gpib_board *dev = info->dev; - - /*if (link->open) { - * ni_gpib_probe(dev); / really? - * //netif_device_attach(dev); - *} - */ - return ines_gpib_config(link); -} - -static struct pcmcia_device_id ines_pcmcia_ids[] = { - PCMCIA_DEVICE_MANF_CARD(0x01b4, 0x4730), - PCMCIA_DEVICE_NULL -}; -MODULE_DEVICE_TABLE(pcmcia, ines_pcmcia_ids); - -static struct pcmcia_driver ines_gpib_cs_driver = { - .owner = THIS_MODULE, - .name = "ines_gpib_cs", - .id_table = ines_pcmcia_ids, - .probe = ines_gpib_probe, - .remove = ines_gpib_remove, - .suspend = ines_gpib_suspend, - .resume = ines_gpib_resume, -}; - -static void ines_pcmcia_cleanup_module(void) -{ - pcmcia_unregister_driver(&ines_gpib_cs_driver); -} - -static struct gpib_interface ines_pcmcia_unaccel_interface = { - .name = "ines_pcmcia_unaccel", - .attach = ines_pcmcia_attach, - .detach = ines_pcmcia_detach, - .read = ines_read, - .write = ines_write, - .command = ines_command, - .take_control = ines_take_control, - .go_to_standby = ines_go_to_standby, - .request_system_control = ines_request_system_control, - .interface_clear = ines_interface_clear, - .remote_enable = ines_remote_enable, - .enable_eos = ines_enable_eos, - .disable_eos = ines_disable_eos, - .parallel_poll = ines_parallel_poll, - .parallel_poll_configure = ines_parallel_poll_configure, - .parallel_poll_response = ines_parallel_poll_response, - .local_parallel_poll_mode = NULL, // XXX - .line_status = ines_line_status, - .update_status = ines_update_status, - .primary_address = ines_primary_address, - .secondary_address = ines_secondary_address, - .serial_poll_response = ines_serial_poll_response, - .serial_poll_status = ines_serial_poll_status, - .t1_delay = ines_t1_delay, - .return_to_local = ines_return_to_local, -}; - -static struct gpib_interface ines_pcmcia_accel_interface = { - .name = "ines_pcmcia_accel", - .attach = ines_pcmcia_accel_attach, - .detach = ines_pcmcia_detach, - .read = ines_accel_read, - .write = ines_accel_write, - .command = ines_command, - .take_control = ines_take_control, - .go_to_standby = ines_go_to_standby, - .request_system_control = ines_request_system_control, - .interface_clear = ines_interface_clear, - .remote_enable = ines_remote_enable, - .enable_eos = ines_enable_eos, - .disable_eos = ines_disable_eos, - .parallel_poll = ines_parallel_poll, - .parallel_poll_configure = ines_parallel_poll_configure, - .parallel_poll_response = ines_parallel_poll_response, - .local_parallel_poll_mode = NULL, // XXX - .line_status = ines_line_status, - .update_status = ines_update_status, - .primary_address = ines_primary_address, - .secondary_address = ines_secondary_address, - .serial_poll_response = ines_serial_poll_response, - .serial_poll_status = ines_serial_poll_status, - .t1_delay = ines_t1_delay, - .return_to_local = ines_return_to_local, -}; - -static struct gpib_interface ines_pcmcia_interface = { - .name = "ines_pcmcia", - .attach = ines_pcmcia_accel_attach, - .detach = ines_pcmcia_detach, - .read = ines_accel_read, - .write = ines_accel_write, - .command = ines_command, - .take_control = ines_take_control, - .go_to_standby = ines_go_to_standby, - .request_system_control = ines_request_system_control, - .interface_clear = ines_interface_clear, - .remote_enable = ines_remote_enable, - .enable_eos = ines_enable_eos, - .disable_eos = ines_disable_eos, - .parallel_poll = ines_parallel_poll, - .parallel_poll_configure = ines_parallel_poll_configure, - .parallel_poll_response = ines_parallel_poll_response, - .local_parallel_poll_mode = NULL, // XXX - .line_status = ines_line_status, - .update_status = ines_update_status, - .primary_address = ines_primary_address, - .secondary_address = ines_secondary_address, - .serial_poll_response = ines_serial_poll_response, - .serial_poll_status = ines_serial_poll_status, - .t1_delay = ines_t1_delay, - .return_to_local = ines_return_to_local, -}; - -static irqreturn_t ines_pcmcia_interrupt(int irq, void *arg) -{ - struct gpib_board *board = arg; - - return ines_interrupt(board); -} - -static int ines_common_pcmcia_attach(struct gpib_board *board) -{ - struct ines_priv *ines_priv; - struct nec7210_priv *nec_priv; - int retval; - - if (!curr_dev) { - dev_err(board->gpib_dev, "no ines pcmcia cards found\n"); - return -1; - } - - retval = ines_generic_attach(board); - if (retval) - return retval; - - ines_priv = board->private_data; - nec_priv = &ines_priv->nec7210_priv; - - if (!request_region(curr_dev->resource[0]->start, - resource_size(curr_dev->resource[0]), DRV_NAME)) { - dev_err(board->gpib_dev, "ioports at 0x%lx already in use\n", - (unsigned long)(curr_dev->resource[0]->start)); - return -1; - } - - nec_priv->iobase = curr_dev->resource[0]->start; - - nec7210_board_reset(nec_priv, board); - - if (request_irq(curr_dev->irq, ines_pcmcia_interrupt, IRQF_SHARED, - "pcmcia-gpib", board)) { - dev_err(board->gpib_dev, "can't request IRQ %d\n", curr_dev->irq); - return -1; - } - ines_priv->irq = curr_dev->irq; - - return 0; -} - -static int ines_pcmcia_attach(struct gpib_board *board, const struct gpib_board_config *config) -{ - struct ines_priv *ines_priv; - int retval; - - retval = ines_common_pcmcia_attach(board); - if (retval < 0) - return retval; - - ines_priv = board->private_data; - ines_online(ines_priv, board, 0); - - return 0; -} - -static int ines_pcmcia_accel_attach(struct gpib_board *board, - const struct gpib_board_config *config) -{ - struct ines_priv *ines_priv; - int retval; - - retval = ines_common_pcmcia_attach(board); - if (retval < 0) - return retval; - - ines_priv = board->private_data; - ines_online(ines_priv, board, 1); - - return 0; -} - -static void ines_pcmcia_detach(struct gpib_board *board) -{ - struct ines_priv *ines_priv = board->private_data; - struct nec7210_priv *nec_priv; - - if (ines_priv) { - nec_priv = &ines_priv->nec7210_priv; - if (ines_priv->irq) - free_irq(ines_priv->irq, board); - if (nec_priv->iobase) { - nec7210_board_reset(nec_priv, board); - release_region(nec_priv->iobase, ines_pcmcia_iosize); - } - } - ines_free_private(board); -} - -#endif /* CONFIG_GPIB_PCMCIA */ - -static int __init ines_init_module(void) -{ - int ret; - - ret = pci_register_driver(&ines_pci_driver); - if (ret) { - pr_err("pci_register_driver failed: error = %d\n", ret); - return ret; - } - - ret = gpib_register_driver(&ines_pci_interface, THIS_MODULE); - if (ret) { - pr_err("gpib_register_driver failed: error = %d\n", ret); - goto err_pci; - } - - ret = gpib_register_driver(&ines_pci_unaccel_interface, THIS_MODULE); - if (ret) { - pr_err("gpib_register_driver failed: error = %d\n", ret); - goto err_pci_unaccel; - } - - ret = gpib_register_driver(&ines_pci_accel_interface, THIS_MODULE); - if (ret) { - pr_err("gpib_register_driver failed: error = %d\n", ret); - goto err_pci_accel; - } - - ret = gpib_register_driver(&ines_isa_interface, THIS_MODULE); - if (ret) { - pr_err("gpib_register_driver failed: error = %d\n", ret); - goto err_isa; - } - -#ifdef CONFIG_GPIB_PCMCIA - ret = gpib_register_driver(&ines_pcmcia_interface, THIS_MODULE); - if (ret) { - pr_err("gpib_register_driver failed: error = %d\n", ret); - goto err_pcmcia; - } - - ret = gpib_register_driver(&ines_pcmcia_unaccel_interface, THIS_MODULE); - if (ret) { - pr_err("gpib_register_driver failed: error = %d\n", ret); - goto err_pcmcia_unaccel; - } - - ret = gpib_register_driver(&ines_pcmcia_accel_interface, THIS_MODULE); - if (ret) { - pr_err("gpib_register_driver failed: error = %d\n", ret); - goto err_pcmcia_accel; - } - - ret = pcmcia_register_driver(&ines_gpib_cs_driver); - if (ret) { - pr_err("pcmcia_register_driver failed: error = %d\n", ret); - goto err_pcmcia_driver; - } -#endif - - return 0; - -#ifdef CONFIG_GPIB_PCMCIA -err_pcmcia_driver: - gpib_unregister_driver(&ines_pcmcia_accel_interface); -err_pcmcia_accel: - gpib_unregister_driver(&ines_pcmcia_unaccel_interface); -err_pcmcia_unaccel: - gpib_unregister_driver(&ines_pcmcia_interface); -err_pcmcia: -#endif - gpib_unregister_driver(&ines_isa_interface); -err_isa: - gpib_unregister_driver(&ines_pci_accel_interface); -err_pci_accel: - gpib_unregister_driver(&ines_pci_unaccel_interface); -err_pci_unaccel: - gpib_unregister_driver(&ines_pci_interface); -err_pci: - pci_unregister_driver(&ines_pci_driver); - - return ret; -} - -static void __exit ines_exit_module(void) -{ - gpib_unregister_driver(&ines_pci_interface); - gpib_unregister_driver(&ines_pci_unaccel_interface); - gpib_unregister_driver(&ines_pci_accel_interface); - gpib_unregister_driver(&ines_isa_interface); -#ifdef CONFIG_GPIB_PCMCIA - gpib_unregister_driver(&ines_pcmcia_interface); - gpib_unregister_driver(&ines_pcmcia_unaccel_interface); - gpib_unregister_driver(&ines_pcmcia_accel_interface); - ines_pcmcia_cleanup_module(); -#endif - - pci_unregister_driver(&ines_pci_driver); -} - -module_init(ines_init_module); -module_exit(ines_exit_module); diff --git a/drivers/staging/gpib/lpvo_usb_gpib/Makefile b/drivers/staging/gpib/lpvo_usb_gpib/Makefile deleted file mode 100644 index 360553488e6d..000000000000 --- a/drivers/staging/gpib/lpvo_usb_gpib/Makefile +++ /dev/null @@ -1,3 +0,0 @@ - -obj-$(CONFIG_GPIB_LPVO) += lpvo_usb_gpib.o - diff --git a/drivers/staging/gpib/lpvo_usb_gpib/lpvo_usb_gpib.c b/drivers/staging/gpib/lpvo_usb_gpib/lpvo_usb_gpib.c deleted file mode 100644 index dd68c4843490..000000000000 --- a/drivers/staging/gpib/lpvo_usb_gpib/lpvo_usb_gpib.c +++ /dev/null @@ -1,2025 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 - -/*************************************************************************** - * This code has been developed at the Department of Physics (University * - * of Florence, Italy) to support in linux-gpib the open usb-gpib adapter * - * implemented at the University of Ljubljana (lpvo.fe.uni-lj.si/gpib) * - * * - * copyright : (C) 2011 Marcello Carla' * - ***************************************************************************/ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -#define dev_fmt pr_fmt -#define NAME KBUILD_MODNAME - -/* base module includes */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "gpibP.h" - -MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("GPIB driver for LPVO usb devices"); - -/* - * Table of devices that work with this driver. - * - * Currently, only one device is known to be used in the - * lpvo_usb_gpib adapter (FTDI 0403:6001). - * If your adapter uses a different chip, insert a line - * in the following table with proper , . - * - * To have your chip automatically handled by the driver, - * update files "/usr/local/etc/modprobe.d/lpvo_usb_gpib.conf" - * and /usr/local/etc/udev/rules.d/99-lpvo_usb_gpib.rules. - * - */ - -static const struct usb_device_id skel_table[] = { - { USB_DEVICE(0x0403, 0x6001) }, - { } /* Terminating entry */ -}; -MODULE_DEVICE_TABLE(usb, skel_table); - -/* - * *** Diagnostics and Debug *** - * To enable the diagnostic and debug messages either compile with DEBUG set - * or control via the dynamic debug mechanisms. - * The module parameter "debug" controls the sending of debug messages to - * syslog. By default it is set to 0 - * debug = 0: only attach/detach messages are sent - * 1: every action is logged - * 2: extended logging; each single exchanged byte is documented - * (about twice the log volume of [1]) - * To switch debug level: - * At module loading: modprobe lpvo_usb_gpib debug={0,1,2} - * On the fly: echo {0,1,2} > /sys/modules/lpvo_usb_gpib/parameters/debug - */ - -static int debug; -module_param(debug, int, 0644); - -#define DIA_LOG(level, format, ...) \ - do { if (debug >= (level)) \ - dev_dbg(board->gpib_dev, format, ## __VA_ARGS__); } \ - while (0) - -#define WQT wait_queue_entry_t -#define WQH head -#define WQE entry - -/* standard and extended command sets of the usb-gpib adapter */ - -#define USB_GPIB_ON "\nIB\n" -#define USB_GPIB_OFF "\nIBO\n" -#define USB_GPIB_IBm0 "\nIBm0\n" /* do not assert REN with IFC */ -#define USB_GPIB_IBm1 "\nIBm1\n" /* assert REN with IFC */ -#define USB_GPIB_IBCL "\nIBZ\n" -#define USB_GPIB_STATUS "\nIBS\n" -#define USB_GPIB_READ "\nIB?\n" -#define USB_GPIB_READ_1 "\nIBB\n" -#define USB_GPIB_EOI "\nIBe0\n" -#define USB_GPIB_FTMO "\nIBf0\n" /* disable first byte timeout */ -#define USB_GPIB_TTMOZ "\nIBt0\n" /* disable byte timeout */ - -/* incomplete commands */ - -#define USB_GPIB_BTMO "\nIBt" /* set byte timeout */ -#define USB_GPIB_TTMO "\nIBT" /* set total timeout */ - -#define USB_GPIB_DEBUG_ON "\nIBDE\xAA\n" -#define USB_GPIB_SET_LISTEN "\nIBDT0\n" -#define USB_GPIB_SET_TALK "\nIBDT1\n" -#define USB_GPIB_SET_LINES "\nIBDC.\n" -#define USB_GPIB_SET_DATA "\nIBDM.\n" -#define USB_GPIB_READ_LINES "\nIBD?C\n" -#define USB_GPIB_READ_DATA "\nIBD?M\n" -#define USB_GPIB_READ_BUS "\nIBD??\n" - -/* command sequences */ - -#define USB_GPIB_UNTALK "\nIBC_\n" -#define USB_GPIB_UNLISTEN "\nIBC?\n" - -/* special characters used by the adapter */ - -#define DLE ('\020') -#define STX ('\02') -#define ETX ('\03') -#define ACK ('\06') -#define NODATA ('\03') -#define NODAV ('\011') - -#define IB_BUS_REN 0x01 -#define IB_BUS_IFC 0x02 -#define IB_BUS_NDAC 0x04 -#define IB_BUS_NRFD 0x08 -#define IB_BUS_DAV 0x10 -#define IB_BUS_EOI 0x20 -#define IB_BUS_ATN 0x40 -#define IB_BUS_SRQ 0x80 - -#define INBUF_SIZE 128 - -struct char_buf { /* used by one_char() routine */ - char *inbuf; - int last; - int nchar; -}; - -struct usb_gpib_priv { /* private data to the device */ - u8 eos; /* eos character */ - short eos_flags; /* eos mode */ - int timeout; /* current value for timeout */ - void *dev; /* the usb device private data structure */ -}; - -#define GPIB_DEV (((struct usb_gpib_priv *)board->private_data)->dev) - -static void show_status(struct gpib_board *board) -{ - DIA_LOG(2, "# - buffer_length %d\n", board->buffer_length); - DIA_LOG(2, "# - status %lx\n", board->status); - DIA_LOG(2, "# - use_count %d\n", board->use_count); - DIA_LOG(2, "# - pad %x\n", board->pad); - DIA_LOG(2, "# - sad %x\n", board->sad); - DIA_LOG(2, "# - timeout %d\n", board->usec_timeout); - DIA_LOG(2, "# - ppc %d\n", board->parallel_poll_configuration); - DIA_LOG(2, "# - t1delay %d\n", board->t1_nano_sec); - DIA_LOG(2, "# - online %d\n", board->online); - DIA_LOG(2, "# - autopoll %d\n", board->autospollers); - DIA_LOG(2, "# - autopoll task %p\n", board->autospoll_task); - DIA_LOG(2, "# - minor %d\n", board->minor); - DIA_LOG(2, "# - master %d\n", board->master); - DIA_LOG(2, "# - list %d\n", board->ist); -} - -/* - * GLOBAL VARIABLES: required for - * pairing among gpib minor and usb minor. - * MAX_DEV is the max number of usb-gpib adapters; free - * to change as you like, but no more than 32 - */ - -#define MAX_DEV 8 -static struct usb_interface *lpvo_usb_interfaces[MAX_DEV]; /* registered interfaces */ -static int usb_minors[MAX_DEV]; /* usb minors */ -static int assigned_usb_minors; /* mask of filled slots */ -static struct mutex minors_lock; /* operations on usb_minors are to be protected */ - -/* - * usb-skeleton prototypes - */ - -struct usb_skel; -static ssize_t skel_do_write(struct usb_skel *, const char *, size_t); -static ssize_t skel_do_read(struct usb_skel *, char *, size_t); -static int skel_do_open(struct gpib_board *, int); -static int skel_do_release(struct gpib_board *); - -/* - * usec_diff : take difference in MICROsec between two 'timespec' - * (unix time in sec and NANOsec) - */ - -static inline int usec_diff(struct timespec64 *a, struct timespec64 *b) -{ - return ((a->tv_sec - b->tv_sec) * 1000000 + - (a->tv_nsec - b->tv_nsec) / 1000); -} - -/* - * *** these routines are specific to the usb-gpib adapter *** - */ - -/** - * write_loop() - Send a byte sequence to the adapter - * - * @dev: the private device structure - * @msg: the byte sequence. - * @leng: the byte sequence length. - * - */ - -static int write_loop(void *dev, char *msg, int leng) -{ - return skel_do_write(dev, msg, leng); -} - -/** - * send_command() - Send a byte sequence and return a single byte reply. - * - * @board: the gpib_board_struct data area for this gpib interface - * @msg: the byte sequence. - * @leng: the byte sequence length; can be given as zero and is - * computed automatically, but if 'msg' contains a zero byte, - * it has to be given explicitly. - */ - -static int send_command(struct gpib_board *board, char *msg, int leng) -{ - char buffer[64]; - int nchar; - int retval; - struct timespec64 before, after; - - ktime_get_real_ts64 (&before); - - if (!leng) - leng = strlen(msg); - retval = write_loop(GPIB_DEV, msg, leng); - if (retval < 0) - return retval; - - nchar = skel_do_read(GPIB_DEV, buffer, 64); - - if (nchar < 0) { - dev_err(board->gpib_dev, " return from read: %d\n", nchar); - return nchar; - } else if (nchar != 1) { - dev_err(board->gpib_dev, " Irregular reply to command: %s\n", msg); - return -EIO; - } - ktime_get_real_ts64 (&after); - - DIA_LOG(1, "Sent %d - done %d us.\n", leng, usec_diff(&after, &before)); - - return buffer[0] & 0xff; -} - -/* - * set_control_line() - Set the value of a single gpib control line - * - * @board: the gpib_board_struct data area for this gpib interface - * @line: line mask - * @value: line new value (0/1) - */ - -static int set_control_line(struct gpib_board *board, int line, int value) -{ - char msg[] = USB_GPIB_SET_LINES; - int retval; - int leng = strlen(msg); - - DIA_LOG(1, "setting line %x to %x\n", line, value); - - retval = send_command(board, USB_GPIB_READ_LINES, 0); - - DIA_LOG(1, "old line values: %x\n", retval); - - if (retval == -EIO) - return retval; - - msg[leng - 2] = value ? (retval & ~line) : retval | line; - - retval = send_command(board, msg, 0); - - DIA_LOG(1, "operation result: %x\n", retval); - - return retval; -} - -/* - * one_char() - read one single byte from input buffer - * - * @board: the gpib_board_struct data area for this gpib interface - * @char_buf: the routine private data structure - */ - -static int one_char(struct gpib_board *board, struct char_buf *b) -{ - struct timespec64 before, after; - - if (b->nchar) { - DIA_LOG(2, "-> %x\n", b->inbuf[b->last - b->nchar]); - return b->inbuf[b->last - b->nchar--]; - } - ktime_get_real_ts64 (&before); - b->nchar = skel_do_read(GPIB_DEV, b->inbuf, INBUF_SIZE); - b->last = b->nchar; - ktime_get_real_ts64 (&after); - - DIA_LOG(2, "read %d bytes in %d usec\n", - b->nchar, usec_diff(&after, &before)); - - if (b->nchar > 0) { - DIA_LOG(2, "--> %x\n", b->inbuf[b->last - b->nchar]); - return b->inbuf[b->last - b->nchar--]; - } - return -EIO; -} - -/** - * set_timeout() - set single byte / total timeouts on the adapter - * - * @board: the gpib_board_struct data area for this gpib interface - * - * For sake of speed, the operation is performed only if it - * modifies the current (saved) value. Minimum allowed timeout - * is 30 ms (T30ms -> 8); timeout disable (TNONE -> 0) currently - * not supported. - */ - -static void set_timeout(struct gpib_board *board) -{ - int n, val; - char command[sizeof(USB_GPIB_TTMO) + 6]; - struct usb_gpib_priv *data = board->private_data; - - if (data->timeout == board->usec_timeout) - return; - - n = (board->usec_timeout + 32767) / 32768; - if (n < 2) - n = 2; - - DIA_LOG(1, "Set timeout to %d us -> %d\n", board->usec_timeout, n); - - sprintf(command, "%s%d\n", USB_GPIB_BTMO, n > 255 ? 255 : n); - val = send_command(board, command, 0); - - if (val == ACK) { - if (n > 65535) - n = 65535; - sprintf(command, "%s%d\n", USB_GPIB_TTMO, n); - val = send_command(board, command, 0); - } - - if (val != ACK) - dev_err(board->gpib_dev, "error in timeout set: <%s>\n", command); - else - data->timeout = board->usec_timeout; -} - -/* - * now the standard interface functions - attach and detach - */ - -/** - * usb_gpib_attach() - activate the usb-gpib converter board - * - * @board: the gpib_board_struct data area for this gpib interface - * @config: firmware data, if any (from gpib_config -I ) - * - * The channel name is ttyUSBn, with n=0 by default. Other values for n - * passed with gpib_config -b . - * - * In this routine I trust that when an error code is returned - * detach() will be called. Always. - */ - -static int usb_gpib_attach(struct gpib_board *board, const struct gpib_board_config *config) -{ - int retval, j; - u32 base = config->ibbase; - char *device_path; - int match; - struct usb_device *udev; - - DIA_LOG(0, "Board %p -t %s -m %d -a %p -u %d -l %d -b %d\n", - board, board->interface->name, board->minor, config->device_path, - config->pci_bus, config->pci_slot, base); - - board->private_data = NULL; /* to be sure - we can detach before setting */ - - /* identify device to be attached */ - - mutex_lock(&minors_lock); - - if (config->device_path) { - /* if config->device_path given, try that first */ - for (j = 0 ; j < MAX_DEV ; j++) { - if ((assigned_usb_minors & 1 << j) == 0) - continue; - udev = usb_get_dev(interface_to_usbdev(lpvo_usb_interfaces[j])); - device_path = kobject_get_path(&udev->dev.kobj, GFP_KERNEL); - match = gpib_match_device_path(&lpvo_usb_interfaces[j]->dev, - config->device_path); - DIA_LOG(1, "dev. %d: minor %d path: %s --> %d\n", j, - lpvo_usb_interfaces[j]->minor, device_path, match); - kfree(device_path); - if (match) - break; - } - } else if (config->pci_bus != -1 && config->pci_slot != -1) { - /* second: look for bus and slot */ - for (j = 0 ; j < MAX_DEV ; j++) { - if ((assigned_usb_minors & 1 << j) == 0) - continue; - udev = usb_get_dev(interface_to_usbdev(lpvo_usb_interfaces[j])); - DIA_LOG(1, "dev. %d: bus %d -> %d dev: %d -> %d\n", j, - udev->bus->busnum, config->pci_bus, udev->devnum, config->pci_slot); - if (config->pci_bus == udev->bus->busnum && - config->pci_slot == udev->devnum) - break; - } - } else { /* last chance: usb_minor, given as ibbase */ - for (j = 0 ; j < MAX_DEV ; j++) { - if (usb_minors[j] == base && assigned_usb_minors & 1 << j) - break; - } - } - mutex_unlock(&minors_lock); - - if (j == MAX_DEV) { - dev_err(board->gpib_dev, "Requested device is not registered.\n"); - return -EIO; - } - - board->private_data = kzalloc(sizeof(struct usb_gpib_priv), GFP_KERNEL); - if (!board->private_data) - return -ENOMEM; - - retval = skel_do_open(board, usb_minors[j]); - - DIA_LOG(1, "Skel open: %d\n", retval); - - if (retval) { - dev_err(board->gpib_dev, "skel open failed.\n"); - kfree(board->private_data); - board->private_data = NULL; - return -ENODEV; - } - - show_status(board); - - retval = send_command(board, USB_GPIB_ON, 0); - DIA_LOG(1, "USB_GPIB_ON returns %x\n", retval); - if (retval != ACK) - return -EIO; - - /* - * We must setup debug mode because we need the extended instruction - * set to cope with the Core (gpib_common) point of view - */ - - retval = send_command(board, USB_GPIB_DEBUG_ON, 0); - DIA_LOG(1, "USB_GPIB_DEBUG_ON returns %x\n", retval); - if (retval != ACK) - return -EIO; - - /* - * We must keep REN off after an IFC because so it is - * assumed by the Core - */ - - retval = send_command(board, USB_GPIB_IBm0, 0); - DIA_LOG(1, "USB_GPIB_IBm0 returns %x\n", retval); - if (retval != ACK) - return -EIO; - - retval = set_control_line(board, IB_BUS_REN, 0); - if (retval != ACK) - return -EIO; - - retval = send_command(board, USB_GPIB_FTMO, 0); - DIA_LOG(1, "USB_GPIB_FTMO returns %x\n", retval); - if (retval != ACK) - return -EIO; - - show_status(board); - DIA_LOG(0, "attached\n"); - return 0; -} - -/** - * usb_gpib_detach() - deactivate the usb-gpib converter board - * - * @board: the gpib_board data area for this gpib interface - * - */ - -static void usb_gpib_detach(struct gpib_board *board) -{ - int retval; - - show_status(board); - - DIA_LOG(0, "detaching\n"); - - if (board->private_data) { - if (GPIB_DEV) { - write_loop(GPIB_DEV, USB_GPIB_OFF, strlen(USB_GPIB_OFF)); - msleep(100); - DIA_LOG(1, "%s", "GPIB off\n"); - retval = skel_do_release(board); - DIA_LOG(1, "skel release -> %d\n", retval); - } - kfree(board->private_data); - board->private_data = NULL; - } - - DIA_LOG(0, "detached\n"); -} - -/* - * Other functions follow in alphabetical order - */ -/* command */ -static int usb_gpib_command(struct gpib_board *board, - u8 *buffer, - size_t length, - size_t *bytes_written) -{ - int i, retval; - char command[6] = "IBc.\n"; - - DIA_LOG(1, "enter %p\n", board); - - set_timeout(board); - - *bytes_written = 0; - for (i = 0 ; i < length ; i++) { - command[3] = buffer[i]; - retval = send_command(board, command, 5); - DIA_LOG(2, "%d ==> %x %x\n", i, buffer[i], retval); - if (retval != 0x06) - return retval; - ++(*bytes_written); - } - return 0; -} - -/** - * usb_gpib_disable_eos() - Disable END on eos byte (END on EOI only) - * - * @board: the gpib_board data area for this gpib interface - * - * With the lpvo adapter eos can only be handled via software. - * Cannot do nothing here, but remember for future use. - */ - -static void usb_gpib_disable_eos(struct gpib_board *board) -{ - ((struct usb_gpib_priv *)board->private_data)->eos_flags &= ~REOS; - DIA_LOG(1, "done: %x\n", - ((struct usb_gpib_priv *)board->private_data)->eos_flags); -} - -/** - * usb_gpib_enable_eos() - Enable END for reads when eos byte is received. - * - * @board: the gpib_board data area for this gpib interface - * @eos_byte: the 'eos' byte - * @compare_8_bits: if zero ignore eigthth bit when comparing - * - */ - -static int usb_gpib_enable_eos(struct gpib_board *board, - u8 eos_byte, - int compare_8_bits) -{ - struct usb_gpib_priv *pd = (struct usb_gpib_priv *)board->private_data; - - DIA_LOG(1, "enter with %x\n", eos_byte); - pd->eos = eos_byte; - pd->eos_flags = REOS; - if (compare_8_bits) - pd->eos_flags |= BIN; - return 0; -} - -/** - * usb_gpib_go_to_standby() - De-assert ATN - * - * @board: the gpib_board data area for this gpib interface - */ - -static int usb_gpib_go_to_standby(struct gpib_board *board) -{ - int retval = set_control_line(board, IB_BUS_ATN, 0); - - DIA_LOG(1, "done with %x\n", retval); - - if (retval == ACK) - return 0; - return -EIO; -} - -/** - * usb_gpib_interface_clear() - Assert or de-assert IFC - * - * @board: the gpib_board data area for this gpib interface - * @assert: 1: assert IFC; 0: de-assert IFC - * - * Currently on the assert request we issue the lpvo IBZ - * command that cycles IFC low for 100 usec, then we ignore - * the de-assert request. - */ - -static void usb_gpib_interface_clear(struct gpib_board *board, int assert) -{ - int retval = 0; - - DIA_LOG(1, "enter with %d\n", assert); - - if (assert) { - retval = send_command(board, USB_GPIB_IBCL, 0); - - set_bit(CIC_NUM, &board->status); - } - - DIA_LOG(1, "done with %d %d\n", assert, retval); -} - -/** - * usb_gpib_line_status() - Read the status of the bus lines. - * - * @board: the gpib_board data area for this gpib interface - * - * We can read all lines. - */ -static int usb_gpib_line_status(const struct gpib_board *board) -{ - int buffer; - int line_status = VALID_ALL; /* all lines will be read */ - struct list_head *p, *q; - WQT *item; - unsigned long flags; - int sleep = 0; - - DIA_LOG(1, "%s\n", "request"); - - /* - * if we are on the wait queue (board->wait), do not hurry - * reading status line; instead, pause a little - */ - - spin_lock_irqsave((spinlock_t *)&board->wait.lock, flags); - q = (struct list_head *)&board->wait.WQH; - list_for_each(p, q) { - item = container_of(p, WQT, WQE); - if (item->private == current) { - sleep = 20; - break; - } - /* pid is: ((struct task_struct *) item->private)->pid); */ - } - spin_unlock_irqrestore((spinlock_t *)&board->wait.lock, flags); - if (sleep) { - DIA_LOG(1, "we are on the wait queue - sleep %d ms\n", sleep); - msleep(sleep); - } - - buffer = send_command((struct gpib_board *)board, USB_GPIB_STATUS, 0); - - if (buffer < 0) { - dev_err(board->gpib_dev, "line status read failed with %d\n", buffer); - return -1; - } - - if ((buffer & 0x01) == 0) - line_status |= BUS_REN; - if ((buffer & 0x02) == 0) - line_status |= BUS_IFC; - if ((buffer & 0x04) == 0) - line_status |= BUS_NDAC; - if ((buffer & 0x08) == 0) - line_status |= BUS_NRFD; - if ((buffer & 0x10) == 0) - line_status |= BUS_DAV; - if ((buffer & 0x20) == 0) - line_status |= BUS_EOI; - if ((buffer & 0x40) == 0) - line_status |= BUS_ATN; - if ((buffer & 0x80) == 0) - line_status |= BUS_SRQ; - - DIA_LOG(1, "done with %x %x\n", buffer, line_status); - - return line_status; -} - -/* parallel_poll */ - -static int usb_gpib_parallel_poll(struct gpib_board *board, u8 *result) -{ - /* - * request parallel poll asserting ATN | EOI; - * we suppose ATN already asserted - */ - - int retval; - - DIA_LOG(1, "enter %p\n", board); - - retval = set_control_line(board, IB_BUS_EOI, 1); - if (retval != ACK) - return -EIO; - - *result = send_command(board, USB_GPIB_READ_DATA, 0); - - DIA_LOG(1, "done with %x\n", *result); - - retval = set_control_line(board, IB_BUS_EOI, 0); - if (retval != 0x06) - return -EIO; - - return 0; -} - -/* read */ - -static int usb_gpib_read(struct gpib_board *board, - u8 *buffer, - size_t length, - int *end, - size_t *bytes_read) -{ -#define MAX_READ_EXCESS 16384 - - struct char_buf b = {NULL, 0}; - - int retval; - char c, nc; - int ic; - struct timespec64 before, after; - int read_count = MAX_READ_EXCESS; - struct usb_gpib_priv *pd = (struct usb_gpib_priv *)board->private_data; - - DIA_LOG(1, "enter %p -> %zu\n", board, length); - - *bytes_read = 0; /* by default, things go wrong */ - *end = 0; - - set_timeout(board); - - /* single byte read has a special handling */ - - if (length == 1) { - char inbuf[2] = {0, 0}; - - /* read a single character */ - - ktime_get_real_ts64 (&before); - - retval = write_loop(GPIB_DEV, USB_GPIB_READ_1, strlen(USB_GPIB_READ_1)); - if (retval < 0) - return retval; - - retval = skel_do_read(GPIB_DEV, inbuf, 1); - retval += skel_do_read(GPIB_DEV, inbuf + 1, 1); - - ktime_get_real_ts64 (&after); - - DIA_LOG(1, "single read: %x %x %x in %d\n", retval, - inbuf[0], inbuf[1], - usec_diff(&after, &before)); - - /* good char / last char? */ - - if (retval == 2 && inbuf[1] == ACK) { - buffer[0] = inbuf[0]; - *bytes_read = 1; - return 0; - } - if (retval < 2) - return -EIO; - else - return -ETIME; - } - - /* allocate buffer for multibyte read */ - - b.inbuf = kmalloc(INBUF_SIZE, GFP_KERNEL); - if (!b.inbuf) - return -ENOMEM; - - /* send read command and check sequence */ - - retval = write_loop(GPIB_DEV, USB_GPIB_READ, strlen(USB_GPIB_READ)); - if (retval < 0) - goto read_return; - - if (one_char(board, &b) != DLE || one_char(board, &b) != STX) { - dev_err(board->gpib_dev, "wrong sequence\n"); - retval = -EIO; - goto read_return; - } - - /* get data flow */ - - while (1) { - ic = one_char(board, &b); - if (ic == -EIO) { - retval = -EIO; - goto read_return; - } - c = ic; - - if (c == DLE) - nc = one_char(board, &b); - if (c != DLE || nc == DLE) { - /* data byte - store into buffer */ - - if (*bytes_read == length) - break; /* data overflow */ - if (c == DLE) - c = nc; - buffer[(*bytes_read)++] = c; - if (c == pd->eos) { - *end = 1; - break; - } - - } else { - /* we are in the closing sequence */ - c = nc; - if (c == ETX) { - c = one_char(board, &b); - if (c == ACK) { - *end = 1; - retval = 0; - goto read_return; - } else { - dev_err(board->gpib_dev, "wrong end of message %x", c); - retval = -ETIME; - goto read_return; - } - } else { - dev_err(board->gpib_dev, "lone in stream"); - retval = -EIO; - goto read_return; - } - } - } - - /* we had a data overflow - flush excess data */ - - while (read_count--) { - if (one_char(board, &b) != DLE) - continue; - c = one_char(board, &b); - if (c == DLE) - continue; - if (c == ETX) { - c = one_char(board, &b); - if (c == ACK) { - if (MAX_READ_EXCESS - read_count > 1) - dev_dbg(board->gpib_dev, "small buffer - maybe some data lost"); - retval = 0; - goto read_return; - } - break; - } - } - - dev_err(board->gpib_dev, "no input end - board in odd state\n"); - retval = -EIO; - -read_return: - kfree(b.inbuf); - - DIA_LOG(1, "done with byte/status: %d %x %d\n", (int)*bytes_read, retval, *end); - - if (retval == 0 || retval == -ETIME) { - if (send_command(board, USB_GPIB_UNTALK, sizeof(USB_GPIB_UNTALK)) == 0x06) - return retval; - return -EIO; - } - - return retval; -} - -/* remote_enable */ - -static void usb_gpib_remote_enable(struct gpib_board *board, int enable) -{ - int retval; - - retval = set_control_line(board, IB_BUS_REN, enable ? 1 : 0); - if (retval != ACK) - dev_err(board->gpib_dev, "could not set REN line: %x\n", retval); - - DIA_LOG(1, "done with %x\n", retval); -} - -/* request_system_control */ - -static int usb_gpib_request_system_control(struct gpib_board *board, int request_control) -{ - if (!request_control) - return -EINVAL; - - DIA_LOG(1, "done with %d -> %lx\n", request_control, board->status); - return 0; -} - -/* take_control */ -/* beware: the sync flag is ignored; what is its real meaning? */ - -static int usb_gpib_take_control(struct gpib_board *board, int sync) -{ - int retval; - - retval = set_control_line(board, IB_BUS_ATN, 1); - - DIA_LOG(1, "done with %d %x\n", sync, retval); - - if (retval == ACK) - return 0; - return -EIO; -} - -/* update_status */ - -static unsigned int usb_gpib_update_status(struct gpib_board *board, - unsigned int clear_mask) -{ - /* There is nothing we can do here, I guess */ - - board->status &= ~clear_mask; - - DIA_LOG(1, "done with %x %lx\n", clear_mask, board->status); - - return board->status; -} - -/* write */ -/* beware: DLE characters are not escaped - can only send ASCII data */ - -static int usb_gpib_write(struct gpib_board *board, - u8 *buffer, - size_t length, - int send_eoi, - size_t *bytes_written) -{ - int retval; - char *msg; - - DIA_LOG(1, "enter %p -> %zu\n", board, length); - - set_timeout(board); - - msg = kmalloc(length + 8, GFP_KERNEL); - if (!msg) - return -ENOMEM; - - memcpy(msg, "\nIB\020\002", 5); - memcpy(msg + 5, buffer, length); - memcpy(msg + 5 + length, "\020\003\n", 3); - - retval = send_command(board, msg, length + 8); - kfree(msg); - - DIA_LOG(1, "<%.*s> -> %x\n", (int)length, buffer, retval); - - if (retval != ACK) - return -EPIPE; - - *bytes_written = length; - - if (send_command(board, USB_GPIB_UNLISTEN, sizeof(USB_GPIB_UNLISTEN)) != 0x06) - return -EPIPE; - - return length; -} - -/* - * *** following functions not implemented yet *** - */ - -/* parallel_poll configure */ - -static void usb_gpib_parallel_poll_configure(struct gpib_board *board, - u8 configuration) -{ -} - -/* parallel_poll_response */ - -static void usb_gpib_parallel_poll_response(struct gpib_board *board, int ist) -{ -} - -/* primary_address */ - -static int usb_gpib_primary_address(struct gpib_board *board, unsigned int address) -{ - return 0; -} - -/* return_to_local */ - -static void usb_gpib_return_to_local(struct gpib_board *board) -{ -} - -/* secondary_address */ - -static int usb_gpib_secondary_address(struct gpib_board *board, - unsigned int address, - int enable) -{ - return 0; -} - -/* serial_poll_response */ - -static void usb_gpib_serial_poll_response(struct gpib_board *board, u8 status) -{ -} - -/* serial_poll_status */ - -static u8 usb_gpib_serial_poll_status(struct gpib_board *board) -{ - return 0; -} - -/* t1_delay */ - -static int usb_gpib_t1_delay(struct gpib_board *board, unsigned int nano_sec) -{ - return 0; -} - -/* - * *** module dispatch table and init/exit functions *** - */ - -static struct gpib_interface usb_gpib_interface = { - .name = NAME, - .attach = usb_gpib_attach, - .detach = usb_gpib_detach, - .read = usb_gpib_read, - .write = usb_gpib_write, - .command = usb_gpib_command, - .take_control = usb_gpib_take_control, - .go_to_standby = usb_gpib_go_to_standby, - .request_system_control = usb_gpib_request_system_control, - .interface_clear = usb_gpib_interface_clear, - .remote_enable = usb_gpib_remote_enable, - .enable_eos = usb_gpib_enable_eos, - .disable_eos = usb_gpib_disable_eos, - .parallel_poll = usb_gpib_parallel_poll, - .parallel_poll_configure = usb_gpib_parallel_poll_configure, - .parallel_poll_response = usb_gpib_parallel_poll_response, - .local_parallel_poll_mode = NULL, // XXX - .line_status = usb_gpib_line_status, - .update_status = usb_gpib_update_status, - .primary_address = usb_gpib_primary_address, - .secondary_address = usb_gpib_secondary_address, - .serial_poll_response = usb_gpib_serial_poll_response, - .serial_poll_status = usb_gpib_serial_poll_status, - .t1_delay = usb_gpib_t1_delay, - .return_to_local = usb_gpib_return_to_local, - .skip_check_for_command_acceptors = 1 -}; - -/* - * usb_gpib_init_module(), usb_gpib_exit_module() - * - * This functions are called every time a new device is detected - * and registered or is removed and unregistered. - * We must take note of created and destroyed usb minors to be used - * when usb_gpib_attach() and usb_gpib_detach() will be called on - * request by gpib_config. - */ - -static int usb_gpib_init_module(struct usb_interface *interface) -{ - int j, mask, rv; - - rv = mutex_lock_interruptible(&minors_lock); - if (rv < 0) - return rv; - - if (!assigned_usb_minors) { - rv = gpib_register_driver(&usb_gpib_interface, THIS_MODULE); - if (rv) { - pr_err("gpib_register_driver failed: error = %d\n", rv); - goto exit; - } - } else { - /* - * check if minor is already registered - maybe useless, but if - * it happens the code is inconsistent somewhere - */ - - for (j = 0 ; j < MAX_DEV ; j++) { - if (usb_minors[j] == interface->minor && assigned_usb_minors & 1 << j) { - pr_err("CODE BUG: USB minor %d registered at %d.\n", - interface->minor, j); - rv = -1; - goto exit; - } - } - } - - /* find a free slot */ - - for (j = 0 ; j < MAX_DEV ; j++) { - mask = 1 << j; - if ((assigned_usb_minors & mask) == 0) { - usb_minors[j] = interface->minor; - lpvo_usb_interfaces[j] = interface; - assigned_usb_minors |= mask; - rv = 0; - goto exit; - } - } - pr_err("No slot available for interface %p minor %d\n", interface, interface->minor); - rv = -1; - -exit: - mutex_unlock(&minors_lock); - return rv; -} - -static void usb_gpib_exit_module(int minor) -{ - int j; - - mutex_lock(&minors_lock); - for (j = 0 ; j < MAX_DEV ; j++) { - if (usb_minors[j] == minor && assigned_usb_minors & 1 << j) { - assigned_usb_minors &= ~(1 << j); - usb_minors[j] = -1; - if (assigned_usb_minors == 0) - gpib_unregister_driver(&usb_gpib_interface); - goto exit; - } - } - pr_err("CODE BUG: USB minor %d not found.\n", minor); - -exit: - mutex_unlock(&minors_lock); -} - -/* - * Default latency time (16 msec) is too long. - * We must use 1 msec (best); anyhow, no more than 5 msec. - * - * Defines and function taken and modified from the kernel tree - * (see ftdi_sio.h and ftdi_sio.c). - */ - -#define FTDI_SIO_SET_LATENCY_TIMER 9 /* Set the latency timer */ -#define FTDI_SIO_SET_LATENCY_TIMER_REQUEST FTDI_SIO_SET_LATENCY_TIMER -#define FTDI_SIO_SET_LATENCY_TIMER_REQUEST_TYPE 0x40 -#define WDR_TIMEOUT 5000 /* default urb timeout */ -#define WDR_SHORT_TIMEOUT 1000 /* shorter urb timeout */ - -#define LATENCY_TIMER 1 /* use a small latency timer: 1 ... 5 msec */ -#define LATENCY_CHANNEL 0 /* channel selection in multichannel devices */ -static int write_latency_timer(struct usb_device *udev) -{ - int rv = usb_control_msg(udev, - usb_sndctrlpipe(udev, 0), - FTDI_SIO_SET_LATENCY_TIMER_REQUEST, - FTDI_SIO_SET_LATENCY_TIMER_REQUEST_TYPE, - LATENCY_TIMER, LATENCY_CHANNEL, - NULL, 0, WDR_TIMEOUT); - if (rv < 0) - dev_err(&udev->dev, "Unable to write latency timer: %i\n", rv); - return rv; -} - -/***************************************************************************** - * * - * The following code is a modified version of the USB Skeleton driver * - * written by Greg Kroah-Hartman and available in the kernel tree. * - * * - * Functions skel_open() and skel_release() have been rewritten and named * - * skel_do_open() and skel_do_release() to process the attach and detach * - * requests coming from gpib_config. * - * * - * Functions skel_read() and skel_write() have been split into a * - * skel_do_read() and skel_do_write(), that cover the kernel stuff of read * - * and write operations, and the original skel_read() and skel_write(), * - * that handle communication with user space and call their _do_ companion. * - * * - * Only the _do_ versions are used by the lpvo_usb_gpib driver; other ones * - * can be (optionally) maintained in the compilation to have direct access * - * to a gpib controller for debug and diagnostics. * - * * - * To avoid collisions in names, devices in user space have been renamed * - * lpvo_raw1, lpvo_raw2 .... and the usb driver has been renamed with the * - * gpib module name. * - * * - *****************************************************************************/ - -/* - * USB Skeleton driver - 2.2 - * - * Copyright (C) 2001-2004 Greg Kroah-Hartman (greg@kroah.com) - * - * This driver is based on the 2.6.3 version of drivers/usb/usb-skeleton.c - * but has been rewritten to be easier to read and use. - */ - -#include -#include -#include -#include - -/* Get a minor range for your devices from the usb maintainer */ -#define USB_SKEL_MINOR_BASE 192 - -/* private defines */ - -#define MAX_TRANSFER (PAGE_SIZE - 512) -/* - * MAX_TRANSFER is chosen so that the VM is not stressed by - * allocations > PAGE_SIZE and the number of packets in a page - * is an integer 512 is the largest possible packet on EHCI - */ - -#define WRITES_IN_FLIGHT 1 /* we do not want more than one pending write */ -#define USER_DEVICE 1 /* compile for device(s) in user space */ - -/* Structure to hold all of our device specific stuff */ -struct usb_skel { - struct usb_device *udev; /* the usb device for this device */ - struct usb_interface *interface; /* the interface for this device */ - struct semaphore limit_sem; /* limiting the number of writes in progress */ - struct usb_anchor submitted; /* in case need to retract our submissions */ - struct urb *bulk_in_urb; /* the urb to read data with */ - unsigned char *bulk_in_buffer; /* the buffer to receive data */ - size_t bulk_in_size; /* the size of the receive buffer */ - size_t bulk_in_filled; /* number of bytes in the buffer */ - size_t bulk_in_copied; /* already copied to user space */ - __u8 bulk_in_endpoint_addr; /* the address of the bulk in endpoint */ - __u8 bulk_out_endpoint_addr; /* the address of the bulk out endpoint */ - int errors; /* the last request tanked */ - bool ongoing_read; /* a read is going on */ - spinlock_t err_lock; /* lock for errors */ - struct kref kref; - struct mutex io_mutex; /* synchronize I/O with disconnect */ - wait_queue_head_t bulk_in_wait; /* to wait for an ongoing read */ -}; - -#define to_skel_dev(d) container_of(d, struct usb_skel, kref) - -static struct usb_driver skel_driver; -static void skel_draw_down(struct usb_skel *dev); - -static void skel_delete(struct kref *kref) -{ - struct usb_skel *dev = to_skel_dev(kref); - - usb_free_urb(dev->bulk_in_urb); - usb_put_dev(dev->udev); - kfree(dev->bulk_in_buffer); - kfree(dev); -} - -/* - * skel_do_open() - to be called by usb_gpib_attach - */ - -static int skel_do_open(struct gpib_board *board, int subminor) -{ - struct usb_skel *dev; - struct usb_interface *interface; - int retval = 0; - - interface = usb_find_interface(&skel_driver, subminor); - if (!interface) { - dev_err(board->gpib_dev, "can't find device for minor %d\n", subminor); - retval = -ENODEV; - goto exit; - } - - dev = usb_get_intfdata(interface); - if (!dev) { - retval = -ENODEV; - goto exit; - } - - retval = usb_autopm_get_interface(interface); - if (retval) - goto exit; - - /* increment our usage count for the device */ - kref_get(&dev->kref); - - /* save our object in the file's private structure */ - GPIB_DEV = dev; - -exit: - return retval; -} - -/* - * skel_do_release() - to be called by usb_gpib_detach - */ - -static int skel_do_release(struct gpib_board *board) -{ - struct usb_skel *dev; - - dev = GPIB_DEV; - if (!dev) - return -ENODEV; - - /* allow the device to be autosuspended */ - mutex_lock(&dev->io_mutex); - if (dev->interface) - usb_autopm_put_interface(dev->interface); - mutex_unlock(&dev->io_mutex); - - /* decrement the count on our device */ - kref_put(&dev->kref, skel_delete); - return 0; -} - -/* - * read functions - */ - -static void skel_read_bulk_callback(struct urb *urb) -{ - struct usb_skel *dev; - unsigned long flags; - - dev = urb->context; - - spin_lock_irqsave(&dev->err_lock, flags); - /* sync/async unlink faults aren't errors */ - if (urb->status) { - if (!(urb->status == -ENOENT || - urb->status == -ECONNRESET || - urb->status == -ESHUTDOWN)) - dev_err(&dev->interface->dev, "nonzero read bulk status received: %d\n", - urb->status); - - dev->errors = urb->status; - } else { - dev->bulk_in_filled = urb->actual_length; - } - dev->ongoing_read = 0; - spin_unlock_irqrestore(&dev->err_lock, flags); - - wake_up_interruptible(&dev->bulk_in_wait); -} - -static int skel_do_read_io(struct usb_skel *dev, size_t count) -{ - int rv; - - /* prepare a read */ - usb_fill_bulk_urb(dev->bulk_in_urb, - dev->udev, - usb_rcvbulkpipe(dev->udev, - dev->bulk_in_endpoint_addr), - dev->bulk_in_buffer, - min(dev->bulk_in_size, count), - skel_read_bulk_callback, - dev); - /* tell everybody to leave the URB alone */ - spin_lock_irq(&dev->err_lock); - dev->ongoing_read = 1; - spin_unlock_irq(&dev->err_lock); - - /* submit bulk in urb, which means no data to deliver */ - dev->bulk_in_filled = 0; - dev->bulk_in_copied = 0; - - /* do it */ - rv = usb_submit_urb(dev->bulk_in_urb, GFP_KERNEL); - if (rv < 0) { - dev_err(&dev->interface->dev, "failed submitting read urb, error %d\n", rv); - rv = (rv == -ENOMEM) ? rv : -EIO; - spin_lock_irq(&dev->err_lock); - dev->ongoing_read = 0; - spin_unlock_irq(&dev->err_lock); - } - - return rv; -} - -/* - * skel_do_read() - read operations from lpvo_usb_gpib - */ - -static ssize_t skel_do_read(struct usb_skel *dev, char *buffer, size_t count) -{ - int rv; - bool ongoing_io; - - /* if we cannot read at all, return EOF */ - - if (!dev->bulk_in_urb || !count) - return 0; - -restart: /* added to comply with ftdi timeout technique */ - - /* no concurrent readers */ - - rv = mutex_lock_interruptible(&dev->io_mutex); - if (rv < 0) - return rv; - - if (!dev->interface) { /* disconnect() was called */ - rv = -ENODEV; - goto exit; - } - -retry: - /* if IO is under way, we must not touch things */ - spin_lock_irq(&dev->err_lock); - ongoing_io = dev->ongoing_read; - spin_unlock_irq(&dev->err_lock); - - if (ongoing_io) { -// /* nonblocking IO shall not wait */ -// /* no file, no O_NONBLOCK; maybe provide when from user space */ -// if (file->f_flags & O_NONBLOCK) { -// rv = -EAGAIN; -// goto exit; -// } - - /* - * IO may take forever - * hence wait in an interruptible state - */ - rv = wait_event_interruptible(dev->bulk_in_wait, (!dev->ongoing_read)); - if (rv < 0) - goto exit; - } - - /* errors must be reported */ - rv = dev->errors; - if (rv < 0) { - /* any error is reported once */ - dev->errors = 0; - /* to preserve notifications about reset */ - rv = (rv == -EPIPE) ? rv : -EIO; - /* report it */ - goto exit; - } - - /* - * if the buffer is filled we may satisfy the read - * else we need to start IO - */ - - if (dev->bulk_in_filled) { - /* we had read data */ - - size_t available = dev->bulk_in_filled - dev->bulk_in_copied; -// size_t chunk = min(available, count); /* compute chunk later */ - size_t chunk; - - if (!available) { - /* - * all data has been used - * actual IO needs to be done - */ - /* - * it seems that requests for less than dev->bulk_in_size - * are not accepted - */ - rv = skel_do_read_io(dev, dev->bulk_in_size); - if (rv < 0) - goto exit; - else - goto retry; - } - - /* - * data is available - chunk tells us how much shall be copied - */ - - /* - * Condition dev->bulk_in_copied > 0 maybe will never happen. In case, - * signal the event and copy using the original procedure, i.e., copy - * first two bytes also - */ - - if (dev->bulk_in_copied) { - chunk = min(available, count); - memcpy(buffer, dev->bulk_in_buffer + dev->bulk_in_copied, chunk); - rv = chunk; - dev->bulk_in_copied += chunk; - - /* copy discarding first two bytes that contain ftdi chip status */ - - } else { - /* account for two bytes to be discarded */ - chunk = min(available, count + 2); - if (chunk < 2) { - dev_err(&dev->udev->dev, "BAD READ - chunk: %zu\n", chunk); - rv = -EIO; - goto exit; - } - - memcpy(buffer, dev->bulk_in_buffer + 2, chunk - 2); - rv = chunk; - dev->bulk_in_copied += chunk; - } - - /* - * if we are asked for more than we have, - * we start IO but don't wait - * - * No, no read ahead allowed; if the case, more data will be - * asked for by the lpvo_usb_gpib layer. - */ -// if (available < count) -// skel_do_read_io(dev, dev->bulk_in_size); - } else { - /* no data in the buffer */ - rv = skel_do_read_io(dev, dev->bulk_in_size); - if (rv < 0) - goto exit; - else - goto retry; - } -exit: - mutex_unlock(&dev->io_mutex); - if (rv == 2) - goto restart; /* ftdi chip returns two status bytes after a latency anyhow */ - - if (rv > 0) - return rv - 2; /* account for 2 discarded bytes in a valid buffer */ - return rv; -} - -/* - * write functions - */ - -static void skel_write_bulk_callback(struct urb *urb) -{ - struct usb_skel *dev; - unsigned long flags; - - dev = urb->context; - - /* sync/async unlink faults aren't errors */ - if (urb->status) { - if (!(urb->status == -ENOENT || - urb->status == -ECONNRESET || - urb->status == -ESHUTDOWN)) - dev_err(&dev->interface->dev, - "nonzero write bulk status received: %d\n", urb->status); - - spin_lock_irqsave(&dev->err_lock, flags); - dev->errors = urb->status; - spin_unlock_irqrestore(&dev->err_lock, flags); - } - - /* free up our allocated buffer */ - usb_free_coherent(urb->dev, urb->transfer_buffer_length, - urb->transfer_buffer, urb->transfer_dma); - up(&dev->limit_sem); -} - -/* - * skel_do_write() - write operations from lpvo_usb_gpib - */ - -static ssize_t skel_do_write(struct usb_skel *dev, const char *buffer, size_t count) -{ - int retval = 0; - struct urb *urb = NULL; - char *buf = NULL; - size_t writesize = min_t(size_t, count, (size_t)MAX_TRANSFER); - - /* verify that we actually have some data to write */ - if (count == 0) - goto exit; - - /* - * limit the number of URBs in flight to stop a user from using up all - * RAM - */ - /* Only one URB is used, because we can't have a pending write() and go on */ - -// if (!(file->f_flags & O_NONBLOCK)) { /* no NONBLOCK provided */ - if (down_interruptible(&dev->limit_sem)) { - retval = -ERESTARTSYS; - goto exit; - } -// } else { -// if (down_trylock(&dev->limit_sem)) { -// retval = -EAGAIN; -// goto exit; -// } -// } - - spin_lock_irq(&dev->err_lock); - retval = dev->errors; - if (retval < 0) { - /* any error is reported once */ - dev->errors = 0; - /* to preserve notifications about reset */ - retval = (retval == -EPIPE) ? retval : -EIO; - } - spin_unlock_irq(&dev->err_lock); - if (retval < 0) - goto error; - - /* create a urb, and a buffer for it, and copy the data to the urb */ - urb = usb_alloc_urb(0, GFP_KERNEL); - if (!urb) { - retval = -ENOMEM; - goto error; - } - - buf = usb_alloc_coherent(dev->udev, writesize, GFP_KERNEL, - &urb->transfer_dma); - if (!buf) { - retval = -ENOMEM; - goto error; - } - - memcpy(buf, buffer, count); - - /* this lock makes sure we don't submit URBs to gone devices */ - mutex_lock(&dev->io_mutex); - if (!dev->interface) { /* disconnect() was called */ - mutex_unlock(&dev->io_mutex); - retval = -ENODEV; - goto error; - } - - /* initialize the urb properly */ - usb_fill_bulk_urb(urb, dev->udev, - usb_sndbulkpipe(dev->udev, dev->bulk_out_endpoint_addr), - buf, writesize, skel_write_bulk_callback, dev); - urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; - usb_anchor_urb(urb, &dev->submitted); - - /* send the data out the bulk port */ - retval = usb_submit_urb(urb, GFP_KERNEL); - mutex_unlock(&dev->io_mutex); - if (retval) { - dev_err(&dev->interface->dev, "failed submitting write urb, error %d\n", retval); - goto error_unanchor; - } - - /* - * release our reference to this urb, the USB core will eventually free - * it entirely - */ - usb_free_urb(urb); - - return writesize; - -error_unanchor: - usb_unanchor_urb(urb); -error: - if (urb) { - usb_free_coherent(dev->udev, writesize, buf, urb->transfer_dma); - usb_free_urb(urb); - } - up(&dev->limit_sem); - -exit: - return retval; -} - -/* - * services for the user space devices - */ - -#if USER_DEVICE /* conditional compilation of user space device */ - -static int skel_flush(struct file *file, fl_owner_t id) -{ - struct usb_skel *dev; - int res; - - dev = file->private_data; - if (!dev) - return -ENODEV; - - /* wait for io to stop */ - mutex_lock(&dev->io_mutex); - skel_draw_down(dev); - - /* read out errors, leave subsequent opens a clean slate */ - spin_lock_irq(&dev->err_lock); - res = dev->errors ? (dev->errors == -EPIPE ? -EPIPE : -EIO) : 0; - dev->errors = 0; - spin_unlock_irq(&dev->err_lock); - - mutex_unlock(&dev->io_mutex); - - return res; -} - -static int skel_open(struct inode *inode, struct file *file) -{ - struct usb_skel *dev; - struct usb_interface *interface; - int subminor; - int retval = 0; - - subminor = iminor(inode); - - interface = usb_find_interface(&skel_driver, subminor); - if (!interface) { - pr_err("can't find device for minor %d\n", subminor); - retval = -ENODEV; - goto exit; - } - - dev = usb_get_intfdata(interface); - if (!dev) { - retval = -ENODEV; - goto exit; - } - - retval = usb_autopm_get_interface(interface); - if (retval) - goto exit; - - /* increment our usage count for the device */ - kref_get(&dev->kref); - - /* save our object in the file's private structure */ - file->private_data = dev; - -exit: - return retval; -} - -static int skel_release(struct inode *inode, struct file *file) -{ - struct usb_skel *dev; - - dev = file->private_data; - if (!dev) - return -ENODEV; - - /* allow the device to be autosuspended */ - mutex_lock(&dev->io_mutex); - if (dev->interface) - usb_autopm_put_interface(dev->interface); - mutex_unlock(&dev->io_mutex); - - /* decrement the count on our device */ - kref_put(&dev->kref, skel_delete); - return 0; -} - -/* - * user space access to read function - */ - -static ssize_t skel_read(struct file *file, char __user *buffer, size_t count, - loff_t *ppos) -{ - struct usb_skel *dev; - char *buf; - ssize_t rv; - - dev = file->private_data; - - buf = kmalloc(count, GFP_KERNEL); - if (!buf) - return -ENOMEM; - - rv = skel_do_read(dev, buf, count); - - if (rv > 0) { - if (copy_to_user(buffer, buf, rv)) { - kfree(buf); - return -EFAULT; - } - } - kfree(buf); - return rv; -} - -/* - * user space access to write function - */ - -static ssize_t skel_write(struct file *file, const char __user *user_buffer, - size_t count, loff_t *ppos) -{ - struct usb_skel *dev; - char *buf; - ssize_t rv; - - dev = file->private_data; - - buf = kmalloc(count, GFP_KERNEL); - if (!buf) - return -ENOMEM; - - if (copy_from_user(buf, user_buffer, count)) { - kfree(buf); - return -EFAULT; - } - - rv = skel_do_write(dev, buf, count); - kfree(buf); - return rv; -} -#endif - -static const struct file_operations skel_fops = { - .owner = THIS_MODULE, -#if USER_DEVICE - .read = skel_read, - .write = skel_write, - .open = skel_open, - .release = skel_release, - .flush = skel_flush, - .llseek = noop_llseek, -#endif -}; - -/* - * usb class driver info in order to get a minor number from the usb core, - * and to have the device registered with the driver core - */ -#if USER_DEVICE -static struct usb_class_driver skel_class = { - .name = "lpvo_raw%d", - .fops = &skel_fops, - .minor_base = USB_SKEL_MINOR_BASE, -}; -#endif - -static int skel_probe(struct usb_interface *interface, - const struct usb_device_id *id) -{ - struct usb_skel *dev; - struct usb_endpoint_descriptor *bulk_in, *bulk_out; - int retval; - char *device_path; - - mutex_init(&minors_lock); /* required for handling minor numbers table */ - - /* allocate memory for our device state and initialize it */ - dev = kzalloc(sizeof(*dev), GFP_KERNEL); - if (!dev) - return -ENOMEM; - - kref_init(&dev->kref); - sema_init(&dev->limit_sem, WRITES_IN_FLIGHT); - mutex_init(&dev->io_mutex); - spin_lock_init(&dev->err_lock); - init_usb_anchor(&dev->submitted); - init_waitqueue_head(&dev->bulk_in_wait); - - dev->udev = usb_get_dev(interface_to_usbdev(interface)); - dev->interface = interface; - - /* set up the endpoint information */ - /* use only the first bulk-in and bulk-out endpoints */ - retval = usb_find_common_endpoints(interface->cur_altsetting, - &bulk_in, &bulk_out, NULL, NULL); - if (retval) { - dev_err(&interface->dev, - "Could not find both bulk-in and bulk-out endpoints\n"); - goto error; - } - - dev->bulk_in_size = usb_endpoint_maxp(bulk_in); - dev->bulk_in_endpoint_addr = bulk_in->bEndpointAddress; - dev->bulk_in_buffer = kmalloc(dev->bulk_in_size, GFP_KERNEL); - if (!dev->bulk_in_buffer) { - retval = -ENOMEM; - goto error; - } - dev->bulk_in_urb = usb_alloc_urb(0, GFP_KERNEL); - if (!dev->bulk_in_urb) { - retval = -ENOMEM; - goto error; - } - - dev->bulk_out_endpoint_addr = bulk_out->bEndpointAddress; - - /* save our data pointer in this interface device */ - usb_set_intfdata(interface, dev); - - /* let the world know */ - - device_path = kobject_get_path(&dev->udev->dev.kobj, GFP_KERNEL); - dev_dbg(&interface->dev, "New lpvo_usb_device -> bus: %d dev: %d path: %s\n", - dev->udev->bus->busnum, dev->udev->devnum, device_path); - kfree(device_path); - -#if USER_DEVICE - /* we can register the device now, as it is ready */ - retval = usb_register_dev(interface, &skel_class); - if (retval) { - /* something prevented us from registering this driver */ - dev_err(&interface->dev, - "Not able to get a minor for this device.\n"); - usb_set_intfdata(interface, NULL); - goto error; - } -#endif - - write_latency_timer(dev->udev); /* adjust the latency timer */ - - usb_gpib_init_module(interface); /* last, init the lpvo for this minor */ - - return 0; - -error: - /* this frees allocated memory */ - kref_put(&dev->kref, skel_delete); - - return retval; -} - -static void skel_disconnect(struct usb_interface *interface) -{ - struct usb_skel *dev; - int minor = interface->minor; - - usb_gpib_exit_module(minor); /* first, disactivate the lpvo */ - - dev = usb_get_intfdata(interface); - usb_set_intfdata(interface, NULL); - -#if USER_DEVICE - /* give back our minor */ - usb_deregister_dev(interface, &skel_class); -#endif - - /* prevent more I/O from starting */ - mutex_lock(&dev->io_mutex); - dev->interface = NULL; - mutex_unlock(&dev->io_mutex); - - usb_kill_anchored_urbs(&dev->submitted); - - /* decrement our usage count */ - kref_put(&dev->kref, skel_delete); -} - -static void skel_draw_down(struct usb_skel *dev) -{ - int time; - - time = usb_wait_anchor_empty_timeout(&dev->submitted, 1000); - if (!time) - usb_kill_anchored_urbs(&dev->submitted); - usb_kill_urb(dev->bulk_in_urb); -} - -static int skel_suspend(struct usb_interface *intf, pm_message_t message) -{ - struct usb_skel *dev = usb_get_intfdata(intf); - - if (!dev) - return 0; - skel_draw_down(dev); - return 0; -} - -static int skel_resume(struct usb_interface *intf) -{ - return 0; -} - -static int skel_pre_reset(struct usb_interface *intf) -{ - struct usb_skel *dev = usb_get_intfdata(intf); - - mutex_lock(&dev->io_mutex); - skel_draw_down(dev); - - return 0; -} - -static int skel_post_reset(struct usb_interface *intf) -{ - struct usb_skel *dev = usb_get_intfdata(intf); - - /* we are sure no URBs are active - no locking needed */ - dev->errors = -EPIPE; - mutex_unlock(&dev->io_mutex); - - return 0; -} - -static struct usb_driver skel_driver = { - .name = NAME, - .probe = skel_probe, - .disconnect = skel_disconnect, - .suspend = skel_suspend, - .resume = skel_resume, - .pre_reset = skel_pre_reset, - .post_reset = skel_post_reset, - .id_table = skel_table, - .supports_autosuspend = 1, -}; - -module_usb_driver(skel_driver); diff --git a/drivers/staging/gpib/nec7210/Makefile b/drivers/staging/gpib/nec7210/Makefile deleted file mode 100644 index 64330f2e89d1..000000000000 --- a/drivers/staging/gpib/nec7210/Makefile +++ /dev/null @@ -1,4 +0,0 @@ - -obj-$(CONFIG_GPIB_NEC7210) += nec7210.o - - diff --git a/drivers/staging/gpib/nec7210/board.h b/drivers/staging/gpib/nec7210/board.h deleted file mode 100644 index ac3fe38ade57..000000000000 --- a/drivers/staging/gpib/nec7210/board.h +++ /dev/null @@ -1,19 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -/*************************************************************************** - * copyright : (C) 2001, 2002 by Frank Mori Hess - ***************************************************************************/ - -#ifndef _GPIB_PCIIA_BOARD_H -#define _GPIB_PCIIA_BOARD_H - -#include "gpibP.h" -#include -#include -#include -#include - -#include "nec7210.h" - -#endif //_GPIB_PCIIA_BOARD_H - diff --git a/drivers/staging/gpib/nec7210/nec7210.c b/drivers/staging/gpib/nec7210/nec7210.c deleted file mode 100644 index bbf39367f5e4..000000000000 --- a/drivers/staging/gpib/nec7210/nec7210.c +++ /dev/null @@ -1,1121 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 - -/*************************************************************************** - * copyright : (C) 2001, 2002 by Frank Mori Hess - ***************************************************************************/ - -#define dev_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include "board.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("GPIB library code for NEC uPD7210"); - -int nec7210_enable_eos(struct gpib_board *board, struct nec7210_priv *priv, u8 eos_byte, - int compare_8_bits) -{ - write_byte(priv, eos_byte, EOSR); - priv->auxa_bits |= HR_REOS; - if (compare_8_bits) - priv->auxa_bits |= HR_BIN; - else - priv->auxa_bits &= ~HR_BIN; - write_byte(priv, priv->auxa_bits, AUXMR); - return 0; -} -EXPORT_SYMBOL(nec7210_enable_eos); - -void nec7210_disable_eos(struct gpib_board *board, struct nec7210_priv *priv) -{ - priv->auxa_bits &= ~HR_REOS; - write_byte(priv, priv->auxa_bits, AUXMR); -} -EXPORT_SYMBOL(nec7210_disable_eos); - -int nec7210_parallel_poll(struct gpib_board *board, struct nec7210_priv *priv, u8 *result) -{ - int ret; - - clear_bit(COMMAND_READY_BN, &priv->state); - - // execute parallel poll - write_byte(priv, AUX_EPP, AUXMR); - // wait for result FIXME: support timeouts - ret = wait_event_interruptible(board->wait, test_bit(COMMAND_READY_BN, &priv->state)); - if (ret) { - dev_dbg(board->gpib_dev, "gpib: parallel poll interrupted\n"); - return -ERESTARTSYS; - } - *result = read_byte(priv, CPTR); - - return 0; -} -EXPORT_SYMBOL(nec7210_parallel_poll); - -void nec7210_parallel_poll_configure(struct gpib_board *board, - struct nec7210_priv *priv, unsigned int configuration) -{ - write_byte(priv, PPR | configuration, AUXMR); -} -EXPORT_SYMBOL(nec7210_parallel_poll_configure); - -void nec7210_parallel_poll_response(struct gpib_board *board, struct nec7210_priv *priv, int ist) -{ - if (ist) - write_byte(priv, AUX_SPPF, AUXMR); - else - write_byte(priv, AUX_CPPF, AUXMR); -} -EXPORT_SYMBOL(nec7210_parallel_poll_response); -/* - * This is really only adequate for chips that do a 488.2 style reqt/reqf - * based on bit 6 of the SPMR (see chapter 11.3.3 of 488.2). For simpler chips that simply - * set rsv directly based on bit 6, we either need to do more hardware setup to expose - * the 488.2 capability (for example with NI chips), or we need to implement the - * 488.2 set srv state machine in the driver (if that is even viable). - */ -void nec7210_serial_poll_response(struct gpib_board *board, - struct nec7210_priv *priv, u8 status) -{ - unsigned long flags; - - spin_lock_irqsave(&board->spinlock, flags); - if (status & request_service_bit) { - priv->srq_pending = 1; - clear_bit(SPOLL_NUM, &board->status); - - } else { - priv->srq_pending = 0; - } - write_byte(priv, status, SPMR); - spin_unlock_irqrestore(&board->spinlock, flags); -} -EXPORT_SYMBOL(nec7210_serial_poll_response); - -u8 nec7210_serial_poll_status(struct gpib_board *board, struct nec7210_priv *priv) -{ - return read_byte(priv, SPSR); -} -EXPORT_SYMBOL(nec7210_serial_poll_status); - -int nec7210_primary_address(const struct gpib_board *board, struct nec7210_priv *priv, - unsigned int address) -{ - // put primary address in address0 - write_byte(priv, address & ADDRESS_MASK, ADR); - return 0; -} -EXPORT_SYMBOL(nec7210_primary_address); - -int nec7210_secondary_address(const struct gpib_board *board, struct nec7210_priv *priv, - unsigned int address, int enable) -{ - if (enable) { - // put secondary address in address1 - write_byte(priv, HR_ARS | (address & ADDRESS_MASK), ADR); - // go to address mode 2 - priv->reg_bits[ADMR] &= ~HR_ADM0; - priv->reg_bits[ADMR] |= HR_ADM1; - } else { - // disable address1 register - write_byte(priv, HR_ARS | HR_DT | HR_DL, ADR); - // go to address mode 1 - priv->reg_bits[ADMR] |= HR_ADM0; - priv->reg_bits[ADMR] &= ~HR_ADM1; - } - write_byte(priv, priv->reg_bits[ADMR], ADMR); - return 0; -} -EXPORT_SYMBOL(nec7210_secondary_address); - -static void update_talker_state(struct nec7210_priv *priv, unsigned int address_status_bits) -{ - if ((address_status_bits & HR_TA)) { - if ((address_status_bits & HR_NATN)) { - if (address_status_bits & HR_SPMS) - priv->talker_state = serial_poll_active; - else - priv->talker_state = talker_active; - } else { - priv->talker_state = talker_addressed; - } - } else { - priv->talker_state = talker_idle; - } -} - -static void update_listener_state(struct nec7210_priv *priv, unsigned int address_status_bits) -{ - if (address_status_bits & HR_LA) { - if ((address_status_bits & HR_NATN)) - priv->listener_state = listener_active; - else - priv->listener_state = listener_addressed; - } else { - priv->listener_state = listener_idle; - } -} - -unsigned int nec7210_update_status_nolock(struct gpib_board *board, struct nec7210_priv *priv) -{ - int address_status_bits; - u8 spoll_status; - - if (!priv) - return 0; - - address_status_bits = read_byte(priv, ADSR); - if (address_status_bits & HR_CIC) - set_bit(CIC_NUM, &board->status); - else - clear_bit(CIC_NUM, &board->status); - // check for talker/listener addressed - update_talker_state(priv, address_status_bits); - if (priv->talker_state == talker_active || priv->talker_state == talker_addressed) - set_bit(TACS_NUM, &board->status); - else - clear_bit(TACS_NUM, &board->status); - update_listener_state(priv, address_status_bits); - if (priv->listener_state == listener_active || - priv->listener_state == listener_addressed) - set_bit(LACS_NUM, &board->status); - else - clear_bit(LACS_NUM, &board->status); - if (address_status_bits & HR_NATN) - clear_bit(ATN_NUM, &board->status); - else - set_bit(ATN_NUM, &board->status); - spoll_status = nec7210_serial_poll_status(board, priv); - if (priv->srq_pending && (spoll_status & request_service_bit) == 0) { - priv->srq_pending = 0; - set_bit(SPOLL_NUM, &board->status); - } - - /* - * we rely on the interrupt handler to set the - * rest of the status bits - */ - - return board->status; -} -EXPORT_SYMBOL(nec7210_update_status_nolock); - -unsigned int nec7210_update_status(struct gpib_board *board, struct nec7210_priv *priv, - unsigned int clear_mask) -{ - unsigned long flags; - unsigned int retval; - - spin_lock_irqsave(&board->spinlock, flags); - board->status &= ~clear_mask; - retval = nec7210_update_status_nolock(board, priv); - spin_unlock_irqrestore(&board->spinlock, flags); - - return retval; -} -EXPORT_SYMBOL(nec7210_update_status); - -unsigned int nec7210_set_reg_bits(struct nec7210_priv *priv, unsigned int reg, - unsigned int mask, unsigned int bits) -{ - priv->reg_bits[reg] &= ~mask; - priv->reg_bits[reg] |= mask & bits; - write_byte(priv, priv->reg_bits[reg], reg); - return priv->reg_bits[reg]; -} -EXPORT_SYMBOL(nec7210_set_reg_bits); - -void nec7210_set_handshake_mode(struct gpib_board *board, struct nec7210_priv *priv, int mode) -{ - unsigned long flags; - - mode &= HR_HANDSHAKE_MASK; - - spin_lock_irqsave(&board->spinlock, flags); - if ((priv->auxa_bits & HR_HANDSHAKE_MASK) != mode) { - priv->auxa_bits &= ~HR_HANDSHAKE_MASK; - priv->auxa_bits |= mode; - write_byte(priv, priv->auxa_bits, AUXMR); - } - spin_unlock_irqrestore(&board->spinlock, flags); -} -EXPORT_SYMBOL(nec7210_set_handshake_mode); - -u8 nec7210_read_data_in(struct gpib_board *board, struct nec7210_priv *priv, int *end) -{ - unsigned long flags; - u8 data; - - spin_lock_irqsave(&board->spinlock, flags); - data = read_byte(priv, DIR); - clear_bit(READ_READY_BN, &priv->state); - if (test_and_clear_bit(RECEIVED_END_BN, &priv->state)) - *end = 1; - else - *end = 0; - spin_unlock_irqrestore(&board->spinlock, flags); - - return data; -} -EXPORT_SYMBOL(nec7210_read_data_in); - -int nec7210_take_control(struct gpib_board *board, struct nec7210_priv *priv, int syncronous) -{ - int i; - const int timeout = 100; - int retval = 0; - unsigned int adsr_bits = 0; - - if (syncronous) - write_byte(priv, AUX_TCS, AUXMR); - else - write_byte(priv, AUX_TCA, AUXMR); - // busy wait until ATN is asserted - for (i = 0; i < timeout; i++) { - adsr_bits = read_byte(priv, ADSR); - if ((adsr_bits & HR_NATN) == 0) - break; - udelay(1); - } - if (i == timeout) - return -ETIMEDOUT; - - clear_bit(WRITE_READY_BN, &priv->state); - - return retval; -} -EXPORT_SYMBOL(nec7210_take_control); - -int nec7210_go_to_standby(struct gpib_board *board, struct nec7210_priv *priv) -{ - int i; - const int timeout = 1000; - unsigned int adsr_bits = 0; - int retval = 0; - - write_byte(priv, AUX_GTS, AUXMR); - // busy wait until ATN is released - for (i = 0; i < timeout; i++) { - adsr_bits = read_byte(priv, ADSR); - if (adsr_bits & HR_NATN) - break; - udelay(1); - } - // if busy wait has failed, try sleeping - if (i == timeout) { - for (i = 0; i < HZ; i++) { - set_current_state(TASK_INTERRUPTIBLE); - if (schedule_timeout(1)) - return -ERESTARTSYS; - adsr_bits = read_byte(priv, ADSR); - if (adsr_bits & HR_NATN) - break; - } - if (i == HZ) - return -ETIMEDOUT; - } - - clear_bit(COMMAND_READY_BN, &priv->state); - return retval; -} -EXPORT_SYMBOL(nec7210_go_to_standby); - -int nec7210_request_system_control(struct gpib_board *board, struct nec7210_priv *priv, - int request_control) -{ - if (request_control == 0) { - write_byte(priv, AUX_CREN, AUXMR); - write_byte(priv, AUX_CIFC, AUXMR); - write_byte(priv, AUX_DSC, AUXMR); - } - return 0; -} -EXPORT_SYMBOL(nec7210_request_system_control); - -void nec7210_interface_clear(struct gpib_board *board, struct nec7210_priv *priv, int assert) -{ - if (assert) - write_byte(priv, AUX_SIFC, AUXMR); - else - write_byte(priv, AUX_CIFC, AUXMR); -} -EXPORT_SYMBOL(nec7210_interface_clear); - -void nec7210_remote_enable(struct gpib_board *board, struct nec7210_priv *priv, int enable) -{ - if (enable) - write_byte(priv, AUX_SREN, AUXMR); - else - write_byte(priv, AUX_CREN, AUXMR); -} -EXPORT_SYMBOL(nec7210_remote_enable); - -void nec7210_release_rfd_holdoff(struct gpib_board *board, struct nec7210_priv *priv) -{ - unsigned long flags; - - spin_lock_irqsave(&board->spinlock, flags); - if (test_bit(RFD_HOLDOFF_BN, &priv->state) && - test_bit(READ_READY_BN, &priv->state) == 0) { - write_byte(priv, AUX_FH, AUXMR); - clear_bit(RFD_HOLDOFF_BN, &priv->state); - } - spin_unlock_irqrestore(&board->spinlock, flags); -} -EXPORT_SYMBOL(nec7210_release_rfd_holdoff); - -int nec7210_t1_delay(struct gpib_board *board, struct nec7210_priv *priv, - unsigned int nano_sec) -{ - unsigned int retval; - - if (nano_sec <= 500) { - priv->auxb_bits |= HR_TRI; - retval = 500; - } else { - priv->auxb_bits &= ~HR_TRI; - retval = 2000; - } - write_byte(priv, priv->auxb_bits, AUXMR); - - return retval; -} -EXPORT_SYMBOL(nec7210_t1_delay); - -void nec7210_return_to_local(const struct gpib_board *board, struct nec7210_priv *priv) -{ - write_byte(priv, AUX_RTL, AUXMR); -} -EXPORT_SYMBOL(nec7210_return_to_local); - -static inline short nec7210_atn_has_changed(struct gpib_board *board, struct nec7210_priv *priv) -{ - short address_status_bits = read_byte(priv, ADSR); - - if (address_status_bits & HR_NATN) { - if (test_bit(ATN_NUM, &board->status)) - return 1; - else - return 0; - } else { - if (test_bit(ATN_NUM, &board->status)) - return 0; - else - return 1; - } - return -1; -} - -int nec7210_command(struct gpib_board *board, struct nec7210_priv *priv, u8 - *buffer, size_t length, size_t *bytes_written) -{ - int retval = 0; - unsigned long flags; - - *bytes_written = 0; - - clear_bit(BUS_ERROR_BN, &priv->state); - - while (*bytes_written < length) { - if (wait_event_interruptible(board->wait, - test_bit(COMMAND_READY_BN, &priv->state) || - test_bit(BUS_ERROR_BN, &priv->state) || - test_bit(TIMO_NUM, &board->status))) { - dev_dbg(board->gpib_dev, "command wait interrupted\n"); - retval = -ERESTARTSYS; - break; - } - if (test_bit(TIMO_NUM, &board->status)) - break; - if (test_and_clear_bit(BUS_ERROR_BN, &priv->state)) - break; - spin_lock_irqsave(&board->spinlock, flags); - clear_bit(COMMAND_READY_BN, &priv->state); - write_byte(priv, buffer[*bytes_written], CDOR); - spin_unlock_irqrestore(&board->spinlock, flags); - - ++(*bytes_written); - - if (need_resched()) - schedule(); - } - // wait for last byte to get sent - if (wait_event_interruptible(board->wait, test_bit(COMMAND_READY_BN, &priv->state) || - test_bit(BUS_ERROR_BN, &priv->state) || - test_bit(TIMO_NUM, &board->status))) - retval = -ERESTARTSYS; - - if (test_bit(TIMO_NUM, &board->status)) - retval = -ETIMEDOUT; - - if (test_and_clear_bit(BUS_ERROR_BN, &priv->state)) - retval = -EIO; - - return retval; -} -EXPORT_SYMBOL(nec7210_command); - -static int pio_read(struct gpib_board *board, struct nec7210_priv *priv, u8 *buffer, - size_t length, int *end, size_t *bytes_read) -{ - ssize_t retval = 0; - - *bytes_read = 0; - *end = 0; - - while (*bytes_read < length) { - if (wait_event_interruptible(board->wait, - test_bit(READ_READY_BN, &priv->state) || - test_bit(DEV_CLEAR_BN, &priv->state) || - test_bit(TIMO_NUM, &board->status))) { - retval = -ERESTARTSYS; - break; - } - if (test_bit(READ_READY_BN, &priv->state)) { - if (*bytes_read == 0) { - /* - * We set the handshake mode here because we know - * no new bytes will arrive (it has already arrived - * and is awaiting being read out of the chip) while we are changing - * modes. This ensures we can reliably keep track - * of the holdoff state. - */ - nec7210_set_handshake_mode(board, priv, HR_HLDA); - } - buffer[(*bytes_read)++] = nec7210_read_data_in(board, priv, end); - if (*end) - break; - } - if (test_bit(TIMO_NUM, &board->status)) { - retval = -ETIMEDOUT; - break; - } - if (test_bit(DEV_CLEAR_BN, &priv->state)) { - retval = -EINTR; - break; - } - - if (*bytes_read < length) - nec7210_release_rfd_holdoff(board, priv); - - if (need_resched()) - schedule(); - } - return retval; -} - -#ifdef NEC_DMA -static ssize_t __dma_read(struct gpib_board *board, struct nec7210_priv *priv, size_t length) -{ - ssize_t retval = 0; - size_t count = 0; - unsigned long flags, dma_irq_flags; - - if (length == 0) - return 0; - - spin_lock_irqsave(&board->spinlock, flags); - - dma_irq_flags = claim_dma_lock(); - disable_dma(priv->dma_channel); - /* program dma controller */ - clear_dma_ff(priv->dma_channel); - set_dma_count(priv->dma_channel, length); - set_dma_addr(priv->dma_channel, priv->dma_buffer_addr); - set_dma_mode(priv->dma_channel, DMA_MODE_READ); - release_dma_lock(dma_irq_flags); - - enable_dma(priv->dma_channel); - - set_bit(DMA_READ_IN_PROGRESS_BN, &priv->state); - clear_bit(READ_READY_BN, &priv->state); - - // enable nec7210 dma - nec7210_set_reg_bits(priv, IMR2, HR_DMAI, HR_DMAI); - - spin_unlock_irqrestore(&board->spinlock, flags); - - // wait for data to transfer - if (wait_event_interruptible(board->wait, - test_bit(DMA_READ_IN_PROGRESS_BN, &priv->state) == 0 || - test_bit(DEV_CLEAR_BN, &priv->state) || - test_bit(TIMO_NUM, &board->status))) - retval = -ERESTARTSYS; - - if (test_bit(TIMO_NUM, &board->status)) - retval = -ETIMEDOUT; - if (test_bit(DEV_CLEAR_BN, &priv->state)) - retval = -EINTR; - - // disable nec7210 dma - nec7210_set_reg_bits(priv, IMR2, HR_DMAI, 0); - - // record how many bytes we transferred - flags = claim_dma_lock(); - clear_dma_ff(priv->dma_channel); - disable_dma(priv->dma_channel); - count += length - get_dma_residue(priv->dma_channel); - release_dma_lock(flags); - - return retval ? retval : count; -} - -static ssize_t dma_read(struct gpib_board *board, struct nec7210_priv *priv, u8 *buffer, - size_t length) -{ - size_t remain = length; - size_t transfer_size; - ssize_t retval = 0; - - while (remain > 0) { - transfer_size = (priv->dma_buffer_length < remain) ? - priv->dma_buffer_length : remain; - retval = __dma_read(board, priv, transfer_size); - if (retval < 0) - break; - memcpy(buffer, priv->dma_buffer, transfer_size); - remain -= retval; - buffer += retval; - if (test_bit(RECEIVED_END_BN, &priv->state)) - break; - } - - if (retval < 0) - return retval; - - return length - remain; -} -#endif - -int nec7210_read(struct gpib_board *board, struct nec7210_priv *priv, u8 *buffer, - size_t length, int *end, size_t *bytes_read) -{ - ssize_t retval = 0; - - *end = 0; - *bytes_read = 0; - - if (length == 0) - return 0; - - clear_bit(DEV_CLEAR_BN, &priv->state); // XXX wrong - - nec7210_release_rfd_holdoff(board, priv); - - retval = pio_read(board, priv, buffer, length, end, bytes_read); - - return retval; -} -EXPORT_SYMBOL(nec7210_read); - -static int pio_write_wait(struct gpib_board *board, struct nec7210_priv *priv, - short wake_on_lacs, short wake_on_atn, short wake_on_bus_error) -{ - // wait until byte is ready to be sent - if (wait_event_interruptible(board->wait, - (test_bit(TACS_NUM, &board->status) && - test_bit(WRITE_READY_BN, &priv->state)) || - test_bit(DEV_CLEAR_BN, &priv->state) || - (wake_on_bus_error && test_bit(BUS_ERROR_BN, &priv->state)) || - (wake_on_lacs && test_bit(LACS_NUM, &board->status)) || - (wake_on_atn && test_bit(ATN_NUM, &board->status)) || - test_bit(TIMO_NUM, &board->status))) - return -ERESTARTSYS; - - if (test_bit(TIMO_NUM, &board->status)) - return -ETIMEDOUT; - - if (test_bit(DEV_CLEAR_BN, &priv->state)) - return -EINTR; - - if (wake_on_bus_error && test_and_clear_bit(BUS_ERROR_BN, &priv->state)) - return -EIO; - - return 0; -} - -static int pio_write(struct gpib_board *board, struct nec7210_priv *priv, u8 *buffer, - size_t length, size_t *bytes_written) -{ - size_t last_count = 0; - ssize_t retval = 0; - unsigned long flags; - const int max_bus_errors = (length > 1000) ? length : 1000; - int bus_error_count = 0; - *bytes_written = 0; - - clear_bit(BUS_ERROR_BN, &priv->state); - - while (*bytes_written < length) { - if (need_resched()) - schedule(); - - retval = pio_write_wait(board, priv, 0, 0, priv->type == NEC7210); - if (retval == -EIO) { - /* resend last byte on bus error */ - *bytes_written = last_count; - /* - * we can get unrecoverable bus errors, - * so give up after a while - */ - bus_error_count++; - if (bus_error_count > max_bus_errors) - return retval; - continue; - } else { - if (retval < 0) - return retval; - } - spin_lock_irqsave(&board->spinlock, flags); - clear_bit(BUS_ERROR_BN, &priv->state); - clear_bit(WRITE_READY_BN, &priv->state); - last_count = *bytes_written; - write_byte(priv, buffer[(*bytes_written)++], CDOR); - spin_unlock_irqrestore(&board->spinlock, flags); - } - retval = pio_write_wait(board, priv, 1, 1, priv->type == NEC7210); - return retval; -} - -#ifdef NEC_DMA -static ssize_t __dma_write(struct gpib_board *board, struct nec7210_priv *priv, dma_addr_t address, - size_t length) -{ - unsigned long flags, dma_irq_flags; - int residue = 0; - int retval = 0; - - spin_lock_irqsave(&board->spinlock, flags); - - /* program dma controller */ - dma_irq_flags = claim_dma_lock(); - disable_dma(priv->dma_channel); - clear_dma_ff(priv->dma_channel); - set_dma_count(priv->dma_channel, length); - set_dma_addr(priv->dma_channel, address); - set_dma_mode(priv->dma_channel, DMA_MODE_WRITE); - enable_dma(priv->dma_channel); - release_dma_lock(dma_irq_flags); - - // enable board's dma for output - nec7210_set_reg_bits(priv, IMR2, HR_DMAO, HR_DMAO); - - clear_bit(WRITE_READY_BN, &priv->state); - set_bit(DMA_WRITE_IN_PROGRESS_BN, &priv->state); - - spin_unlock_irqrestore(&board->spinlock, flags); - - // suspend until message is sent - if (wait_event_interruptible(board->wait, - test_bit(DMA_WRITE_IN_PROGRESS_BN, &priv->state) == 0 || - test_bit(BUS_ERROR_BN, &priv->state) || - test_bit(DEV_CLEAR_BN, &priv->state) || - test_bit(TIMO_NUM, &board->status))) - retval = -ERESTARTSYS; - - if (test_bit(TIMO_NUM, &board->status)) - retval = -ETIMEDOUT; - if (test_and_clear_bit(DEV_CLEAR_BN, &priv->state)) - retval = -EINTR; - if (test_and_clear_bit(BUS_ERROR_BN, &priv->state)) - retval = -EIO; - - // disable board's dma - nec7210_set_reg_bits(priv, IMR2, HR_DMAO, 0); - - dma_irq_flags = claim_dma_lock(); - clear_dma_ff(priv->dma_channel); - disable_dma(priv->dma_channel); - residue = get_dma_residue(priv->dma_channel); - release_dma_lock(dma_irq_flags); - - if (residue) - retval = -EPIPE; - - return retval ? retval : length; -} - -static ssize_t dma_write(struct gpib_board *board, struct nec7210_priv *priv, u8 *buffer, - size_t length) -{ - size_t remain = length; - size_t transfer_size; - ssize_t retval = 0; - - while (remain > 0) { - transfer_size = (priv->dma_buffer_length < remain) ? - priv->dma_buffer_length : remain; - memcpy(priv->dma_buffer, buffer, transfer_size); - retval = __dma_write(board, priv, priv->dma_buffer_addr, transfer_size); - if (retval < 0) - break; - remain -= retval; - buffer += retval; - } - - if (retval < 0) - return retval; - - return length - remain; -} -#endif -int nec7210_write(struct gpib_board *board, struct nec7210_priv *priv, - u8 *buffer, size_t length, int send_eoi, - size_t *bytes_written) -{ - int retval = 0; - - *bytes_written = 0; - - clear_bit(DEV_CLEAR_BN, &priv->state); // XXX - - if (send_eoi) - length-- ; // save the last byte for sending EOI - - if (length > 0) { - // isa dma transfer - if (0 /*priv->dma_channel*/) { -/* - * dma writes are unreliable since they can't recover from bus errors - * (which happen when ATN is asserted in the middle of a write) - */ -#ifdef NEC_DMA - retval = dma_write(board, priv, buffer, length); - if (retval < 0) - return retval; - count += retval; -#endif - } else { // PIO transfer - size_t num_bytes; - - retval = pio_write(board, priv, buffer, length, &num_bytes); - - *bytes_written += num_bytes; - if (retval < 0) - return retval; - } - } - if (send_eoi) { - size_t num_bytes; - - /* - * We need to wait to make sure we will immediately be able to write the data byte - * into the chip before sending the associated AUX_SEOI command. This is really - * only needed for length==1 since otherwise the earlier calls to pio_write - * will have dont the wait already. - */ - retval = pio_write_wait(board, priv, 0, 0, priv->type == NEC7210); - if (retval < 0) - return retval; - /*send EOI */ - write_byte(priv, AUX_SEOI, AUXMR); - - retval = pio_write(board, priv, &buffer[*bytes_written], 1, &num_bytes); - *bytes_written += num_bytes; - if (retval < 0) - return retval; - } - - return retval; -} -EXPORT_SYMBOL(nec7210_write); - -/* - * interrupt service routine - */ -irqreturn_t nec7210_interrupt(struct gpib_board *board, struct nec7210_priv *priv) -{ - int status1, status2; - - // read interrupt status (also clears status) - status1 = read_byte(priv, ISR1); - status2 = read_byte(priv, ISR2); - - return nec7210_interrupt_have_status(board, priv, status1, status2); -} -EXPORT_SYMBOL(nec7210_interrupt); - -irqreturn_t nec7210_interrupt_have_status(struct gpib_board *board, - struct nec7210_priv *priv, int status1, int status2) -{ -#ifdef NEC_DMA - unsigned long dma_flags; -#endif - int retval = IRQ_NONE; - - // record service request in status - if (status2 & HR_SRQI) - set_bit(SRQI_NUM, &board->status); - - // change in lockout status - if (status2 & HR_LOKC) { - if (status2 & HR_LOK) - set_bit(LOK_NUM, &board->status); - else - clear_bit(LOK_NUM, &board->status); - } - - // change in remote status - if (status2 & HR_REMC) { - if (status2 & HR_REM) - set_bit(REM_NUM, &board->status); - else - clear_bit(REM_NUM, &board->status); - } - - // record reception of END - if (status1 & HR_END) { - set_bit(RECEIVED_END_BN, &priv->state); - if ((priv->auxa_bits & HR_HANDSHAKE_MASK) == HR_HLDE) - set_bit(RFD_HOLDOFF_BN, &priv->state); - } - - // get incoming data in PIO mode - if ((status1 & HR_DI)) { - set_bit(READ_READY_BN, &priv->state); - if ((priv->auxa_bits & HR_HANDSHAKE_MASK) == HR_HLDA) - set_bit(RFD_HOLDOFF_BN, &priv->state); - } -#ifdef NEC_DMA - // check for dma read transfer complete - if (test_bit(DMA_READ_IN_PROGRESS_BN, &priv->state)) { - dma_flags = claim_dma_lock(); - disable_dma(priv->dma_channel); - clear_dma_ff(priv->dma_channel); - if ((status1 & HR_END) || get_dma_residue(priv->dma_channel) == 0) - clear_bit(DMA_READ_IN_PROGRESS_BN, &priv->state); - else - enable_dma(priv->dma_channel); - release_dma_lock(dma_flags); - } -#endif - if ((status1 & HR_DO)) { - if (test_bit(DMA_WRITE_IN_PROGRESS_BN, &priv->state) == 0) - set_bit(WRITE_READY_BN, &priv->state); -#ifdef NEC_DMA - if (test_bit(DMA_WRITE_IN_PROGRESS_BN, &priv->state)) { // write data, isa dma mode - // check if dma transfer is complete - dma_flags = claim_dma_lock(); - disable_dma(priv->dma_channel); - clear_dma_ff(priv->dma_channel); - if (get_dma_residue(priv->dma_channel) == 0) { - clear_bit(DMA_WRITE_IN_PROGRESS_BN, &priv->state); - // XXX race? byte may still be in CDOR reg - } else { - clear_bit(WRITE_READY_BN, &priv->state); - enable_dma(priv->dma_channel); - } - release_dma_lock(dma_flags); - } -#endif - } - - // outgoing command can be sent - if (status2 & HR_CO) - set_bit(COMMAND_READY_BN, &priv->state); - - // command pass through received - if (status1 & HR_CPT) - write_byte(priv, AUX_NVAL, AUXMR); - - if (status1 & HR_ERR) - set_bit(BUS_ERROR_BN, &priv->state); - - if (status1 & HR_DEC) { - unsigned short address_status_bits = read_byte(priv, ADSR); - - // ignore device clear events if we are controller in charge - if ((address_status_bits & HR_CIC) == 0) { - push_gpib_event(board, EVENT_DEV_CLR); - set_bit(DEV_CLEAR_BN, &priv->state); - } - } - - if (status1 & HR_DET) - push_gpib_event(board, EVENT_DEV_TRG); - - // Addressing status has changed - if (status2 & HR_ADSC) - set_bit(ADR_CHANGE_BN, &priv->state); - - if ((status1 & priv->reg_bits[IMR1]) || - (status2 & (priv->reg_bits[IMR2] & IMR2_ENABLE_INTR_MASK)) || - nec7210_atn_has_changed(board, priv)) { - nec7210_update_status_nolock(board, priv); - dev_dbg(board->gpib_dev, "minor %i, stat %lx, isr1 0x%x, imr1 0x%x, isr2 0x%x, imr2 0x%x\n", - board->minor, board->status, status1, priv->reg_bits[IMR1], status2, - priv->reg_bits[IMR2]); - wake_up_interruptible(&board->wait); /* wake up sleeping process */ - retval = IRQ_HANDLED; - } - - return retval; -} -EXPORT_SYMBOL(nec7210_interrupt_have_status); - -void nec7210_board_reset(struct nec7210_priv *priv, const struct gpib_board *board) -{ - /* 7210 chip reset */ - write_byte(priv, AUX_CR, AUXMR); - - /* disable all interrupts */ - priv->reg_bits[IMR1] = 0; - write_byte(priv, priv->reg_bits[IMR1], IMR1); - priv->reg_bits[IMR2] = 0; - write_byte(priv, priv->reg_bits[IMR2], IMR2); - write_byte(priv, 0, SPMR); - - /* clear registers by reading */ - read_byte(priv, CPTR); - read_byte(priv, ISR1); - read_byte(priv, ISR2); - - /* parallel poll unconfigure */ - write_byte(priv, PPR | HR_PPU, AUXMR); - - priv->reg_bits[ADMR] = HR_TRM0 | HR_TRM1; - - priv->auxa_bits = AUXRA | HR_HLDA; - write_byte(priv, priv->auxa_bits, AUXMR); - - write_byte(priv, AUXRE | 0, AUXMR); - - /* set INT pin to active high, enable command pass through of unknown commands */ - priv->auxb_bits = AUXRB | HR_CPTE; - write_byte(priv, priv->auxb_bits, AUXMR); - write_byte(priv, AUXRE, AUXMR); -} -EXPORT_SYMBOL(nec7210_board_reset); - -void nec7210_board_online(struct nec7210_priv *priv, const struct gpib_board *board) -{ - /* set GPIB address */ - nec7210_primary_address(board, priv, board->pad); - nec7210_secondary_address(board, priv, board->sad, board->sad >= 0); - - /* enable interrupts */ - priv->reg_bits[IMR1] = HR_ERRIE | HR_DECIE | HR_ENDIE | - HR_DETIE | HR_CPTIE | HR_DOIE | HR_DIIE; - priv->reg_bits[IMR2] = IMR2_ENABLE_INTR_MASK; - write_byte(priv, priv->reg_bits[IMR1], IMR1); - write_byte(priv, priv->reg_bits[IMR2], IMR2); - - write_byte(priv, AUX_PON, AUXMR); -} -EXPORT_SYMBOL(nec7210_board_online); - -#ifdef CONFIG_HAS_IOPORT -/* wrappers for io */ -u8 nec7210_ioport_read_byte(struct nec7210_priv *priv, unsigned int register_num) -{ - return inb(priv->iobase + register_num * priv->offset); -} -EXPORT_SYMBOL(nec7210_ioport_read_byte); - -void nec7210_ioport_write_byte(struct nec7210_priv *priv, u8 data, unsigned int register_num) -{ - if (register_num == AUXMR) - /* - * locking makes absolutely sure noone accesses the - * AUXMR register faster than once per microsecond - */ - nec7210_locking_ioport_write_byte(priv, data, register_num); - else - outb(data, priv->iobase + register_num * priv->offset); -} -EXPORT_SYMBOL(nec7210_ioport_write_byte); - -/* locking variants of io wrappers, for chips that page-in registers */ -u8 nec7210_locking_ioport_read_byte(struct nec7210_priv *priv, unsigned int register_num) -{ - u8 retval; - unsigned long flags; - - spin_lock_irqsave(&priv->register_page_lock, flags); - retval = inb(priv->iobase + register_num * priv->offset); - spin_unlock_irqrestore(&priv->register_page_lock, flags); - return retval; -} -EXPORT_SYMBOL(nec7210_locking_ioport_read_byte); - -void nec7210_locking_ioport_write_byte(struct nec7210_priv *priv, u8 data, - unsigned int register_num) -{ - unsigned long flags; - - spin_lock_irqsave(&priv->register_page_lock, flags); - if (register_num == AUXMR) - udelay(1); - outb(data, priv->iobase + register_num * priv->offset); - spin_unlock_irqrestore(&priv->register_page_lock, flags); -} -EXPORT_SYMBOL(nec7210_locking_ioport_write_byte); -#endif - -u8 nec7210_iomem_read_byte(struct nec7210_priv *priv, unsigned int register_num) -{ - return readb(priv->mmiobase + register_num * priv->offset); -} -EXPORT_SYMBOL(nec7210_iomem_read_byte); - -void nec7210_iomem_write_byte(struct nec7210_priv *priv, u8 data, unsigned int register_num) -{ - if (register_num == AUXMR) - /* - * locking makes absolutely sure noone accesses the - * AUXMR register faster than once per microsecond - */ - nec7210_locking_iomem_write_byte(priv, data, register_num); - else - writeb(data, priv->mmiobase + register_num * priv->offset); -} -EXPORT_SYMBOL(nec7210_iomem_write_byte); - -u8 nec7210_locking_iomem_read_byte(struct nec7210_priv *priv, unsigned int register_num) -{ - u8 retval; - unsigned long flags; - - spin_lock_irqsave(&priv->register_page_lock, flags); - retval = readb(priv->mmiobase + register_num * priv->offset); - spin_unlock_irqrestore(&priv->register_page_lock, flags); - return retval; -} -EXPORT_SYMBOL(nec7210_locking_iomem_read_byte); - -void nec7210_locking_iomem_write_byte(struct nec7210_priv *priv, u8 data, - unsigned int register_num) -{ - unsigned long flags; - - spin_lock_irqsave(&priv->register_page_lock, flags); - if (register_num == AUXMR) - udelay(1); - writeb(data, priv->mmiobase + register_num * priv->offset); - spin_unlock_irqrestore(&priv->register_page_lock, flags); -} -EXPORT_SYMBOL(nec7210_locking_iomem_write_byte); - -static int __init nec7210_init_module(void) -{ - return 0; -} - -static void __exit nec7210_exit_module(void) -{ -} - -module_init(nec7210_init_module); -module_exit(nec7210_exit_module); diff --git a/drivers/staging/gpib/ni_usb/Makefile b/drivers/staging/gpib/ni_usb/Makefile deleted file mode 100644 index 469c5d16add3..000000000000 --- a/drivers/staging/gpib/ni_usb/Makefile +++ /dev/null @@ -1,4 +0,0 @@ - -obj-$(CONFIG_GPIB_NI_USB) += ni_usb_gpib.o - - diff --git a/drivers/staging/gpib/ni_usb/ni_usb_gpib.c b/drivers/staging/gpib/ni_usb/ni_usb_gpib.c deleted file mode 100644 index 1f8412de9fa3..000000000000 --- a/drivers/staging/gpib/ni_usb/ni_usb_gpib.c +++ /dev/null @@ -1,2678 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 - -/*************************************************************************** - * driver for National Instruments usb to gpib adapters - * copyright : (C) 2004 by Frank Mori Hess - ***************************************************************************/ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -#define dev_fmt pr_fmt -#define DRV_NAME KBUILD_MODNAME - -#include -#include -#include -#include "ni_usb_gpib.h" -#include "gpibP.h" -#include "nec7210.h" -#include "tnt4882_registers.h" - -MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("GPIB driver for National Instruments USB devices"); - -#define MAX_NUM_NI_USB_INTERFACES 128 -static struct usb_interface *ni_usb_driver_interfaces[MAX_NUM_NI_USB_INTERFACES]; - -static int ni_usb_parse_status_block(const u8 *buffer, struct ni_usb_status_block *status); -static int ni_usb_set_interrupt_monitor(struct gpib_board *board, unsigned int monitored_bits); -static void ni_usb_stop(struct ni_usb_priv *ni_priv); - -static DEFINE_MUTEX(ni_usb_hotplug_lock); - -// calculates a reasonable timeout in that can be passed to usb functions -static inline unsigned long ni_usb_timeout_msecs(unsigned int usec) -{ - if (usec == 0) - return 0; - return 2000 + usec / 500; -}; - -// returns timeout code byte for use in ni-usb-b instructions -static unsigned short ni_usb_timeout_code(unsigned int usec) -{ - if (usec == 0) - return 0xf0; - else if (usec <= 10) - return 0xf1; - else if (usec <= 30) - return 0xf2; - else if (usec <= 100) - return 0xf3; - else if (usec <= 300) - return 0xf4; - else if (usec <= 1000) - return 0xf5; - else if (usec <= 3000) - return 0xf6; - else if (usec <= 10000) - return 0xf7; - else if (usec <= 30000) - return 0xf8; - else if (usec <= 100000) - return 0xf9; - else if (usec <= 300000) - return 0xfa; - else if (usec <= 1000000) - return 0xfb; - else if (usec <= 3000000) - return 0xfc; - else if (usec <= 10000000) - return 0xfd; - else if (usec <= 30000000) - return 0xfe; - else if (usec <= 100000000) - return 0xff; - else if (usec <= 300000000) - return 0x01; - /* - * NI driver actually uses 0xff for timeout T1000s, which is a bug in their code. - * I've verified on a usb-b that a code of 0x2 is correct for a 1000 sec timeout - */ - else if (usec <= 1000000000) - return 0x02; - pr_err("bug? usec is greater than 1e9\n"); - return 0xf0; -} - -static void ni_usb_bulk_complete(struct urb *urb) -{ - struct ni_usb_urb_ctx *context = urb->context; - - complete(&context->complete); -} - -static void ni_usb_timeout_handler(struct timer_list *t) -{ - struct ni_usb_priv *ni_priv = timer_container_of(ni_priv, t, - bulk_timer); - struct ni_usb_urb_ctx *context = &ni_priv->context; - - context->timed_out = 1; - complete(&context->complete); -}; - -// I'm using nonblocking loosely here, it only means -EAGAIN can be returned in certain cases -static int ni_usb_nonblocking_send_bulk_msg(struct ni_usb_priv *ni_priv, void *data, - int data_length, int *actual_data_length, - int timeout_msecs) -{ - struct usb_device *usb_dev; - int retval; - unsigned int out_pipe; - struct ni_usb_urb_ctx *context = &ni_priv->context; - - *actual_data_length = 0; - mutex_lock(&ni_priv->bulk_transfer_lock); - if (!ni_priv->bus_interface) { - mutex_unlock(&ni_priv->bulk_transfer_lock); - return -ENODEV; - } - if (ni_priv->bulk_urb) { - mutex_unlock(&ni_priv->bulk_transfer_lock); - return -EAGAIN; - } - ni_priv->bulk_urb = usb_alloc_urb(0, GFP_KERNEL); - if (!ni_priv->bulk_urb) { - mutex_unlock(&ni_priv->bulk_transfer_lock); - return -ENOMEM; - } - usb_dev = interface_to_usbdev(ni_priv->bus_interface); - out_pipe = usb_sndbulkpipe(usb_dev, ni_priv->bulk_out_endpoint); - init_completion(&context->complete); - context->timed_out = 0; - usb_fill_bulk_urb(ni_priv->bulk_urb, usb_dev, out_pipe, data, data_length, - &ni_usb_bulk_complete, context); - - if (timeout_msecs) - mod_timer(&ni_priv->bulk_timer, jiffies + msecs_to_jiffies(timeout_msecs)); - - retval = usb_submit_urb(ni_priv->bulk_urb, GFP_KERNEL); - if (retval) { - timer_delete_sync(&ni_priv->bulk_timer); - usb_free_urb(ni_priv->bulk_urb); - ni_priv->bulk_urb = NULL; - dev_err(&usb_dev->dev, "failed to submit bulk out urb, retval=%i\n", - retval); - mutex_unlock(&ni_priv->bulk_transfer_lock); - return retval; - } - mutex_unlock(&ni_priv->bulk_transfer_lock); - wait_for_completion(&context->complete); // wait for ni_usb_bulk_complete - if (context->timed_out) { - usb_kill_urb(ni_priv->bulk_urb); - dev_err(&usb_dev->dev, "killed urb due to timeout\n"); - retval = -ETIMEDOUT; - } else { - retval = ni_priv->bulk_urb->status; - } - - timer_delete_sync(&ni_priv->bulk_timer); - *actual_data_length = ni_priv->bulk_urb->actual_length; - mutex_lock(&ni_priv->bulk_transfer_lock); - usb_free_urb(ni_priv->bulk_urb); - ni_priv->bulk_urb = NULL; - mutex_unlock(&ni_priv->bulk_transfer_lock); - return retval; -} - -static int ni_usb_send_bulk_msg(struct ni_usb_priv *ni_priv, void *data, int data_length, - int *actual_data_length, int timeout_msecs) -{ - int retval; - int timeout_msecs_remaining = timeout_msecs; - - retval = ni_usb_nonblocking_send_bulk_msg(ni_priv, data, data_length, actual_data_length, - timeout_msecs_remaining); - while (retval == -EAGAIN && (timeout_msecs == 0 || timeout_msecs_remaining > 0)) { - usleep_range(1000, 1500); - retval = ni_usb_nonblocking_send_bulk_msg(ni_priv, data, data_length, - actual_data_length, - timeout_msecs_remaining); - if (timeout_msecs != 0) - --timeout_msecs_remaining; - } - if (timeout_msecs != 0 && timeout_msecs_remaining <= 0) - return -ETIMEDOUT; - return retval; -} - -// I'm using nonblocking loosely here, it only means -EAGAIN can be returned in certain cases -static int ni_usb_nonblocking_receive_bulk_msg(struct ni_usb_priv *ni_priv, - void *data, int data_length, - int *actual_data_length, int timeout_msecs, - int interruptible) -{ - struct usb_device *usb_dev; - int retval; - unsigned int in_pipe; - struct ni_usb_urb_ctx *context = &ni_priv->context; - - *actual_data_length = 0; - mutex_lock(&ni_priv->bulk_transfer_lock); - if (!ni_priv->bus_interface) { - mutex_unlock(&ni_priv->bulk_transfer_lock); - return -ENODEV; - } - if (ni_priv->bulk_urb) { - mutex_unlock(&ni_priv->bulk_transfer_lock); - return -EAGAIN; - } - ni_priv->bulk_urb = usb_alloc_urb(0, GFP_KERNEL); - if (!ni_priv->bulk_urb) { - mutex_unlock(&ni_priv->bulk_transfer_lock); - return -ENOMEM; - } - usb_dev = interface_to_usbdev(ni_priv->bus_interface); - in_pipe = usb_rcvbulkpipe(usb_dev, ni_priv->bulk_in_endpoint); - init_completion(&context->complete); - context->timed_out = 0; - usb_fill_bulk_urb(ni_priv->bulk_urb, usb_dev, in_pipe, data, data_length, - &ni_usb_bulk_complete, context); - - if (timeout_msecs) - mod_timer(&ni_priv->bulk_timer, jiffies + msecs_to_jiffies(timeout_msecs)); - - retval = usb_submit_urb(ni_priv->bulk_urb, GFP_KERNEL); - if (retval) { - timer_delete_sync(&ni_priv->bulk_timer); - usb_free_urb(ni_priv->bulk_urb); - ni_priv->bulk_urb = NULL; - dev_err(&usb_dev->dev, "failed to submit bulk in urb, retval=%i\n", retval); - mutex_unlock(&ni_priv->bulk_transfer_lock); - return retval; - } - mutex_unlock(&ni_priv->bulk_transfer_lock); - if (interruptible) { - if (wait_for_completion_interruptible(&context->complete)) { - /* - * If we got interrupted by a signal while - * waiting for the usb gpib to respond, we - * should send a stop command so it will - * finish up with whatever it was doing and - * send its response now. - */ - ni_usb_stop(ni_priv); - retval = -ERESTARTSYS; - /* - * now do an uninterruptible wait, it shouldn't take long - * for the board to respond now. - */ - wait_for_completion(&context->complete); - } - } else { - wait_for_completion(&context->complete); - } - if (context->timed_out) { - usb_kill_urb(ni_priv->bulk_urb); - dev_err(&usb_dev->dev, "killed urb due to timeout\n"); - retval = -ETIMEDOUT; - } else { - if (ni_priv->bulk_urb->status) - retval = ni_priv->bulk_urb->status; - } - timer_delete_sync(&ni_priv->bulk_timer); - *actual_data_length = ni_priv->bulk_urb->actual_length; - mutex_lock(&ni_priv->bulk_transfer_lock); - usb_free_urb(ni_priv->bulk_urb); - ni_priv->bulk_urb = NULL; - mutex_unlock(&ni_priv->bulk_transfer_lock); - return retval; -} - -static int ni_usb_receive_bulk_msg(struct ni_usb_priv *ni_priv, void *data, - int data_length, int *actual_data_length, int timeout_msecs, - int interruptible) -{ - int retval; - int timeout_msecs_remaining = timeout_msecs; - - retval = ni_usb_nonblocking_receive_bulk_msg(ni_priv, data, data_length, - actual_data_length, timeout_msecs_remaining, - interruptible); - while (retval == -EAGAIN && (timeout_msecs == 0 || timeout_msecs_remaining > 0)) { - usleep_range(1000, 1500); - retval = ni_usb_nonblocking_receive_bulk_msg(ni_priv, data, data_length, - actual_data_length, - timeout_msecs_remaining, - interruptible); - if (timeout_msecs != 0) - --timeout_msecs_remaining; - } - if (timeout_msecs && timeout_msecs_remaining <= 0) - return -ETIMEDOUT; - return retval; -} - -static int ni_usb_receive_control_msg(struct ni_usb_priv *ni_priv, __u8 request, - __u8 requesttype, __u16 value, __u16 index, - void *data, __u16 size, int timeout_msecs) -{ - struct usb_device *usb_dev; - int retval; - unsigned int in_pipe; - - mutex_lock(&ni_priv->control_transfer_lock); - if (!ni_priv->bus_interface) { - mutex_unlock(&ni_priv->control_transfer_lock); - return -ENODEV; - } - usb_dev = interface_to_usbdev(ni_priv->bus_interface); - in_pipe = usb_rcvctrlpipe(usb_dev, 0); - retval = usb_control_msg(usb_dev, in_pipe, request, requesttype, value, index, data, - size, timeout_msecs); - mutex_unlock(&ni_priv->control_transfer_lock); - return retval; -} - -static void ni_usb_soft_update_status(struct gpib_board *board, unsigned int ni_usb_ibsta, - unsigned int clear_mask) -{ - static const unsigned int ni_usb_ibsta_mask = SRQI | ATN | CIC | REM | LACS | TACS | LOK; - - struct ni_usb_priv *ni_priv = board->private_data; - struct usb_device *usb_dev = interface_to_usbdev(ni_priv->bus_interface); - unsigned int need_monitoring_bits = ni_usb_ibsta_monitor_mask; - unsigned long flags; - - board->status &= ~clear_mask; - board->status &= ~ni_usb_ibsta_mask; - board->status |= ni_usb_ibsta & ni_usb_ibsta_mask; - if (ni_usb_ibsta & DCAS) - push_gpib_event(board, EVENT_DEV_CLR); - if (ni_usb_ibsta & DTAS) - push_gpib_event(board, EVENT_DEV_TRG); - - spin_lock_irqsave(&board->spinlock, flags); -/* remove set status bits from monitored set why ?***/ - ni_priv->monitored_ibsta_bits &= ~ni_usb_ibsta; - need_monitoring_bits &= ~ni_priv->monitored_ibsta_bits; /* mm - monitored set */ - spin_unlock_irqrestore(&board->spinlock, flags); - dev_dbg(&usb_dev->dev, "need_monitoring_bits=0x%x\n", need_monitoring_bits); - - if (need_monitoring_bits & ~ni_usb_ibsta) - ni_usb_set_interrupt_monitor(board, ni_usb_ibsta_monitor_mask); - else if (need_monitoring_bits & ni_usb_ibsta) - wake_up_interruptible(&board->wait); - - dev_dbg(&usb_dev->dev, "ibsta=0x%x\n", ni_usb_ibsta); -} - -static int ni_usb_parse_status_block(const u8 *buffer, struct ni_usb_status_block *status) -{ - u16 count; - - status->id = buffer[0]; - status->ibsta = (buffer[1] << 8) | buffer[2]; - status->error_code = buffer[3]; - count = buffer[4] | (buffer[5] << 8); - count = ~count; - count++; - status->count = count; - return 8; -}; - -static void ni_usb_dump_raw_block(const u8 *raw_data, int length) -{ - print_hex_dump(KERN_INFO, "", DUMP_PREFIX_NONE, 8, 1, raw_data, length, true); -} - -static int ni_usb_parse_register_read_block(const u8 *raw_data, unsigned int *results, - int num_results) -{ - int i = 0; - int j; - int unexpected = 0; - static const int results_per_chunk = 3; - - for (j = 0; j < num_results;) { - int k; - - if (raw_data[i++] != NIUSB_REGISTER_READ_DATA_START_ID) { - pr_err("parse error: wrong start id\n"); - unexpected = 1; - } - for (k = 0; k < results_per_chunk && j < num_results; ++k) - results[j++] = raw_data[i++]; - } - while (i % 4) - i++; - if (raw_data[i++] != NIUSB_REGISTER_READ_DATA_END_ID) { - pr_err("parse error: wrong end id\n"); - unexpected = 1; - } - if (raw_data[i++] % results_per_chunk != num_results % results_per_chunk) { - pr_err("parse error: wrong count=%i for NIUSB_REGISTER_READ_DATA_END\n", - (int)raw_data[i - 1]); - unexpected = 1; - } - while (i % 4) { - if (raw_data[i++] != 0) { - pr_err("unexpected data: raw_data[%i]=0x%x, expected 0\n", - i - 1, (int)raw_data[i - 1]); - unexpected = 1; - } - } - if (unexpected) - ni_usb_dump_raw_block(raw_data, i); - return i; -} - -static int ni_usb_parse_termination_block(const u8 *buffer) -{ - int i = 0; - - if (buffer[i++] != NIUSB_TERM_ID || - buffer[i++] != 0x0 || - buffer[i++] != 0x0 || - buffer[i++] != 0x0) { - pr_err("received unexpected termination block\n"); - pr_err(" expected: 0x%x 0x%x 0x%x 0x%x\n", NIUSB_TERM_ID, 0x0, 0x0, 0x0); - pr_err(" received: 0x%x 0x%x 0x%x 0x%x\n", - buffer[i - 4], buffer[i - 3], buffer[i - 2], buffer[i - 1]); - } - return i; -}; - -static int parse_board_ibrd_readback(const u8 *raw_data, struct ni_usb_status_block *status, - u8 *parsed_data, int parsed_data_length, - int *actual_bytes_read) -{ - static const int ibrd_data_block_length = 0xf; - static const int ibrd_extended_data_block_length = 0x1e; - int data_block_length = 0; - int i = 0; - int j = 0; - int k; - int num_data_blocks = 0; - struct ni_usb_status_block register_write_status; - int unexpected = 0; - - while (raw_data[i] == NIUSB_IBRD_DATA_ID || raw_data[i] == NIUSB_IBRD_EXTENDED_DATA_ID) { - if (raw_data[i] == NIUSB_IBRD_DATA_ID) { - data_block_length = ibrd_data_block_length; - } else if (raw_data[i] == NIUSB_IBRD_EXTENDED_DATA_ID) { - data_block_length = ibrd_extended_data_block_length; - if (raw_data[++i] != 0) { - pr_err("unexpected data: raw_data[%i]=0x%x, expected 0\n", - i, (int)raw_data[i]); - unexpected = 1; - } - } else { - pr_err("Unexpected NIUSB_IBRD ID\n"); - return -EINVAL; - } - ++i; - for (k = 0; k < data_block_length; k++) { - if (j < parsed_data_length) - parsed_data[j++] = raw_data[i++]; - else - ++i; - } - ++num_data_blocks; - } - i += ni_usb_parse_status_block(&raw_data[i], status); - if (status->id != NIUSB_IBRD_STATUS_ID) { - pr_err("bug: status->id=%i, != ibrd_status_id\n", status->id); - return -EIO; - } - i++; - if (num_data_blocks) { - *actual_bytes_read = (num_data_blocks - 1) * data_block_length + raw_data[i++]; - } else { - ++i; - *actual_bytes_read = 0; - } - if (*actual_bytes_read > j) - pr_err("bug: discarded data. actual_bytes_read=%i, j=%i\n", *actual_bytes_read, j); - for (k = 0; k < 2; k++) - if (raw_data[i++] != 0) { - pr_err("unexpected data: raw_data[%i]=0x%x, expected 0\n", - i - 1, (int)raw_data[i - 1]); - unexpected = 1; - } - i += ni_usb_parse_status_block(&raw_data[i], ®ister_write_status); - if (register_write_status.id != NIUSB_REG_WRITE_ID) { - pr_err("unexpected data: register write status id=0x%x, expected 0x%x\n", - register_write_status.id, NIUSB_REG_WRITE_ID); - unexpected = 1; - } - if (raw_data[i++] != 2) { - pr_err("unexpected data: register write count=%i, expected 2\n", - (int)raw_data[i - 1]); - unexpected = 1; - } - for (k = 0; k < 3; k++) - if (raw_data[i++] != 0) { - pr_err("unexpected data: raw_data[%i]=0x%x, expected 0\n", - i - 1, (int)raw_data[i - 1]); - unexpected = 1; - } - i += ni_usb_parse_termination_block(&raw_data[i]); - if (unexpected) - ni_usb_dump_raw_block(raw_data, i); - return i; -} - -static int ni_usb_parse_reg_write_status_block(const u8 *raw_data, - struct ni_usb_status_block *status, - int *writes_completed) -{ - int i = 0; - - i += ni_usb_parse_status_block(raw_data, status); - *writes_completed = raw_data[i++]; - while (i % 4) - i++; - return i; -} - -static int ni_usb_write_registers(struct ni_usb_priv *ni_priv, - const struct ni_usb_register *writes, int num_writes, - unsigned int *ibsta) -{ - struct usb_device *usb_dev = interface_to_usbdev(ni_priv->bus_interface); - int retval; - u8 *out_data, *in_data; - int out_data_length; - static const int in_data_length = 0x20; - int bytes_written = 0, bytes_read = 0; - int i = 0; - int j; - struct ni_usb_status_block status; - static const int bytes_per_write = 3; - int reg_writes_completed; - - out_data_length = num_writes * bytes_per_write + 0x10; - out_data = kmalloc(out_data_length, GFP_KERNEL); - if (!out_data) - return -ENOMEM; - i += ni_usb_bulk_register_write_header(&out_data[i], num_writes); - for (j = 0; j < num_writes; j++) - i += ni_usb_bulk_register_write(&out_data[i], writes[j]); - while (i % 4) - out_data[i++] = 0x00; - i += ni_usb_bulk_termination(&out_data[i]); - - mutex_lock(&ni_priv->addressed_transfer_lock); - - retval = ni_usb_send_bulk_msg(ni_priv, out_data, i, &bytes_written, 1000); - kfree(out_data); - if (retval) { - mutex_unlock(&ni_priv->addressed_transfer_lock); - dev_err(&usb_dev->dev, "send_bulk_msg returned %i, bytes_written=%i, i=%i\n", - retval, bytes_written, i); - return retval; - } - - in_data = kmalloc(in_data_length, GFP_KERNEL); - if (!in_data) { - mutex_unlock(&ni_priv->addressed_transfer_lock); - return -ENOMEM; - } - retval = ni_usb_receive_bulk_msg(ni_priv, in_data, in_data_length, &bytes_read, 1000, 0); - if (retval || bytes_read != 16) { - mutex_unlock(&ni_priv->addressed_transfer_lock); - dev_err(&usb_dev->dev, "receive_bulk_msg returned %i, bytes_read=%i\n", - retval, bytes_read); - ni_usb_dump_raw_block(in_data, bytes_read); - kfree(in_data); - return retval; - } - - mutex_unlock(&ni_priv->addressed_transfer_lock); - - ni_usb_parse_reg_write_status_block(in_data, &status, ®_writes_completed); - // FIXME parse extra 09 status bits and termination - kfree(in_data); - if (status.id != NIUSB_REG_WRITE_ID) { - dev_err(&usb_dev->dev, "parse error, id=0x%x != NIUSB_REG_WRITE_ID\n", status.id); - return -EIO; - } - if (status.error_code) { - dev_err(&usb_dev->dev, "nonzero error code 0x%x\n", status.error_code); - return -EIO; - } - if (reg_writes_completed != num_writes) { - dev_err(&usb_dev->dev, "reg_writes_completed=%i, num_writes=%i\n", - reg_writes_completed, num_writes); - return -EIO; - } - if (ibsta) - *ibsta = status.ibsta; - return 0; -} - -// interface functions -static int ni_usb_read(struct gpib_board *board, u8 *buffer, size_t length, - int *end, size_t *bytes_read) -{ - int retval, parse_retval; - struct ni_usb_priv *ni_priv = board->private_data; - struct usb_device *usb_dev; - u8 *out_data, *in_data; - static const int out_data_length = 0x20; - int in_data_length; - int usb_bytes_written = 0, usb_bytes_read = 0; - int i = 0; - int complement_count; - int actual_length; - struct ni_usb_status_block status; - static const int max_read_length = 0xffff; - struct ni_usb_register reg; - - *bytes_read = 0; - if (!ni_priv->bus_interface) - return -ENODEV; - if (length > max_read_length) - return -EINVAL; - usb_dev = interface_to_usbdev(ni_priv->bus_interface); - out_data = kmalloc(out_data_length, GFP_KERNEL); - if (!out_data) - return -ENOMEM; - out_data[i++] = 0x0a; - out_data[i++] = ni_priv->eos_mode >> 8; - out_data[i++] = ni_priv->eos_char; - out_data[i++] = ni_usb_timeout_code(board->usec_timeout); - complement_count = length - 1; - complement_count = ~complement_count; - out_data[i++] = complement_count & 0xff; - out_data[i++] = (complement_count >> 8) & 0xff; - out_data[i++] = 0x0; - out_data[i++] = 0x0; - i += ni_usb_bulk_register_write_header(&out_data[i], 2); - reg.device = NIUSB_SUBDEV_TNT4882; - reg.address = nec7210_to_tnt4882_offset(AUXMR); - reg.value = AUX_HLDI; - i += ni_usb_bulk_register_write(&out_data[i], reg); - reg.value = AUX_CLEAR_END; - i += ni_usb_bulk_register_write(&out_data[i], reg); - while (i % 4) // pad with zeros to 4-byte boundary - out_data[i++] = 0x0; - i += ni_usb_bulk_termination(&out_data[i]); - - mutex_lock(&ni_priv->addressed_transfer_lock); - - retval = ni_usb_send_bulk_msg(ni_priv, out_data, i, &usb_bytes_written, 1000); - kfree(out_data); - if (retval || usb_bytes_written != i) { - if (retval == 0) - retval = -EIO; - dev_err(&usb_dev->dev, "send_bulk_msg returned %i, usb_bytes_written=%i, i=%i\n", - retval, usb_bytes_written, i); - mutex_unlock(&ni_priv->addressed_transfer_lock); - return retval; - } - - in_data_length = (length / 30 + 1) * 0x20 + 0x20; - in_data = kmalloc(in_data_length, GFP_KERNEL); - if (!in_data) { - mutex_unlock(&ni_priv->addressed_transfer_lock); - return -ENOMEM; - } - retval = ni_usb_receive_bulk_msg(ni_priv, in_data, in_data_length, &usb_bytes_read, - ni_usb_timeout_msecs(board->usec_timeout), 1); - - mutex_unlock(&ni_priv->addressed_transfer_lock); - - if (retval == -ERESTARTSYS) { - } else if (retval) { - dev_err(&usb_dev->dev, "receive_bulk_msg returned %i, usb_bytes_read=%i\n", - retval, usb_bytes_read); - kfree(in_data); - return retval; - } - parse_retval = parse_board_ibrd_readback(in_data, &status, buffer, length, &actual_length); - if (parse_retval != usb_bytes_read) { - if (parse_retval >= 0) - parse_retval = -EIO; - dev_err(&usb_dev->dev, "retval=%i usb_bytes_read=%i\n", - parse_retval, usb_bytes_read); - kfree(in_data); - return parse_retval; - } - if (actual_length != length - status.count) { - dev_err(&usb_dev->dev, "actual_length=%i expected=%li\n", - actual_length, (long)(length - status.count)); - ni_usb_dump_raw_block(in_data, usb_bytes_read); - } - kfree(in_data); - switch (status.error_code) { - case NIUSB_NO_ERROR: - retval = 0; - break; - case NIUSB_ABORTED_ERROR: - /* - * this is expected if ni_usb_receive_bulk_msg got - * interrupted by a signal and returned -ERESTARTSYS - */ - break; - case NIUSB_ATN_STATE_ERROR: - if (status.ibsta & DCAS) { - retval = -EINTR; - } else { - retval = -EIO; - dev_dbg(&usb_dev->dev, "read when ATN set stat: 0x%06x\n", status.ibsta); - } - break; - case NIUSB_ADDRESSING_ERROR: - retval = -EIO; - break; - case NIUSB_TIMEOUT_ERROR: - retval = -ETIMEDOUT; - break; - case NIUSB_EOSMODE_ERROR: - dev_err(&usb_dev->dev, "driver bug, we should have been able to avoid NIUSB_EOSMODE_ERROR.\n"); - retval = -EINVAL; - break; - default: - dev_err(&usb_dev->dev, "unknown error code=%i\n", status.error_code); - retval = -EIO; - break; - } - ni_usb_soft_update_status(board, status.ibsta, 0); - if (status.ibsta & END) - *end = 1; - else - *end = 0; - *bytes_read = actual_length; - return retval; -} - -static int ni_usb_write(struct gpib_board *board, u8 *buffer, size_t length, - int send_eoi, size_t *bytes_written) -{ - int retval; - struct ni_usb_priv *ni_priv = board->private_data; - struct usb_device *usb_dev; - u8 *out_data, *in_data; - int out_data_length; - static const int in_data_length = 0x10; - int usb_bytes_written = 0, usb_bytes_read = 0; - int i = 0, j; - int complement_count; - struct ni_usb_status_block status; - static const int max_write_length = 0xffff; - - if (!ni_priv->bus_interface) - return -ENODEV; - if (length > max_write_length) - return -EINVAL; - usb_dev = interface_to_usbdev(ni_priv->bus_interface); - out_data_length = length + 0x10; - out_data = kmalloc(out_data_length, GFP_KERNEL); - if (!out_data) - return -ENOMEM; - out_data[i++] = 0x0d; - complement_count = length - 1; - complement_count = ~complement_count; - out_data[i++] = complement_count & 0xff; - out_data[i++] = (complement_count >> 8) & 0xff; - out_data[i++] = ni_usb_timeout_code(board->usec_timeout); - out_data[i++] = 0x0; - out_data[i++] = 0x0; - if (send_eoi) - out_data[i++] = 0x8; - else - out_data[i++] = 0x0; - out_data[i++] = 0x0; - for (j = 0; j < length; j++) - out_data[i++] = buffer[j]; - while (i % 4) // pad with zeros to 4-byte boundary - out_data[i++] = 0x0; - i += ni_usb_bulk_termination(&out_data[i]); - - mutex_lock(&ni_priv->addressed_transfer_lock); - - retval = ni_usb_send_bulk_msg(ni_priv, out_data, i, &usb_bytes_written, - ni_usb_timeout_msecs(board->usec_timeout)); - kfree(out_data); - if (retval || usb_bytes_written != i) { - mutex_unlock(&ni_priv->addressed_transfer_lock); - dev_err(&usb_dev->dev, "send_bulk_msg returned %i, usb_bytes_written=%i, i=%i\n", - retval, usb_bytes_written, i); - return retval; - } - - in_data = kmalloc(in_data_length, GFP_KERNEL); - if (!in_data) { - mutex_unlock(&ni_priv->addressed_transfer_lock); - return -ENOMEM; - } - retval = ni_usb_receive_bulk_msg(ni_priv, in_data, in_data_length, &usb_bytes_read, - ni_usb_timeout_msecs(board->usec_timeout), 1); - - mutex_unlock(&ni_priv->addressed_transfer_lock); - - if ((retval && retval != -ERESTARTSYS) || usb_bytes_read != 12) { - dev_err(&usb_dev->dev, "receive_bulk_msg returned %i, usb_bytes_read=%i\n", - retval, usb_bytes_read); - kfree(in_data); - return retval; - } - ni_usb_parse_status_block(in_data, &status); - kfree(in_data); - switch (status.error_code) { - case NIUSB_NO_ERROR: - retval = 0; - break; - case NIUSB_ABORTED_ERROR: - /* - * this is expected if ni_usb_receive_bulk_msg got - * interrupted by a signal and returned -ERESTARTSYS - */ - break; - case NIUSB_ADDRESSING_ERROR: - dev_err(&usb_dev->dev, "Addressing error retval %d error code=%i\n", - retval, status.error_code); - retval = -ENXIO; - break; - case NIUSB_NO_LISTENER_ERROR: - retval = -ECOMM; - break; - case NIUSB_TIMEOUT_ERROR: - retval = -ETIMEDOUT; - break; - default: - dev_err(&usb_dev->dev, "unknown error code=%i\n", status.error_code); - retval = -EPIPE; - break; - } - ni_usb_soft_update_status(board, status.ibsta, 0); - *bytes_written = length - status.count; - return retval; -} - -static int ni_usb_command_chunk(struct gpib_board *board, u8 *buffer, size_t length, - size_t *command_bytes_written) -{ - int retval; - struct ni_usb_priv *ni_priv = board->private_data; - struct usb_device *usb_dev; - u8 *out_data, *in_data; - int out_data_length; - static const int in_data_length = 0x10; - int bytes_written = 0, bytes_read = 0; - int i = 0, j; - unsigned int complement_count; - struct ni_usb_status_block status; - // usb-b gives error 4 if you try to send more than 16 command bytes at once - static const int max_command_length = 0x10; - - *command_bytes_written = 0; - if (!ni_priv->bus_interface) - return -ENODEV; - if (length > max_command_length) - length = max_command_length; - usb_dev = interface_to_usbdev(ni_priv->bus_interface); - out_data_length = length + 0x10; - out_data = kmalloc(out_data_length, GFP_KERNEL); - if (!out_data) - return -ENOMEM; - out_data[i++] = 0x0c; - complement_count = length - 1; - complement_count = ~complement_count; - out_data[i++] = complement_count; - out_data[i++] = 0x0; - out_data[i++] = ni_usb_timeout_code(board->usec_timeout); - for (j = 0; j < length; j++) - out_data[i++] = buffer[j]; - while (i % 4) // pad with zeros to 4-byte boundary - out_data[i++] = 0x0; - i += ni_usb_bulk_termination(&out_data[i]); - - mutex_lock(&ni_priv->addressed_transfer_lock); - - retval = ni_usb_send_bulk_msg(ni_priv, out_data, i, &bytes_written, - ni_usb_timeout_msecs(board->usec_timeout)); - kfree(out_data); - if (retval || bytes_written != i) { - mutex_unlock(&ni_priv->addressed_transfer_lock); - dev_err(&usb_dev->dev, "send_bulk_msg returned %i, bytes_written=%i, i=%i\n", - retval, bytes_written, i); - return retval; - } - - in_data = kmalloc(in_data_length, GFP_KERNEL); - if (!in_data) { - mutex_unlock(&ni_priv->addressed_transfer_lock); - return -ENOMEM; - } - - retval = ni_usb_receive_bulk_msg(ni_priv, in_data, in_data_length, &bytes_read, - ni_usb_timeout_msecs(board->usec_timeout), 1); - - mutex_unlock(&ni_priv->addressed_transfer_lock); - - if ((retval && retval != -ERESTARTSYS) || bytes_read != 12) { - dev_err(&usb_dev->dev, "receive_bulk_msg returned %i, bytes_read=%i\n", - retval, bytes_read); - kfree(in_data); - return retval; - } - ni_usb_parse_status_block(in_data, &status); - kfree(in_data); - *command_bytes_written = length - status.count; - switch (status.error_code) { - case NIUSB_NO_ERROR: - break; - case NIUSB_ABORTED_ERROR: - /* - * this is expected if ni_usb_receive_bulk_msg got - * interrupted by a signal and returned -ERESTARTSYS - */ - break; - case NIUSB_NO_BUS_ERROR: - return -ENOTCONN; - case NIUSB_EOSMODE_ERROR: - dev_err(&usb_dev->dev, "got eosmode error. Driver bug?\n"); - return -EIO; - case NIUSB_TIMEOUT_ERROR: - return -ETIMEDOUT; - default: - dev_err(&usb_dev->dev, "unknown error code=%i\n", status.error_code); - return -EIO; - } - ni_usb_soft_update_status(board, status.ibsta, 0); - return 0; -} - -static int ni_usb_command(struct gpib_board *board, u8 *buffer, size_t length, - size_t *bytes_written) -{ - size_t count; - int retval; - - *bytes_written = 0; - while (*bytes_written < length) { - retval = ni_usb_command_chunk(board, buffer + *bytes_written, - length - *bytes_written, &count); - *bytes_written += count; - if (retval < 0) - return retval; - } - return 0; -} - -static int ni_usb_take_control(struct gpib_board *board, int synchronous) -{ - int retval; - struct ni_usb_priv *ni_priv = board->private_data; - struct usb_device *usb_dev; - u8 *out_data, *in_data; - static const int out_data_length = 0x10; - static const int in_data_length = 0x10; - int bytes_written = 0, bytes_read = 0; - int i = 0; - struct ni_usb_status_block status; - - if (!ni_priv->bus_interface) - return -ENODEV; - usb_dev = interface_to_usbdev(ni_priv->bus_interface); - out_data = kmalloc(out_data_length, GFP_KERNEL); - if (!out_data) - return -ENOMEM; - out_data[i++] = NIUSB_IBCAC_ID; - if (synchronous) - out_data[i++] = 0x1; - else - out_data[i++] = 0x0; - out_data[i++] = 0x0; - out_data[i++] = 0x0; - i += ni_usb_bulk_termination(&out_data[i]); - - mutex_lock(&ni_priv->addressed_transfer_lock); - - retval = ni_usb_send_bulk_msg(ni_priv, out_data, i, &bytes_written, 1000); - kfree(out_data); - if (retval || bytes_written != i) { - mutex_unlock(&ni_priv->addressed_transfer_lock); - dev_err(&usb_dev->dev, "send_bulk_msg returned %i, bytes_written=%i, i=%i\n", - retval, bytes_written, i); - return retval; - } - - in_data = kmalloc(in_data_length, GFP_KERNEL); - if (!in_data) { - mutex_unlock(&ni_priv->addressed_transfer_lock); - return -ENOMEM; - } - retval = ni_usb_receive_bulk_msg(ni_priv, in_data, in_data_length, &bytes_read, 1000, 1); - - mutex_unlock(&ni_priv->addressed_transfer_lock); - - if ((retval && retval != -ERESTARTSYS) || bytes_read != 12) { - if (retval == 0) - retval = -EIO; - dev_err(&usb_dev->dev, "receive_bulk_msg returned %i, bytes_read=%i\n", - retval, bytes_read); - kfree(in_data); - return retval; - } - ni_usb_parse_status_block(in_data, &status); - kfree(in_data); - ni_usb_soft_update_status(board, status.ibsta, 0); - return retval; -} - -static int ni_usb_go_to_standby(struct gpib_board *board) -{ - int retval; - struct ni_usb_priv *ni_priv = board->private_data; - struct usb_device *usb_dev; - u8 *out_data, *in_data; - static const int out_data_length = 0x10; - static const int in_data_length = 0x20; - int bytes_written = 0, bytes_read = 0; - int i = 0; - struct ni_usb_status_block status; - - if (!ni_priv->bus_interface) - return -ENODEV; - usb_dev = interface_to_usbdev(ni_priv->bus_interface); - out_data = kmalloc(out_data_length, GFP_KERNEL); - if (!out_data) - return -ENOMEM; - - out_data[i++] = NIUSB_IBGTS_ID; - out_data[i++] = 0x0; - out_data[i++] = 0x0; - out_data[i++] = 0x0; - i += ni_usb_bulk_termination(&out_data[i]); - - mutex_lock(&ni_priv->addressed_transfer_lock); - - retval = ni_usb_send_bulk_msg(ni_priv, out_data, i, &bytes_written, 1000); - kfree(out_data); - if (retval || bytes_written != i) { - mutex_unlock(&ni_priv->addressed_transfer_lock); - dev_err(&usb_dev->dev, "send_bulk_msg returned %i, bytes_written=%i, i=%i\n", - retval, bytes_written, i); - return retval; - } - - in_data = kmalloc(in_data_length, GFP_KERNEL); - if (!in_data) { - mutex_unlock(&ni_priv->addressed_transfer_lock); - return -ENOMEM; - } - retval = ni_usb_receive_bulk_msg(ni_priv, in_data, in_data_length, &bytes_read, 1000, 0); - - mutex_unlock(&ni_priv->addressed_transfer_lock); - - if (retval || bytes_read != 12) { - dev_err(&usb_dev->dev, "receive_bulk_msg returned %i, bytes_read=%i\n", - retval, bytes_read); - kfree(in_data); - return retval; - } - ni_usb_parse_status_block(in_data, &status); - kfree(in_data); - if (status.id != NIUSB_IBGTS_ID) - dev_err(&usb_dev->dev, "bug: status.id 0x%x != INUSB_IBGTS_ID\n", status.id); - ni_usb_soft_update_status(board, status.ibsta, 0); - return 0; -} - -static int ni_usb_request_system_control(struct gpib_board *board, int request_control) -{ - int retval; - struct ni_usb_priv *ni_priv = board->private_data; - struct usb_device *usb_dev; - int i = 0; - struct ni_usb_register writes[4]; - unsigned int ibsta; - - if (!ni_priv->bus_interface) - return -ENODEV; - usb_dev = interface_to_usbdev(ni_priv->bus_interface); - if (request_control) { - writes[i].device = NIUSB_SUBDEV_TNT4882; - writes[i].address = CMDR; - writes[i].value = SETSC; - i++; - writes[i].device = NIUSB_SUBDEV_TNT4882; - writes[i].address = nec7210_to_tnt4882_offset(AUXMR); - writes[i].value = AUX_CIFC; - i++; - } else { - writes[i].device = NIUSB_SUBDEV_TNT4882; - writes[i].address = nec7210_to_tnt4882_offset(AUXMR); - writes[i].value = AUX_CREN; - i++; - writes[i].device = NIUSB_SUBDEV_TNT4882; - writes[i].address = nec7210_to_tnt4882_offset(AUXMR); - writes[i].value = AUX_CIFC; - i++; - writes[i].device = NIUSB_SUBDEV_TNT4882; - writes[i].address = nec7210_to_tnt4882_offset(AUXMR); - writes[i].value = AUX_DSC; - i++; - writes[i].device = NIUSB_SUBDEV_TNT4882; - writes[i].address = CMDR; - writes[i].value = CLRSC; - i++; - } - retval = ni_usb_write_registers(ni_priv, writes, i, &ibsta); - if (retval < 0) { - dev_err(&usb_dev->dev, "register write failed, retval=%i\n", retval); - return retval; - } - if (!request_control) - ni_priv->ren_state = 0; - ni_usb_soft_update_status(board, ibsta, 0); - return 0; -} - -// FIXME maybe the interface should have a "pulse interface clear" function that can return an error? -static void ni_usb_interface_clear(struct gpib_board *board, int assert) -{ - int retval; - struct ni_usb_priv *ni_priv = board->private_data; - struct usb_device *usb_dev; - u8 *out_data, *in_data; - static const int out_data_length = 0x10; - static const int in_data_length = 0x10; - int bytes_written = 0, bytes_read = 0; - int i = 0; - struct ni_usb_status_block status; - - if (!ni_priv->bus_interface) - return; // -ENODEV; - usb_dev = interface_to_usbdev(ni_priv->bus_interface); -// FIXME: we are going to pulse when assert is true, and ignore otherwise - if (assert == 0) - return; - out_data = kmalloc(out_data_length, GFP_KERNEL); - if (!out_data) - return; - out_data[i++] = NIUSB_IBSIC_ID; - out_data[i++] = 0x0; - out_data[i++] = 0x0; - out_data[i++] = 0x0; - i += ni_usb_bulk_termination(&out_data[i]); - retval = ni_usb_send_bulk_msg(ni_priv, out_data, i, &bytes_written, 1000); - kfree(out_data); - if (retval || bytes_written != i) { - dev_err(&usb_dev->dev, "send_bulk_msg returned %i, bytes_written=%i, i=%i\n", - retval, bytes_written, i); - return; - } - in_data = kmalloc(in_data_length, GFP_KERNEL); - if (!in_data) - return; - - retval = ni_usb_receive_bulk_msg(ni_priv, in_data, in_data_length, &bytes_read, 1000, 0); - if (retval || bytes_read != 12) { - dev_err(&usb_dev->dev, "receive_bulk_msg returned %i, bytes_read=%i\n", - retval, bytes_read); - kfree(in_data); - return; - } - ni_usb_parse_status_block(in_data, &status); - kfree(in_data); - ni_usb_soft_update_status(board, status.ibsta, 0); -} - -static void ni_usb_remote_enable(struct gpib_board *board, int enable) -{ - int retval; - struct ni_usb_priv *ni_priv = board->private_data; - struct usb_device *usb_dev; - struct ni_usb_register reg; - unsigned int ibsta; - - if (!ni_priv->bus_interface) - return; // -ENODEV; - usb_dev = interface_to_usbdev(ni_priv->bus_interface); - reg.device = NIUSB_SUBDEV_TNT4882; - reg.address = nec7210_to_tnt4882_offset(AUXMR); - if (enable) - reg.value = AUX_SREN; - else - reg.value = AUX_CREN; - retval = ni_usb_write_registers(ni_priv, ®, 1, &ibsta); - if (retval < 0) { - dev_err(&usb_dev->dev, "register write failed, retval=%i\n", retval); - return; //retval; - } - ni_priv->ren_state = enable; - ni_usb_soft_update_status(board, ibsta, 0); - return;// 0; -} - -static int ni_usb_enable_eos(struct gpib_board *board, u8 eos_byte, int compare_8_bits) -{ - struct ni_usb_priv *ni_priv = board->private_data; - - ni_priv->eos_char = eos_byte; - ni_priv->eos_mode |= REOS; - if (compare_8_bits) - ni_priv->eos_mode |= BIN; - else - ni_priv->eos_mode &= ~BIN; - return 0; -} - -static void ni_usb_disable_eos(struct gpib_board *board) -{ - struct ni_usb_priv *ni_priv = board->private_data; - /* - * adapter gets unhappy if you don't zero all the bits - * for the eos mode and eos char (returns error 4 on reads). - */ - ni_priv->eos_mode = 0; - ni_priv->eos_char = 0; -} - -static unsigned int ni_usb_update_status(struct gpib_board *board, unsigned int clear_mask) -{ - int retval; - struct ni_usb_priv *ni_priv = board->private_data; - struct usb_device *usb_dev; - static const int buffer_length = 8; - u8 *buffer; - struct ni_usb_status_block status; - - if (!ni_priv->bus_interface) - return -ENODEV; - usb_dev = interface_to_usbdev(ni_priv->bus_interface); - buffer = kmalloc(buffer_length, GFP_KERNEL); - if (!buffer) - return board->status; - - retval = ni_usb_receive_control_msg(ni_priv, NI_USB_WAIT_REQUEST, USB_DIR_IN | - USB_TYPE_VENDOR | USB_RECIP_DEVICE, - 0x200, 0x0, buffer, buffer_length, 1000); - if (retval != buffer_length) { - dev_err(&usb_dev->dev, "usb_control_msg returned %i\n", retval); - kfree(buffer); - return board->status; - } - ni_usb_parse_status_block(buffer, &status); - kfree(buffer); - ni_usb_soft_update_status(board, status.ibsta, clear_mask); - return board->status; -} - -// tells ni-usb to immediately stop an ongoing i/o operation -static void ni_usb_stop(struct ni_usb_priv *ni_priv) -{ - struct usb_device *usb_dev = interface_to_usbdev(ni_priv->bus_interface); - int retval; - static const int buffer_length = 8; - u8 *buffer; - struct ni_usb_status_block status; - - buffer = kmalloc(buffer_length, GFP_KERNEL); - if (!buffer) - return; - - retval = ni_usb_receive_control_msg(ni_priv, NI_USB_STOP_REQUEST, USB_DIR_IN | - USB_TYPE_VENDOR | USB_RECIP_DEVICE, - 0x0, 0x0, buffer, buffer_length, 1000); - if (retval != buffer_length) { - dev_err(&usb_dev->dev, "usb_control_msg returned %i\n", retval); - kfree(buffer); - return; - } - ni_usb_parse_status_block(buffer, &status); - kfree(buffer); -} - -static int ni_usb_primary_address(struct gpib_board *board, unsigned int address) -{ - int retval; - struct ni_usb_priv *ni_priv = board->private_data; - struct usb_device *usb_dev; - int i = 0; - struct ni_usb_register writes[2]; - unsigned int ibsta; - - if (!ni_priv->bus_interface) - return -ENODEV; - usb_dev = interface_to_usbdev(ni_priv->bus_interface); - writes[i].device = NIUSB_SUBDEV_TNT4882; - writes[i].address = nec7210_to_tnt4882_offset(ADR); - writes[i].value = address; - i++; - writes[i].device = NIUSB_SUBDEV_UNKNOWN2; - writes[i].address = 0x0; - writes[i].value = address; - i++; - retval = ni_usb_write_registers(ni_priv, writes, i, &ibsta); - if (retval < 0) { - dev_err(&usb_dev->dev, "register write failed, retval=%i\n", retval); - return retval; - } - ni_usb_soft_update_status(board, ibsta, 0); - return 0; -} - -static int ni_usb_write_sad(struct ni_usb_register *writes, int address, int enable) -{ - unsigned int adr_bits, admr_bits; - int i = 0; - - adr_bits = HR_ARS; - admr_bits = HR_TRM0 | HR_TRM1; - if (enable) { - adr_bits |= address; - admr_bits |= HR_ADM1; - } else { - adr_bits |= HR_DT | HR_DL; - admr_bits |= HR_ADM0; - } - writes[i].device = NIUSB_SUBDEV_TNT4882; - writes[i].address = nec7210_to_tnt4882_offset(ADR); - writes[i].value = adr_bits; - i++; - writes[i].device = NIUSB_SUBDEV_TNT4882; - writes[i].address = nec7210_to_tnt4882_offset(ADMR); - writes[i].value = admr_bits; - i++; - writes[i].device = NIUSB_SUBDEV_UNKNOWN2; - writes[i].address = 0x1; - writes[i].value = enable ? MSA(address) : 0x0; - i++; - return i; -} - -static int ni_usb_secondary_address(struct gpib_board *board, unsigned int address, int enable) -{ - int retval; - struct ni_usb_priv *ni_priv = board->private_data; - struct usb_device *usb_dev; - int i = 0; - struct ni_usb_register writes[3]; - unsigned int ibsta; - - if (!ni_priv->bus_interface) - return -ENODEV; - usb_dev = interface_to_usbdev(ni_priv->bus_interface); - i += ni_usb_write_sad(writes, address, enable); - retval = ni_usb_write_registers(ni_priv, writes, i, &ibsta); - if (retval < 0) { - dev_err(&usb_dev->dev, "register write failed, retval=%i\n", retval); - return retval; - } - ni_usb_soft_update_status(board, ibsta, 0); - return 0; -} - -static int ni_usb_parallel_poll(struct gpib_board *board, u8 *result) -{ - int retval; - struct ni_usb_priv *ni_priv = board->private_data; - struct usb_device *usb_dev; - u8 *out_data, *in_data; - static const int out_data_length = 0x10; - static const int in_data_length = 0x20; - int bytes_written = 0, bytes_read = 0; - int i = 0; - int j = 0; - struct ni_usb_status_block status; - - if (!ni_priv->bus_interface) - return -ENODEV; - usb_dev = interface_to_usbdev(ni_priv->bus_interface); - out_data = kmalloc(out_data_length, GFP_KERNEL); - if (!out_data) - return -ENOMEM; - - out_data[i++] = NIUSB_IBRPP_ID; - out_data[i++] = 0xf0; // FIXME: this should be the parallel poll timeout code - out_data[i++] = 0x0; - out_data[i++] = 0x0; - i += ni_usb_bulk_termination(&out_data[i]); - /*FIXME: 1000 should use parallel poll timeout (not supported yet)*/ - retval = ni_usb_send_bulk_msg(ni_priv, out_data, i, &bytes_written, 1000); - - kfree(out_data); - if (retval || bytes_written != i) { - dev_err(&usb_dev->dev, "send_bulk_msg returned %i, bytes_written=%i, i=%i\n", - retval, bytes_written, i); - return retval; - } - in_data = kmalloc(in_data_length, GFP_KERNEL); - if (!in_data) - return -ENOMEM; - - /*FIXME: should use parallel poll timeout (not supported yet)*/ - retval = ni_usb_receive_bulk_msg(ni_priv, in_data, in_data_length, - &bytes_read, 1000, 1); - - if (retval && retval != -ERESTARTSYS) { - dev_err(&usb_dev->dev, "receive_bulk_msg returned %i, bytes_read=%i\n", - retval, bytes_read); - kfree(in_data); - return retval; - } - j += ni_usb_parse_status_block(in_data, &status); - *result = in_data[j++]; - kfree(in_data); - ni_usb_soft_update_status(board, status.ibsta, 0); - return retval; -} - -static void ni_usb_parallel_poll_configure(struct gpib_board *board, u8 config) -{ - int retval; - struct ni_usb_priv *ni_priv = board->private_data; - struct usb_device *usb_dev; - int i = 0; - struct ni_usb_register writes[1]; - unsigned int ibsta; - - if (!ni_priv->bus_interface) - return; // -ENODEV; - usb_dev = interface_to_usbdev(ni_priv->bus_interface); - writes[i].device = NIUSB_SUBDEV_TNT4882; - writes[i].address = nec7210_to_tnt4882_offset(AUXMR); - writes[i].value = PPR | config; - i++; - retval = ni_usb_write_registers(ni_priv, writes, i, &ibsta); - if (retval < 0) { - dev_err(&usb_dev->dev, "register write failed, retval=%i\n", retval); - return;// retval; - } - ni_usb_soft_update_status(board, ibsta, 0); - return;// 0; -} - -static void ni_usb_parallel_poll_response(struct gpib_board *board, int ist) -{ - int retval; - struct ni_usb_priv *ni_priv = board->private_data; - struct usb_device *usb_dev; - int i = 0; - struct ni_usb_register writes[1]; - unsigned int ibsta; - - if (!ni_priv->bus_interface) - return; // -ENODEV; - usb_dev = interface_to_usbdev(ni_priv->bus_interface); - writes[i].device = NIUSB_SUBDEV_TNT4882; - writes[i].address = nec7210_to_tnt4882_offset(AUXMR); - if (ist) - writes[i].value = AUX_SPPF; - else - writes[i].value = AUX_CPPF; - i++; - retval = ni_usb_write_registers(ni_priv, writes, i, &ibsta); - if (retval < 0) { - dev_err(&usb_dev->dev, "register write failed, retval=%i\n", retval); - return;// retval; - } - ni_usb_soft_update_status(board, ibsta, 0); - return;// 0; -} - -static void ni_usb_serial_poll_response(struct gpib_board *board, u8 status) -{ - int retval; - struct ni_usb_priv *ni_priv = board->private_data; - struct usb_device *usb_dev; - int i = 0; - struct ni_usb_register writes[1]; - unsigned int ibsta; - - if (!ni_priv->bus_interface) - return; // -ENODEV; - usb_dev = interface_to_usbdev(ni_priv->bus_interface); - writes[i].device = NIUSB_SUBDEV_TNT4882; - writes[i].address = nec7210_to_tnt4882_offset(SPMR); - writes[i].value = status; - i++; - retval = ni_usb_write_registers(ni_priv, writes, i, &ibsta); - if (retval < 0) { - dev_err(&usb_dev->dev, "register write failed, retval=%i\n", retval); - return;// retval; - } - ni_usb_soft_update_status(board, ibsta, 0); - return;// 0; -} - -static u8 ni_usb_serial_poll_status(struct gpib_board *board) -{ - return 0; -} - -static void ni_usb_return_to_local(struct gpib_board *board) -{ - int retval; - struct ni_usb_priv *ni_priv = board->private_data; - struct usb_device *usb_dev; - int i = 0; - struct ni_usb_register writes[1]; - unsigned int ibsta; - - if (!ni_priv->bus_interface) - return; // -ENODEV; - usb_dev = interface_to_usbdev(ni_priv->bus_interface); - writes[i].device = NIUSB_SUBDEV_TNT4882; - writes[i].address = nec7210_to_tnt4882_offset(AUXMR); - writes[i].value = AUX_RTL; - i++; - retval = ni_usb_write_registers(ni_priv, writes, i, &ibsta); - if (retval < 0) { - dev_err(&usb_dev->dev, "register write failed, retval=%i\n", retval); - return;// retval; - } - ni_usb_soft_update_status(board, ibsta, 0); - return;// 0; -} - -static int ni_usb_line_status(const struct gpib_board *board) -{ - int retval; - struct ni_usb_priv *ni_priv = board->private_data; - struct usb_device *usb_dev; - u8 *out_data, *in_data; - static const int out_data_length = 0x20; - static const int in_data_length = 0x20; - int bytes_written = 0, bytes_read = 0; - int i = 0; - unsigned int bsr_bits; - int line_status = VALID_ALL; - // NI windows driver reads 0xd(HSSEL), 0xc (ARD0), 0x1f (BSR) - - if (!ni_priv->bus_interface) - return -ENODEV; - usb_dev = interface_to_usbdev(ni_priv->bus_interface); - out_data = kmalloc(out_data_length, GFP_KERNEL); - if (!out_data) - return -ENOMEM; - - /* line status gets called during ibwait */ - retval = mutex_trylock(&ni_priv->addressed_transfer_lock); - - if (retval == 0) { - kfree(out_data); - return -EBUSY; - } - i += ni_usb_bulk_register_read_header(&out_data[i], 1); - i += ni_usb_bulk_register_read(&out_data[i], NIUSB_SUBDEV_TNT4882, BSR); - while (i % 4) - out_data[i++] = 0x0; - i += ni_usb_bulk_termination(&out_data[i]); - retval = ni_usb_nonblocking_send_bulk_msg(ni_priv, out_data, i, &bytes_written, 1000); - kfree(out_data); - if (retval || bytes_written != i) { - mutex_unlock(&ni_priv->addressed_transfer_lock); - if (retval != -EAGAIN) - dev_err(&usb_dev->dev, "send_bulk_msg returned %i, bytes_written=%i, i=%i\n", - retval, bytes_written, i); - return retval; - } - - in_data = kmalloc(in_data_length, GFP_KERNEL); - if (!in_data) { - mutex_unlock(&ni_priv->addressed_transfer_lock); - return -ENOMEM; - } - retval = ni_usb_nonblocking_receive_bulk_msg(ni_priv, in_data, in_data_length, - &bytes_read, 1000, 0); - - mutex_unlock(&ni_priv->addressed_transfer_lock); - - if (retval) { - if (retval != -EAGAIN) - dev_err(&usb_dev->dev, "receive_bulk_msg returned %i, bytes_read=%i\n", - retval, bytes_read); - kfree(in_data); - return retval; - } - - ni_usb_parse_register_read_block(in_data, &bsr_bits, 1); - kfree(in_data); - if (bsr_bits & BCSR_REN_BIT) - line_status |= BUS_REN; - if (bsr_bits & BCSR_IFC_BIT) - line_status |= BUS_IFC; - if (bsr_bits & BCSR_SRQ_BIT) - line_status |= BUS_SRQ; - if (bsr_bits & BCSR_EOI_BIT) - line_status |= BUS_EOI; - if (bsr_bits & BCSR_NRFD_BIT) - line_status |= BUS_NRFD; - if (bsr_bits & BCSR_NDAC_BIT) - line_status |= BUS_NDAC; - if (bsr_bits & BCSR_DAV_BIT) - line_status |= BUS_DAV; - if (bsr_bits & BCSR_ATN_BIT) - line_status |= BUS_ATN; - return line_status; -} - -static int ni_usb_setup_t1_delay(struct ni_usb_register *reg, unsigned int nano_sec, - unsigned int *actual_ns) -{ - int i = 0; - - *actual_ns = 2000; - - reg[i].device = NIUSB_SUBDEV_TNT4882; - reg[i].address = nec7210_to_tnt4882_offset(AUXMR); - if (nano_sec <= 1100) { - reg[i].value = AUXRI | USTD | SISB; - *actual_ns = 1100; - } else { - reg[i].value = AUXRI | SISB; - } - i++; - reg[i].device = NIUSB_SUBDEV_TNT4882; - reg[i].address = nec7210_to_tnt4882_offset(AUXMR); - if (nano_sec <= 500) { - reg[i].value = AUXRB | HR_TRI; - *actual_ns = 500; - } else { - reg[i].value = AUXRB; - } - i++; - reg[i].device = NIUSB_SUBDEV_TNT4882; - reg[i].address = KEYREG; - if (nano_sec <= 350) { - reg[i].value = MSTD; - *actual_ns = 350; - } else { - reg[i].value = 0x0; - } - i++; - return i; -} - -static int ni_usb_t1_delay(struct gpib_board *board, unsigned int nano_sec) -{ - int retval; - struct ni_usb_priv *ni_priv = board->private_data; - struct usb_device *usb_dev; - struct ni_usb_register writes[3]; - unsigned int ibsta; - unsigned int actual_ns; - int i; - - if (!ni_priv->bus_interface) - return -ENODEV; - usb_dev = interface_to_usbdev(ni_priv->bus_interface); - i = ni_usb_setup_t1_delay(writes, nano_sec, &actual_ns); - retval = ni_usb_write_registers(ni_priv, writes, i, &ibsta); - if (retval < 0) { - dev_err(&usb_dev->dev, "register write failed, retval=%i\n", retval); - return retval; - } - board->t1_nano_sec = actual_ns; - ni_usb_soft_update_status(board, ibsta, 0); - return actual_ns; -} - -static int ni_usb_allocate_private(struct gpib_board *board) -{ - struct ni_usb_priv *ni_priv; - - board->private_data = kmalloc(sizeof(struct ni_usb_priv), GFP_KERNEL); - if (!board->private_data) - return -ENOMEM; - ni_priv = board->private_data; - memset(ni_priv, 0, sizeof(struct ni_usb_priv)); - mutex_init(&ni_priv->bulk_transfer_lock); - mutex_init(&ni_priv->control_transfer_lock); - mutex_init(&ni_priv->interrupt_transfer_lock); - mutex_init(&ni_priv->addressed_transfer_lock); - return 0; -} - -static void ni_usb_free_private(struct ni_usb_priv *ni_priv) -{ - usb_free_urb(ni_priv->interrupt_urb); - kfree(ni_priv); -} - -#define NUM_INIT_WRITES 26 -static int ni_usb_setup_init(struct gpib_board *board, struct ni_usb_register *writes) -{ - struct ni_usb_priv *ni_priv = board->private_data; - struct usb_device *usb_dev = interface_to_usbdev(ni_priv->bus_interface); - unsigned int mask, actual_ns; - int i = 0; - - writes[i].device = NIUSB_SUBDEV_UNKNOWN3; - writes[i].address = 0x10; - writes[i].value = 0x0; - i++; - writes[i].device = NIUSB_SUBDEV_TNT4882; - writes[i].address = CMDR; - writes[i].value = SOFT_RESET; - i++; - writes[i].device = NIUSB_SUBDEV_TNT4882; - writes[i].address = nec7210_to_tnt4882_offset(AUXMR); - mask = AUXRA | HR_HLDA; - if (ni_priv->eos_mode & BIN) - mask |= HR_BIN; - writes[i].value = mask; - i++; - writes[i].device = NIUSB_SUBDEV_TNT4882; - writes[i].address = AUXCR; - writes[i].value = mask; - i++; - writes[i].device = NIUSB_SUBDEV_TNT4882; - writes[i].address = HSSEL; - writes[i].value = TNT_ONE_CHIP_BIT; - i++; - writes[i].device = NIUSB_SUBDEV_TNT4882; - writes[i].address = nec7210_to_tnt4882_offset(AUXMR); - writes[i].value = AUX_CR; - i++; - writes[i].device = NIUSB_SUBDEV_TNT4882; - writes[i].address = IMR0; - writes[i].value = TNT_IMR0_ALWAYS_BITS; - i++; - writes[i].device = NIUSB_SUBDEV_TNT4882; - writes[i].address = nec7210_to_tnt4882_offset(IMR1); - writes[i].value = 0x0; - i++; - writes[i].device = NIUSB_SUBDEV_TNT4882; - writes[i].address = nec7210_to_tnt4882_offset(IMR2); - writes[i].value = 0x0; - i++; - writes[i].device = NIUSB_SUBDEV_TNT4882; - writes[i].address = IMR3; - writes[i].value = 0x0; - i++; - writes[i].device = NIUSB_SUBDEV_TNT4882; - writes[i].address = nec7210_to_tnt4882_offset(AUXMR); - writes[i].value = AUX_HLDI; - i++; - - i += ni_usb_setup_t1_delay(&writes[i], board->t1_nano_sec, &actual_ns); - - writes[i].device = NIUSB_SUBDEV_TNT4882; - writes[i].address = nec7210_to_tnt4882_offset(AUXMR); - writes[i].value = AUXRG | NTNL_BIT; - i++; - writes[i].device = NIUSB_SUBDEV_TNT4882; - writes[i].address = CMDR; - if (board->master) - mask = SETSC; // set system controller - else - mask = CLRSC; // clear system controller - writes[i].value = mask; - i++; - writes[i].device = NIUSB_SUBDEV_TNT4882; - writes[i].address = nec7210_to_tnt4882_offset(AUXMR); - writes[i].value = AUX_CIFC; - i++; - writes[i].device = NIUSB_SUBDEV_TNT4882; - writes[i].address = nec7210_to_tnt4882_offset(ADR); - writes[i].value = board->pad; - i++; - writes[i].device = NIUSB_SUBDEV_UNKNOWN2; - writes[i].address = 0x0; - writes[i].value = board->pad; - i++; - - i += ni_usb_write_sad(&writes[i], board->sad, board->sad >= 0); - - writes[i].device = NIUSB_SUBDEV_UNKNOWN2; - writes[i].address = 0x2; // could this be a timeout ? - writes[i].value = 0xfd; - i++; - writes[i].device = NIUSB_SUBDEV_TNT4882; - writes[i].address = 0xf; // undocumented address - writes[i].value = 0x11; - i++; - writes[i].device = NIUSB_SUBDEV_TNT4882; - writes[i].address = nec7210_to_tnt4882_offset(AUXMR); - writes[i].value = AUX_PON; - i++; - writes[i].device = NIUSB_SUBDEV_TNT4882; - writes[i].address = nec7210_to_tnt4882_offset(AUXMR); - writes[i].value = AUX_CPPF; - i++; - if (i > NUM_INIT_WRITES) { - dev_err(&usb_dev->dev, "bug!, buffer overrun, i=%i\n", i); - return 0; - } - return i; -} - -static int ni_usb_init(struct gpib_board *board) -{ - int retval; - struct ni_usb_priv *ni_priv = board->private_data; - struct usb_device *usb_dev = interface_to_usbdev(ni_priv->bus_interface); - struct ni_usb_register *writes; - unsigned int ibsta; - int writes_len; - - writes = kmalloc_array(NUM_INIT_WRITES, sizeof(*writes), GFP_KERNEL); - if (!writes) - return -ENOMEM; - - writes_len = ni_usb_setup_init(board, writes); - if (writes_len) - retval = ni_usb_write_registers(ni_priv, writes, writes_len, &ibsta); - else - return -EFAULT; - kfree(writes); - if (retval) { - dev_err(&usb_dev->dev, "register write failed, retval=%i\n", retval); - return retval; - } - ni_usb_soft_update_status(board, ibsta, 0); - return 0; -} - -static void ni_usb_interrupt_complete(struct urb *urb) -{ - struct gpib_board *board = urb->context; - struct ni_usb_priv *ni_priv = board->private_data; - struct usb_device *usb_dev = interface_to_usbdev(ni_priv->bus_interface); - int retval; - struct ni_usb_status_block status; - unsigned long flags; - - switch (urb->status) { - /* success */ - case 0: - break; - /* unlinked, don't resubmit */ - case -ECONNRESET: - case -ENOENT: - case -ESHUTDOWN: - return; - default: /* other error, resubmit */ - retval = usb_submit_urb(ni_priv->interrupt_urb, GFP_ATOMIC); - if (retval) - dev_err(&usb_dev->dev, "failed to resubmit interrupt urb\n"); - return; - } - - ni_usb_parse_status_block(urb->transfer_buffer, &status); - - spin_lock_irqsave(&board->spinlock, flags); - ni_priv->monitored_ibsta_bits &= ~status.ibsta; - spin_unlock_irqrestore(&board->spinlock, flags); - - wake_up_interruptible(&board->wait); - - retval = usb_submit_urb(ni_priv->interrupt_urb, GFP_ATOMIC); - if (retval) - dev_err(&usb_dev->dev, "failed to resubmit interrupt urb\n"); -} - -static int ni_usb_set_interrupt_monitor(struct gpib_board *board, unsigned int monitored_bits) -{ - int retval; - struct ni_usb_priv *ni_priv = board->private_data; - struct usb_device *usb_dev = interface_to_usbdev(ni_priv->bus_interface); - static const int buffer_length = 8; - u8 *buffer; - struct ni_usb_status_block status; - unsigned long flags; - - buffer = kmalloc(buffer_length, GFP_KERNEL); - if (!buffer) - return -ENOMEM; - - spin_lock_irqsave(&board->spinlock, flags); - ni_priv->monitored_ibsta_bits = ni_usb_ibsta_monitor_mask & monitored_bits; - spin_unlock_irqrestore(&board->spinlock, flags); - retval = ni_usb_receive_control_msg(ni_priv, NI_USB_WAIT_REQUEST, USB_DIR_IN | - USB_TYPE_VENDOR | USB_RECIP_DEVICE, - 0x300, ni_usb_ibsta_monitor_mask & monitored_bits, - buffer, buffer_length, 1000); - if (retval != buffer_length) { - dev_err(&usb_dev->dev, "usb_control_msg returned %i\n", retval); - kfree(buffer); - return -1; - } - ni_usb_parse_status_block(buffer, &status); - kfree(buffer); - return 0; -} - -static int ni_usb_setup_urbs(struct gpib_board *board) -{ - struct ni_usb_priv *ni_priv = board->private_data; - struct usb_device *usb_dev; - int int_pipe; - int retval; - - if (ni_priv->interrupt_in_endpoint < 0) - return 0; - - mutex_lock(&ni_priv->interrupt_transfer_lock); - if (!ni_priv->bus_interface) { - mutex_unlock(&ni_priv->interrupt_transfer_lock); - return -ENODEV; - } - ni_priv->interrupt_urb = usb_alloc_urb(0, GFP_KERNEL); - if (!ni_priv->interrupt_urb) { - mutex_unlock(&ni_priv->interrupt_transfer_lock); - return -ENOMEM; - } - usb_dev = interface_to_usbdev(ni_priv->bus_interface); - int_pipe = usb_rcvintpipe(usb_dev, ni_priv->interrupt_in_endpoint); - usb_fill_int_urb(ni_priv->interrupt_urb, usb_dev, int_pipe, ni_priv->interrupt_buffer, - sizeof(ni_priv->interrupt_buffer), &ni_usb_interrupt_complete, board, 1); - retval = usb_submit_urb(ni_priv->interrupt_urb, GFP_KERNEL); - mutex_unlock(&ni_priv->interrupt_transfer_lock); - if (retval) { - dev_err(&usb_dev->dev, "failed to submit first interrupt urb, retval=%i\n", retval); - return retval; - } - return 0; -} - -static void ni_usb_cleanup_urbs(struct ni_usb_priv *ni_priv) -{ - if (ni_priv && ni_priv->bus_interface) { - if (ni_priv->interrupt_urb) - usb_kill_urb(ni_priv->interrupt_urb); - if (ni_priv->bulk_urb) - usb_kill_urb(ni_priv->bulk_urb); - } -} - -static int ni_usb_b_read_serial_number(struct ni_usb_priv *ni_priv) -{ - struct usb_device *usb_dev = interface_to_usbdev(ni_priv->bus_interface); - int retval; - u8 *out_data; - u8 *in_data; - static const int out_data_length = 0x20; - static const int in_data_length = 0x20; - int bytes_written = 0, bytes_read = 0; - int i = 0; - static const int num_reads = 4; - unsigned int results[4]; - int j; - unsigned int serial_number; - - in_data = kmalloc(in_data_length, GFP_KERNEL); - if (!in_data) - return -ENOMEM; - - out_data = kmalloc(out_data_length, GFP_KERNEL); - if (!out_data) { - kfree(in_data); - return -ENOMEM; - } - i += ni_usb_bulk_register_read_header(&out_data[i], num_reads); - i += ni_usb_bulk_register_read(&out_data[i], NIUSB_SUBDEV_UNKNOWN3, SERIAL_NUMBER_1_REG); - i += ni_usb_bulk_register_read(&out_data[i], NIUSB_SUBDEV_UNKNOWN3, SERIAL_NUMBER_2_REG); - i += ni_usb_bulk_register_read(&out_data[i], NIUSB_SUBDEV_UNKNOWN3, SERIAL_NUMBER_3_REG); - i += ni_usb_bulk_register_read(&out_data[i], NIUSB_SUBDEV_UNKNOWN3, SERIAL_NUMBER_4_REG); - while (i % 4) - out_data[i++] = 0x0; - i += ni_usb_bulk_termination(&out_data[i]); - retval = ni_usb_send_bulk_msg(ni_priv, out_data, out_data_length, &bytes_written, 1000); - if (retval) { - dev_err(&usb_dev->dev, "send_bulk_msg returned %i, bytes_written=%i, i=%li\n", - retval, bytes_written, (long)out_data_length); - goto serial_out; - } - retval = ni_usb_receive_bulk_msg(ni_priv, in_data, in_data_length, &bytes_read, 1000, 0); - if (retval) { - dev_err(&usb_dev->dev, "receive_bulk_msg returned %i, bytes_read=%i\n", - retval, bytes_read); - ni_usb_dump_raw_block(in_data, bytes_read); - goto serial_out; - } - if (ARRAY_SIZE(results) < num_reads) { - dev_err(&usb_dev->dev, "serial number eetup bug\n"); - retval = -EINVAL; - goto serial_out; - } - ni_usb_parse_register_read_block(in_data, results, num_reads); - serial_number = 0; - for (j = 0; j < num_reads; ++j) - serial_number |= (results[j] & 0xff) << (8 * j); - dev_dbg(&usb_dev->dev, "board serial number is 0x%x\n", serial_number); - retval = 0; -serial_out: - kfree(in_data); - kfree(out_data); - return retval; -} - -static int ni_usb_hs_wait_for_ready(struct ni_usb_priv *ni_priv) -{ - struct usb_device *usb_dev = interface_to_usbdev(ni_priv->bus_interface); - static const int buffer_size = 0x10; - static const int timeout = 50; - static const int msec_sleep_duration = 100; - int i; int retval; - int j; - int unexpected = 0; - unsigned int serial_number; - u8 *buffer; - - buffer = kmalloc(buffer_size, GFP_KERNEL); - if (!buffer) - return -ENOMEM; - - retval = ni_usb_receive_control_msg(ni_priv, NI_USB_SERIAL_NUMBER_REQUEST, - USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, - 0x0, 0x0, buffer, buffer_size, 1000); - if (retval < 0) { - dev_err(&usb_dev->dev, "usb_control_msg request 0x%x returned %i\n", - NI_USB_SERIAL_NUMBER_REQUEST, retval); - goto ready_out; - } - j = 0; - if (buffer[j] != NI_USB_SERIAL_NUMBER_REQUEST) { - dev_err(&usb_dev->dev, "unexpected data: buffer[%i]=0x%x, expected 0x%x\n", - j, (int)buffer[j], NI_USB_SERIAL_NUMBER_REQUEST); - unexpected = 1; - } - if (unexpected) - ni_usb_dump_raw_block(buffer, retval); - // NI-USB-HS+ pads the serial with 0x0 to make 16 bytes - if (retval != 5 && retval != 16) { - dev_err(&usb_dev->dev, "received unexpected number of bytes = %i, expected 5 or 16\n", - retval); - ni_usb_dump_raw_block(buffer, retval); - } - serial_number = 0; - serial_number |= buffer[++j]; - serial_number |= (buffer[++j] << 8); - serial_number |= (buffer[++j] << 16); - serial_number |= (buffer[++j] << 24); - dev_dbg(&usb_dev->dev, "board serial number is 0x%x\n", serial_number); - for (i = 0; i < timeout; ++i) { - int ready = 0; - - retval = ni_usb_receive_control_msg(ni_priv, NI_USB_POLL_READY_REQUEST, - USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, - 0x0, 0x0, buffer, buffer_size, 100); - if (retval < 0) { - dev_err(&usb_dev->dev, "usb_control_msg request 0x%x returned %i\n", - NI_USB_POLL_READY_REQUEST, retval); - goto ready_out; - } - j = 0; - unexpected = 0; - if (buffer[j] != NI_USB_POLL_READY_REQUEST) { // [0] - dev_err(&usb_dev->dev, "unexpected data: buffer[%i]=0x%x, expected 0x%x\n", - j, (int)buffer[j], NI_USB_POLL_READY_REQUEST); - unexpected = 1; - } - ++j; - if (buffer[j] != 0x1 && buffer[j] != 0x0) { // [1] HS+ sends 0x0 - dev_err(&usb_dev->dev, "unexpected data: buffer[%i]=0x%x, expected 0x1 or 0x0\n", - j, (int)buffer[j]); - unexpected = 1; - } - if (buffer[++j] != 0x0) { // [2] - dev_err(&usb_dev->dev, "unexpected data: buffer[%i]=0x%x, expected 0x%x\n", - j, (int)buffer[j], 0x0); - unexpected = 1; - } - ++j; - /* - * MC usb-488 (and sometimes NI-USB-HS?) sends 0x8 here; MC usb-488A sends 0x7 here - * NI-USB-HS+ sends 0x0 - */ - if (buffer[j] != 0x1 && buffer[j] != 0x8 && buffer[j] != 0x7 && buffer[j] != 0x0) { - // [3] - dev_err(&usb_dev->dev, "unexpected data: buffer[%i]=0x%x, expected 0x0, 0x1, 0x7 or 0x8\n", - j, (int)buffer[j]); - unexpected = 1; - } - ++j; - // NI-USB-HS+ sends 0 here - if (buffer[j] != 0x30 && buffer[j] != 0x0) { // [4] - dev_err(&usb_dev->dev, "unexpected data: buffer[%i]=0x%x, expected 0x0 or 0x30\n", - j, (int)buffer[j]); - unexpected = 1; - } - ++j; - // MC usb-488 (and sometimes NI-USB-HS?) and NI-USB-HS+ sends 0x0 here - if (buffer[j] != 0x1 && buffer[j] != 0x0) { // [5] - dev_err(&usb_dev->dev, "unexpected data: buffer[%i]=0x%x, expected 0x1 or 0x0\n", - j, (int)buffer[j]); - unexpected = 1; - } - if (buffer[++j] != 0x0) { // [6] - ready = 1; - // NI-USB-HS+ sends 0xf or 0x19 here - if (buffer[j] != 0x2 && buffer[j] != 0xe && buffer[j] != 0xf && - buffer[j] != 0x16 && buffer[j] != 0x19) { - dev_err(&usb_dev->dev, "unexpected data: buffer[%i]=0x%x, expected 0x2, 0xe, 0xf, 0x16 or 0x19\n", - j, (int)buffer[j]); - unexpected = 1; - } - } - if (buffer[++j] != 0x0) { // [7] - ready = 1; - // MC usb-488 sends 0x5 here; MC usb-488A sends 0x6 here - if (buffer[j] != 0x3 && buffer[j] != 0x5 && buffer[j] != 0x6 && - buffer[j] != 0x8) { - dev_err(&usb_dev->dev, "unexpected data: buffer[%i]=0x%x, expected 0x3 or 0x5, 0x6 or 0x08\n", - j, (int)buffer[j]); - unexpected = 1; - } - } - ++j; - if (buffer[j] != 0x0 && buffer[j] != 0x2) { // [8] MC usb-488 sends 0x2 here - dev_err(&usb_dev->dev, " unexpected data: buffer[%i]=0x%x, expected 0x0 or 0x2\n", - j, (int)buffer[j]); - unexpected = 1; - } - ++j; - // MC usb-488A and NI-USB-HS sends 0x3 here; NI-USB-HS+ sends 0x30 here - if (buffer[j] != 0x0 && buffer[j] != 0x3 && buffer[j] != 0x30) { // [9] - dev_err(&usb_dev->dev, "unexpected data: buffer[%i]=0x%x, expected 0x0, 0x3 or 0x30\n", - j, (int)buffer[j]); - unexpected = 1; - } - if (buffer[++j] != 0x0) { // [10] MC usb-488 sends 0x7 here, new HS+ sends 0x59 - ready = 1; - if (buffer[j] != 0x96 && buffer[j] != 0x7 && buffer[j] != 0x6e && - buffer[j] != 0x59) { - dev_err(&usb_dev->dev, "unexpected data: buffer[%i]=0x%x, expected 0x96, 0x07, 0x6e or 0x59\n", - j, (int)buffer[j]); - unexpected = 1; - } - } - if (unexpected) - ni_usb_dump_raw_block(buffer, retval); - if (ready) - break; - retval = msleep_interruptible(msec_sleep_duration); - if (retval) { - retval = -ERESTARTSYS; - goto ready_out; - } - } - retval = 0; - -ready_out: - kfree(buffer); - dev_dbg(&usb_dev->dev, "exit retval=%d\n", retval); - return retval; -} - -/* - * This does some extra init for HS+ models, as observed on Windows. One of the - * control requests causes the LED to stop blinking. - * I'm not sure what the other 2 requests do. None of these requests are actually required - * for the adapter to work, maybe they do some init for the analyzer interface - * (which we don't use). - */ -static int ni_usb_hs_plus_extra_init(struct ni_usb_priv *ni_priv) -{ - struct usb_device *usb_dev = interface_to_usbdev(ni_priv->bus_interface); - int retval; - u8 *buffer; - static const int buffer_size = 16; - int transfer_size; - - buffer = kmalloc(buffer_size, GFP_KERNEL); - if (!buffer) - return -ENOMEM; - do { - transfer_size = 16; - - retval = ni_usb_receive_control_msg(ni_priv, NI_USB_HS_PLUS_0x48_REQUEST, - USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, - 0x0, 0x0, buffer, transfer_size, 1000); - if (retval < 0) { - dev_err(&usb_dev->dev, "usb_control_msg request 0x%x returned %i\n", - NI_USB_HS_PLUS_0x48_REQUEST, retval); - break; - } - // expected response data: 48 f3 30 00 00 00 00 00 00 00 00 00 00 00 00 00 - if (buffer[0] != NI_USB_HS_PLUS_0x48_REQUEST) - dev_err(&usb_dev->dev, "unexpected data: buffer[0]=0x%x, expected 0x%x\n", - (int)buffer[0], NI_USB_HS_PLUS_0x48_REQUEST); - - transfer_size = 2; - - retval = ni_usb_receive_control_msg(ni_priv, NI_USB_HS_PLUS_LED_REQUEST, - USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, - 0x1, 0x0, buffer, transfer_size, 1000); - if (retval < 0) { - dev_err(&usb_dev->dev, "usb_control_msg request 0x%x returned %i\n", - NI_USB_HS_PLUS_LED_REQUEST, retval); - break; - } - // expected response data: 4b 00 - if (buffer[0] != NI_USB_HS_PLUS_LED_REQUEST) - dev_err(&usb_dev->dev, "unexpected data: buffer[0]=0x%x, expected 0x%x\n", - (int)buffer[0], NI_USB_HS_PLUS_LED_REQUEST); - - transfer_size = 9; - - retval = ni_usb_receive_control_msg(ni_priv, NI_USB_HS_PLUS_0xf8_REQUEST, - USB_DIR_IN | USB_TYPE_VENDOR | - USB_RECIP_INTERFACE, - 0x0, 0x1, buffer, transfer_size, 1000); - if (retval < 0) { - dev_err(&usb_dev->dev, "usb_control_msg request 0x%x returned %i\n", - NI_USB_HS_PLUS_0xf8_REQUEST, retval); - break; - } - // expected response data: f8 01 00 00 00 01 00 00 00 - if (buffer[0] != NI_USB_HS_PLUS_0xf8_REQUEST) - dev_err(&usb_dev->dev, "unexpected data: buffer[0]=0x%x, expected 0x%x\n", - (int)buffer[0], NI_USB_HS_PLUS_0xf8_REQUEST); - } while (0); - - // cleanup - kfree(buffer); - return retval; -} - -static inline int ni_usb_device_match(struct usb_interface *interface, - const struct gpib_board_config *config) -{ - if (gpib_match_device_path(&interface->dev, config->device_path) == 0) - return 0; - return 1; -} - -static int ni_usb_attach(struct gpib_board *board, const struct gpib_board_config *config) -{ - int retval; - int i, index; - struct ni_usb_priv *ni_priv; - int product_id; - struct usb_device *usb_dev; - - mutex_lock(&ni_usb_hotplug_lock); - retval = ni_usb_allocate_private(board); - if (retval < 0) { - mutex_unlock(&ni_usb_hotplug_lock); - return retval; - } - ni_priv = board->private_data; - for (i = 0; i < MAX_NUM_NI_USB_INTERFACES; i++) { - if (ni_usb_driver_interfaces[i] && - !usb_get_intfdata(ni_usb_driver_interfaces[i]) && - ni_usb_device_match(ni_usb_driver_interfaces[i], config)) { - ni_priv->bus_interface = ni_usb_driver_interfaces[i]; - usb_set_intfdata(ni_usb_driver_interfaces[i], board); - usb_dev = interface_to_usbdev(ni_priv->bus_interface); - index = i; - break; - } - } - if (i == MAX_NUM_NI_USB_INTERFACES) { - mutex_unlock(&ni_usb_hotplug_lock); - dev_err(board->gpib_dev, "No supported adapters found, have you loaded its firmware?\n"); - return -ENODEV; - } - if (usb_reset_configuration(interface_to_usbdev(ni_priv->bus_interface))) - dev_err(&usb_dev->dev, "usb_reset_configuration() failed.\n"); - - product_id = le16_to_cpu(usb_dev->descriptor.idProduct); - ni_priv->product_id = product_id; - - timer_setup(&ni_priv->bulk_timer, ni_usb_timeout_handler, 0); - - switch (product_id) { - case USB_DEVICE_ID_NI_USB_B: - ni_priv->bulk_out_endpoint = NIUSB_B_BULK_OUT_ENDPOINT; - ni_priv->bulk_in_endpoint = NIUSB_B_BULK_IN_ENDPOINT; - ni_priv->interrupt_in_endpoint = NIUSB_B_INTERRUPT_IN_ENDPOINT; - ni_usb_b_read_serial_number(ni_priv); - break; - case USB_DEVICE_ID_NI_USB_HS: - case USB_DEVICE_ID_MC_USB_488: - case USB_DEVICE_ID_KUSB_488A: - ni_priv->bulk_out_endpoint = NIUSB_HS_BULK_OUT_ENDPOINT; - ni_priv->bulk_in_endpoint = NIUSB_HS_BULK_IN_ENDPOINT; - ni_priv->interrupt_in_endpoint = NIUSB_HS_INTERRUPT_IN_ENDPOINT; - retval = ni_usb_hs_wait_for_ready(ni_priv); - if (retval < 0) { - mutex_unlock(&ni_usb_hotplug_lock); - return retval; - } - break; - case USB_DEVICE_ID_NI_USB_HS_PLUS: - ni_priv->bulk_out_endpoint = NIUSB_HS_PLUS_BULK_OUT_ENDPOINT; - ni_priv->bulk_in_endpoint = NIUSB_HS_PLUS_BULK_IN_ENDPOINT; - ni_priv->interrupt_in_endpoint = NIUSB_HS_PLUS_INTERRUPT_IN_ENDPOINT; - retval = ni_usb_hs_wait_for_ready(ni_priv); - if (retval < 0) { - mutex_unlock(&ni_usb_hotplug_lock); - return retval; - } - retval = ni_usb_hs_plus_extra_init(ni_priv); - if (retval < 0) { - mutex_unlock(&ni_usb_hotplug_lock); - return retval; - } - break; - default: - mutex_unlock(&ni_usb_hotplug_lock); - dev_err(&usb_dev->dev, "\tDriver bug: unknown endpoints for usb device id %x\n", - product_id); - return -EINVAL; - } - - retval = ni_usb_setup_urbs(board); - if (retval < 0) { - mutex_unlock(&ni_usb_hotplug_lock); - return retval; - } - retval = ni_usb_set_interrupt_monitor(board, 0); - if (retval < 0) { - mutex_unlock(&ni_usb_hotplug_lock); - return retval; - } - - board->t1_nano_sec = 500; - - retval = ni_usb_init(board); - if (retval < 0) { - mutex_unlock(&ni_usb_hotplug_lock); - return retval; - } - retval = ni_usb_set_interrupt_monitor(board, ni_usb_ibsta_monitor_mask); - if (retval < 0) { - mutex_unlock(&ni_usb_hotplug_lock); - return retval; - } - - mutex_unlock(&ni_usb_hotplug_lock); - dev_info(&usb_dev->dev, - "bus %d dev num %d attached to gpib%d, intf %i\n", - usb_dev->bus->busnum, usb_dev->devnum, board->minor, index); - return retval; -} - -static int ni_usb_shutdown_hardware(struct ni_usb_priv *ni_priv) -{ - struct usb_device *usb_dev = interface_to_usbdev(ni_priv->bus_interface); - int retval; - struct ni_usb_register writes[2]; - static const int writes_length = ARRAY_SIZE(writes); - unsigned int ibsta; - - writes[0].device = NIUSB_SUBDEV_TNT4882; - writes[0].address = nec7210_to_tnt4882_offset(AUXMR); - writes[0].value = AUX_CR; - writes[1].device = NIUSB_SUBDEV_UNKNOWN3; - writes[1].address = 0x10; - writes[1].value = 0x0; - retval = ni_usb_write_registers(ni_priv, writes, writes_length, &ibsta); - if (retval) { - dev_err(&usb_dev->dev, "register write failed, retval=%i\n", retval); - return retval; - } - return 0; -} - -static void ni_usb_detach(struct gpib_board *board) -{ - struct ni_usb_priv *ni_priv; - - mutex_lock(&ni_usb_hotplug_lock); - /* - * under windows, software unplug does chip_reset nec7210 aux command, - * then writes 0x0 to address 0x10 of device 3 - */ - ni_priv = board->private_data; - if (ni_priv) { - if (ni_priv->bus_interface) { - ni_usb_set_interrupt_monitor(board, 0); - ni_usb_shutdown_hardware(ni_priv); - usb_set_intfdata(ni_priv->bus_interface, NULL); - } - mutex_lock(&ni_priv->bulk_transfer_lock); - mutex_lock(&ni_priv->control_transfer_lock); - mutex_lock(&ni_priv->interrupt_transfer_lock); - ni_usb_cleanup_urbs(ni_priv); - ni_usb_free_private(ni_priv); - } - mutex_unlock(&ni_usb_hotplug_lock); -} - -static struct gpib_interface ni_usb_gpib_interface = { - .name = "ni_usb_b", - .attach = ni_usb_attach, - .detach = ni_usb_detach, - .read = ni_usb_read, - .write = ni_usb_write, - .command = ni_usb_command, - .take_control = ni_usb_take_control, - .go_to_standby = ni_usb_go_to_standby, - .request_system_control = ni_usb_request_system_control, - .interface_clear = ni_usb_interface_clear, - .remote_enable = ni_usb_remote_enable, - .enable_eos = ni_usb_enable_eos, - .disable_eos = ni_usb_disable_eos, - .parallel_poll = ni_usb_parallel_poll, - .parallel_poll_configure = ni_usb_parallel_poll_configure, - .parallel_poll_response = ni_usb_parallel_poll_response, - .local_parallel_poll_mode = NULL, // XXX - .line_status = ni_usb_line_status, - .update_status = ni_usb_update_status, - .primary_address = ni_usb_primary_address, - .secondary_address = ni_usb_secondary_address, - .serial_poll_response = ni_usb_serial_poll_response, - .serial_poll_status = ni_usb_serial_poll_status, - .t1_delay = ni_usb_t1_delay, - .return_to_local = ni_usb_return_to_local, - .skip_check_for_command_acceptors = 1 -}; - -// Table with the USB-devices: just now only testing IDs -static struct usb_device_id ni_usb_driver_device_table[] = { - {USB_DEVICE(USB_VENDOR_ID_NI, USB_DEVICE_ID_NI_USB_B)}, - {USB_DEVICE(USB_VENDOR_ID_NI, USB_DEVICE_ID_NI_USB_HS)}, - // gpib-usb-hs+ has a second interface for the analyzer, which we ignore - {USB_DEVICE_INTERFACE_NUMBER(USB_VENDOR_ID_NI, USB_DEVICE_ID_NI_USB_HS_PLUS, 0)}, - {USB_DEVICE(USB_VENDOR_ID_NI, USB_DEVICE_ID_KUSB_488A)}, - {USB_DEVICE(USB_VENDOR_ID_NI, USB_DEVICE_ID_MC_USB_488)}, - {} /* Terminating entry */ -}; -MODULE_DEVICE_TABLE(usb, ni_usb_driver_device_table); - -static int ni_usb_driver_probe(struct usb_interface *interface, const struct usb_device_id *id) -{ - struct usb_device *usb_dev = interface_to_usbdev(interface); - int i; - char *path; - static const int path_length = 1024; - - mutex_lock(&ni_usb_hotplug_lock); - usb_get_dev(usb_dev); - for (i = 0; i < MAX_NUM_NI_USB_INTERFACES; i++) { - if (!ni_usb_driver_interfaces[i]) { - ni_usb_driver_interfaces[i] = interface; - usb_set_intfdata(interface, NULL); - break; - } - } - if (i == MAX_NUM_NI_USB_INTERFACES) { - usb_put_dev(usb_dev); - mutex_unlock(&ni_usb_hotplug_lock); - dev_err(&usb_dev->dev, "ni_usb_driver_interfaces[] full\n"); - return -1; - } - path = kmalloc(path_length, GFP_KERNEL); - if (!path) { - usb_put_dev(usb_dev); - mutex_unlock(&ni_usb_hotplug_lock); - return -ENOMEM; - } - usb_make_path(usb_dev, path, path_length); - dev_info(&usb_dev->dev, "probe succeeded for path: %s\n", path); - kfree(path); - mutex_unlock(&ni_usb_hotplug_lock); - return 0; -} - -static void ni_usb_driver_disconnect(struct usb_interface *interface) -{ - struct usb_device *usb_dev = interface_to_usbdev(interface); - int i; - - mutex_lock(&ni_usb_hotplug_lock); - for (i = 0; i < MAX_NUM_NI_USB_INTERFACES; i++) { - if (ni_usb_driver_interfaces[i] == interface) { - struct gpib_board *board = usb_get_intfdata(interface); - - if (board) { - struct ni_usb_priv *ni_priv = board->private_data; - - if (ni_priv) { - mutex_lock(&ni_priv->bulk_transfer_lock); - mutex_lock(&ni_priv->control_transfer_lock); - mutex_lock(&ni_priv->interrupt_transfer_lock); - ni_usb_cleanup_urbs(ni_priv); - ni_priv->bus_interface = NULL; - mutex_unlock(&ni_priv->interrupt_transfer_lock); - mutex_unlock(&ni_priv->control_transfer_lock); - mutex_unlock(&ni_priv->bulk_transfer_lock); - } - } - ni_usb_driver_interfaces[i] = NULL; - break; - } - } - if (i == MAX_NUM_NI_USB_INTERFACES) - dev_err(&usb_dev->dev, "unable to find interface bug?\n"); - usb_put_dev(usb_dev); - mutex_unlock(&ni_usb_hotplug_lock); -} - -static int ni_usb_driver_suspend(struct usb_interface *interface, pm_message_t message) -{ - struct usb_device *usb_dev = interface_to_usbdev(interface); - struct gpib_board *board; - int i, retval; - - mutex_lock(&ni_usb_hotplug_lock); - - for (i = 0; i < MAX_NUM_NI_USB_INTERFACES; i++) { - if (ni_usb_driver_interfaces[i] == interface) { - board = usb_get_intfdata(interface); - if (board) - break; - } - } - if (i == MAX_NUM_NI_USB_INTERFACES) { - mutex_unlock(&ni_usb_hotplug_lock); - return 0; - } - - struct ni_usb_priv *ni_priv = board->private_data; - - if (ni_priv) { - ni_usb_set_interrupt_monitor(board, 0); - retval = ni_usb_shutdown_hardware(ni_priv); - if (retval) { - mutex_unlock(&ni_usb_hotplug_lock); - return retval; - } - if (ni_priv->interrupt_urb) { - mutex_lock(&ni_priv->interrupt_transfer_lock); - ni_usb_cleanup_urbs(ni_priv); - mutex_unlock(&ni_priv->interrupt_transfer_lock); - } - dev_dbg(&usb_dev->dev, - "bus %d dev num %d gpib%d, interface %i suspended\n", - usb_dev->bus->busnum, usb_dev->devnum, board->minor, i); - } - - mutex_unlock(&ni_usb_hotplug_lock); - return 0; -} - -static int ni_usb_driver_resume(struct usb_interface *interface) -{ - struct usb_device *usb_dev = interface_to_usbdev(interface); - - struct gpib_board *board; - int i, retval; - - mutex_lock(&ni_usb_hotplug_lock); - - for (i = 0; i < MAX_NUM_NI_USB_INTERFACES; i++) { - if (ni_usb_driver_interfaces[i] == interface) { - board = usb_get_intfdata(interface); - if (board) - break; - } - } - if (i == MAX_NUM_NI_USB_INTERFACES) { - mutex_unlock(&ni_usb_hotplug_lock); - return 0; - } - - struct ni_usb_priv *ni_priv = board->private_data; - - if (ni_priv) { - if (ni_priv->interrupt_urb) { - mutex_lock(&ni_priv->interrupt_transfer_lock); - retval = usb_submit_urb(ni_priv->interrupt_urb, GFP_KERNEL); - if (retval) { - dev_err(&usb_dev->dev, "resume failed to resubmit interrupt urb, retval=%i\n", - retval); - mutex_unlock(&ni_priv->interrupt_transfer_lock); - mutex_unlock(&ni_usb_hotplug_lock); - return retval; - } - mutex_unlock(&ni_priv->interrupt_transfer_lock); - } else { - dev_err(&usb_dev->dev, "bug! resume int urb not set up\n"); - mutex_unlock(&ni_usb_hotplug_lock); - return -EINVAL; - } - - switch (ni_priv->product_id) { - case USB_DEVICE_ID_NI_USB_B: - ni_usb_b_read_serial_number(ni_priv); - break; - case USB_DEVICE_ID_NI_USB_HS: - case USB_DEVICE_ID_MC_USB_488: - case USB_DEVICE_ID_KUSB_488A: - retval = ni_usb_hs_wait_for_ready(ni_priv); - if (retval < 0) { - mutex_unlock(&ni_usb_hotplug_lock); - return retval; - } - break; - case USB_DEVICE_ID_NI_USB_HS_PLUS: - retval = ni_usb_hs_wait_for_ready(ni_priv); - if (retval < 0) { - mutex_unlock(&ni_usb_hotplug_lock); - return retval; - } - retval = ni_usb_hs_plus_extra_init(ni_priv); - if (retval < 0) { - mutex_unlock(&ni_usb_hotplug_lock); - return retval; - } - break; - default: - mutex_unlock(&ni_usb_hotplug_lock); - dev_err(&usb_dev->dev, "\tDriver bug: unknown endpoints for usb device id\n"); - return -EINVAL; - } - - retval = ni_usb_set_interrupt_monitor(board, 0); - if (retval < 0) { - mutex_unlock(&ni_usb_hotplug_lock); - return retval; - } - - retval = ni_usb_init(board); - if (retval < 0) { - mutex_unlock(&ni_usb_hotplug_lock); - return retval; - } - retval = ni_usb_set_interrupt_monitor(board, ni_usb_ibsta_monitor_mask); - if (retval < 0) { - mutex_unlock(&ni_usb_hotplug_lock); - return retval; - } - if (board->master) - ni_usb_interface_clear(board, 1); // this is a pulsed action - if (ni_priv->ren_state) - ni_usb_remote_enable(board, 1); - - dev_dbg(&usb_dev->dev, - "bus %d dev num %d gpib%d, interface %i resumed\n", - usb_dev->bus->busnum, usb_dev->devnum, board->minor, i); - } - - mutex_unlock(&ni_usb_hotplug_lock); - return 0; -} - -static struct usb_driver ni_usb_bus_driver = { - .name = DRV_NAME, - .probe = ni_usb_driver_probe, - .disconnect = ni_usb_driver_disconnect, - .suspend = ni_usb_driver_suspend, - .resume = ni_usb_driver_resume, - .id_table = ni_usb_driver_device_table, -}; - -static int __init ni_usb_init_module(void) -{ - int i; - int ret; - - for (i = 0; i < MAX_NUM_NI_USB_INTERFACES; i++) - ni_usb_driver_interfaces[i] = NULL; - - ret = usb_register(&ni_usb_bus_driver); - if (ret) { - pr_err("usb_register failed: error = %d\n", ret); - return ret; - } - - ret = gpib_register_driver(&ni_usb_gpib_interface, THIS_MODULE); - if (ret) { - pr_err("gpib_register_driver failed: error = %d\n", ret); - return ret; - } - - return 0; -} - -static void __exit ni_usb_exit_module(void) -{ - gpib_unregister_driver(&ni_usb_gpib_interface); - usb_deregister(&ni_usb_bus_driver); -} - -module_init(ni_usb_init_module); -module_exit(ni_usb_exit_module); diff --git a/drivers/staging/gpib/ni_usb/ni_usb_gpib.h b/drivers/staging/gpib/ni_usb/ni_usb_gpib.h deleted file mode 100644 index 688f5e08792f..000000000000 --- a/drivers/staging/gpib/ni_usb/ni_usb_gpib.h +++ /dev/null @@ -1,226 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ - -/*************************************************************************** - * copyright : (C) 2004 by Frank Mori Hess - ***************************************************************************/ - -#ifndef _NI_USB_GPIB_H -#define _NI_USB_GPIB_H - -#include -#include -#include -#include -#include "gpibP.h" - -enum { - USB_VENDOR_ID_NI = 0x3923 -}; - -enum { - USB_DEVICE_ID_NI_USB_B = 0x702a, - USB_DEVICE_ID_NI_USB_B_PREINIT = 0x702b, // device id before firmware is loaded - USB_DEVICE_ID_NI_USB_HS = 0x709b, - USB_DEVICE_ID_NI_USB_HS_PLUS = 0x7618, - USB_DEVICE_ID_KUSB_488A = 0x725c, - USB_DEVICE_ID_MC_USB_488 = 0x725d -}; - -enum ni_usb_device { - NIUSB_SUBDEV_TNT4882 = 1, - NIUSB_SUBDEV_UNKNOWN2 = 2, - NIUSB_SUBDEV_UNKNOWN3 = 3, -}; - -enum endpoint_addresses { - NIUSB_B_BULK_OUT_ENDPOINT = 0x2, - NIUSB_B_BULK_IN_ENDPOINT = 0x2, - NIUSB_B_BULK_IN_ALT_ENDPOINT = 0x6, - NIUSB_B_INTERRUPT_IN_ENDPOINT = 0x4, -}; - -enum hs_enpoint_addresses { - NIUSB_HS_BULK_OUT_ENDPOINT = 0x2, - NIUSB_HS_BULK_OUT_ALT_ENDPOINT = 0x6, - NIUSB_HS_BULK_IN_ENDPOINT = 0x4, - NIUSB_HS_BULK_IN_ALT_ENDPOINT = 0x8, - NIUSB_HS_INTERRUPT_IN_ENDPOINT = 0x1, -}; - -enum hs_plus_endpoint_addresses { - NIUSB_HS_PLUS_BULK_OUT_ENDPOINT = 0x1, - NIUSB_HS_PLUS_BULK_OUT_ALT_ENDPOINT = 0x4, - NIUSB_HS_PLUS_BULK_IN_ENDPOINT = 0x2, - NIUSB_HS_PLUS_BULK_IN_ALT_ENDPOINT = 0x5, - NIUSB_HS_PLUS_INTERRUPT_IN_ENDPOINT = 0x3, -}; - -struct ni_usb_urb_ctx { - struct completion complete; - unsigned timed_out : 1; -}; - -// struct which defines private_data for ni_usb devices -struct ni_usb_priv { - struct usb_interface *bus_interface; - int bulk_out_endpoint; - int bulk_in_endpoint; - int interrupt_in_endpoint; - u8 eos_char; - unsigned short eos_mode; - unsigned int monitored_ibsta_bits; - struct urb *bulk_urb; - struct urb *interrupt_urb; - u8 interrupt_buffer[0x11]; - struct mutex addressed_transfer_lock; // protect transfer lock - struct mutex bulk_transfer_lock; // protect bulk message sends - struct mutex control_transfer_lock; // protect control messages - struct mutex interrupt_transfer_lock; // protect interrupt messages - struct timer_list bulk_timer; - struct ni_usb_urb_ctx context; - int product_id; - unsigned short ren_state; -}; - -struct ni_usb_status_block { - short id; - unsigned short ibsta; - short error_code; - unsigned short count; -}; - -struct ni_usb_register { - enum ni_usb_device device; - short address; - unsigned short value; -}; - -enum ni_usb_bulk_ids { - NIUSB_IBCAC_ID = 0x1, - NIUSB_UNKNOWN3_ID = 0x3, // device level function id? - NIUSB_TERM_ID = 0x4, - NIUSB_IBGTS_ID = 0x6, - NIUSB_IBRPP_ID = 0x7, - NIUSB_REG_READ_ID = 0x8, - NIUSB_REG_WRITE_ID = 0x9, - NIUSB_IBSIC_ID = 0xf, - NIUSB_REGISTER_READ_DATA_START_ID = 0x34, - NIUSB_REGISTER_READ_DATA_END_ID = 0x35, - NIUSB_IBRD_DATA_ID = 0x36, - NIUSB_IBRD_EXTENDED_DATA_ID = 0x37, - NIUSB_IBRD_STATUS_ID = 0x38 -}; - -enum ni_usb_error_codes { - NIUSB_NO_ERROR = 0, - /* - * NIUSB_ABORTED_ERROR occurs when I/O is interrupted early by - * doing a NI_USB_STOP_REQUEST on the control endpoint. - */ - NIUSB_ABORTED_ERROR = 1, - /* - * NIUSB_READ_ATN_ERROR occurs when you do a board read while - * ATN is set - */ - NIUSB_ATN_STATE_ERROR = 2, - /* - * NIUSB_ADDRESSING_ERROR occurs when you do a board - * read/write as CIC but are not in LACS/TACS - */ - NIUSB_ADDRESSING_ERROR = 3, - /* - * NIUSB_EOSMODE_ERROR occurs on reads if any eos mode or char - * bits are set when REOS is not set. - * Have also seen error 4 if you try to send more than 16 - * command bytes at once on a usb-b. - */ - NIUSB_EOSMODE_ERROR = 4, - /* - * NIUSB_NO_BUS_ERROR occurs when you try to write a command - * byte but there are no devices connected to the gpib bus - */ - NIUSB_NO_BUS_ERROR = 5, - /* - * NIUSB_NO_LISTENER_ERROR occurs when you do a board write as - * CIC with no listener - */ - NIUSB_NO_LISTENER_ERROR = 8, - /* get NIUSB_TIMEOUT_ERROR on board read/write timeout */ - NIUSB_TIMEOUT_ERROR = 10, -}; - -enum ni_usb_control_requests { - NI_USB_STOP_REQUEST = 0x20, - NI_USB_WAIT_REQUEST = 0x21, - NI_USB_POLL_READY_REQUEST = 0x40, - NI_USB_SERIAL_NUMBER_REQUEST = 0x41, - NI_USB_HS_PLUS_0x48_REQUEST = 0x48, - NI_USB_HS_PLUS_LED_REQUEST = 0x4b, - NI_USB_HS_PLUS_0xf8_REQUEST = 0xf8 -}; - -static const unsigned int ni_usb_ibsta_monitor_mask = - SRQI | LOK | REM | CIC | ATN | TACS | LACS | DTAS | DCAS; - -static inline int nec7210_to_tnt4882_offset(int offset) -{ - return 2 * offset; -}; - -static inline int ni_usb_bulk_termination(u8 *buffer) -{ - int i = 0; - - buffer[i++] = NIUSB_TERM_ID; - buffer[i++] = 0x0; - buffer[i++] = 0x0; - buffer[i++] = 0x0; - return i; -} - -enum ni_usb_unknown3_register { - SERIAL_NUMBER_4_REG = 0x8, - SERIAL_NUMBER_3_REG = 0x9, - SERIAL_NUMBER_2_REG = 0xa, - SERIAL_NUMBER_1_REG = 0xb, -}; - -static inline int ni_usb_bulk_register_write_header(u8 *buffer, int num_writes) -{ - int i = 0; - - buffer[i++] = NIUSB_REG_WRITE_ID; - buffer[i++] = num_writes; - buffer[i++] = 0x0; - return i; -} - -static inline int ni_usb_bulk_register_write(u8 *buffer, struct ni_usb_register reg) -{ - int i = 0; - - buffer[i++] = reg.device; - buffer[i++] = reg.address; - buffer[i++] = reg.value; - return i; -} - -static inline int ni_usb_bulk_register_read_header(u8 *buffer, int num_reads) -{ - int i = 0; - - buffer[i++] = NIUSB_REG_READ_ID; - buffer[i++] = num_reads; - return i; -} - -static inline int ni_usb_bulk_register_read(u8 *buffer, int device, int address) -{ - int i = 0; - - buffer[i++] = device; - buffer[i++] = address; - return i; -} - -#endif // _NI_USB_GPIB_H diff --git a/drivers/staging/gpib/pc2/Makefile b/drivers/staging/gpib/pc2/Makefile deleted file mode 100644 index 481ee4296e1b..000000000000 --- a/drivers/staging/gpib/pc2/Makefile +++ /dev/null @@ -1,5 +0,0 @@ - -obj-$(CONFIG_GPIB_PC2) += pc2_gpib.o - - - diff --git a/drivers/staging/gpib/pc2/pc2_gpib.c b/drivers/staging/gpib/pc2/pc2_gpib.c deleted file mode 100644 index 9f3943d1df66..000000000000 --- a/drivers/staging/gpib/pc2/pc2_gpib.c +++ /dev/null @@ -1,684 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 - -/*************************************************************************** - * copyright : (C) 2001, 2002 by Frank Mori Hess - ***************************************************************************/ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -#define dev_fmt pr_fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "nec7210.h" -#include "gpibP.h" - -// struct which defines private_data for pc2 driver -struct pc2_priv { - struct nec7210_priv nec7210_priv; - unsigned int irq; - // io address that clears interrupt for pc2a (0x2f0 + irq) - unsigned int clear_intr_addr; -}; - -// pc2 uses 8 consecutive io addresses -static const int pc2_iosize = 8; -static const int pc2a_iosize = 8; -static const int pc2_2a_iosize = 16; - -// offset between io addresses of successive nec7210 registers -static const int pc2a_reg_offset = 0x400; -static const int pc2_reg_offset = 1; - -// interrupt service routine -static irqreturn_t pc2_interrupt(int irq, void *arg); -static irqreturn_t pc2a_interrupt(int irq, void *arg); - -// pc2 specific registers and bits - -// interrupt clear register address -static const int pc2a_clear_intr_iobase = 0x2f0; -static inline unsigned int CLEAR_INTR_REG(unsigned int irq) -{ - return pc2a_clear_intr_iobase + irq; -} - -MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("GPIB driver for PC2/PC2a and compatible devices"); - -/* - * GPIB interrupt service routines - */ - -irqreturn_t pc2_interrupt(int irq, void *arg) -{ - struct gpib_board *board = arg; - struct pc2_priv *priv = board->private_data; - unsigned long flags; - irqreturn_t retval; - - spin_lock_irqsave(&board->spinlock, flags); - retval = nec7210_interrupt(board, &priv->nec7210_priv); - spin_unlock_irqrestore(&board->spinlock, flags); - return retval; -} - -irqreturn_t pc2a_interrupt(int irq, void *arg) -{ - struct gpib_board *board = arg; - struct pc2_priv *priv = board->private_data; - int status1, status2; - unsigned long flags; - irqreturn_t retval; - - spin_lock_irqsave(&board->spinlock, flags); - // read interrupt status (also clears status) - status1 = read_byte(&priv->nec7210_priv, ISR1); - status2 = read_byte(&priv->nec7210_priv, ISR2); - /* clear interrupt circuit */ - if (priv->irq) - outb(0xff, CLEAR_INTR_REG(priv->irq)); - retval = nec7210_interrupt_have_status(board, &priv->nec7210_priv, status1, status2); - spin_unlock_irqrestore(&board->spinlock, flags); - return retval; -} - -// wrappers for interface functions -static int pc2_read(struct gpib_board *board, u8 *buffer, size_t length, int *end, - size_t *bytes_read) -{ - struct pc2_priv *priv = board->private_data; - - return nec7210_read(board, &priv->nec7210_priv, buffer, length, end, bytes_read); -} - -static int pc2_write(struct gpib_board *board, u8 *buffer, size_t length, int send_eoi, - size_t *bytes_written) -{ - struct pc2_priv *priv = board->private_data; - - return nec7210_write(board, &priv->nec7210_priv, buffer, length, send_eoi, bytes_written); -} - -static int pc2_command(struct gpib_board *board, u8 *buffer, - size_t length, size_t *bytes_written) -{ - struct pc2_priv *priv = board->private_data; - - return nec7210_command(board, &priv->nec7210_priv, buffer, length, bytes_written); -} - -static int pc2_take_control(struct gpib_board *board, int synchronous) -{ - struct pc2_priv *priv = board->private_data; - - return nec7210_take_control(board, &priv->nec7210_priv, synchronous); -} - -static int pc2_go_to_standby(struct gpib_board *board) -{ - struct pc2_priv *priv = board->private_data; - - return nec7210_go_to_standby(board, &priv->nec7210_priv); -} - -static int pc2_request_system_control(struct gpib_board *board, int request_control) -{ - struct pc2_priv *priv = board->private_data; - - return nec7210_request_system_control(board, &priv->nec7210_priv, request_control); -} - -static void pc2_interface_clear(struct gpib_board *board, int assert) -{ - struct pc2_priv *priv = board->private_data; - - nec7210_interface_clear(board, &priv->nec7210_priv, assert); -} - -static void pc2_remote_enable(struct gpib_board *board, int enable) -{ - struct pc2_priv *priv = board->private_data; - - nec7210_remote_enable(board, &priv->nec7210_priv, enable); -} - -static int pc2_enable_eos(struct gpib_board *board, u8 eos_byte, int compare_8_bits) -{ - struct pc2_priv *priv = board->private_data; - - return nec7210_enable_eos(board, &priv->nec7210_priv, eos_byte, compare_8_bits); -} - -static void pc2_disable_eos(struct gpib_board *board) -{ - struct pc2_priv *priv = board->private_data; - - nec7210_disable_eos(board, &priv->nec7210_priv); -} - -static unsigned int pc2_update_status(struct gpib_board *board, unsigned int clear_mask) -{ - struct pc2_priv *priv = board->private_data; - - return nec7210_update_status(board, &priv->nec7210_priv, clear_mask); -} - -static int pc2_primary_address(struct gpib_board *board, unsigned int address) -{ - struct pc2_priv *priv = board->private_data; - - return nec7210_primary_address(board, &priv->nec7210_priv, address); -} - -static int pc2_secondary_address(struct gpib_board *board, unsigned int address, int enable) -{ - struct pc2_priv *priv = board->private_data; - - return nec7210_secondary_address(board, &priv->nec7210_priv, address, enable); -} - -static int pc2_parallel_poll(struct gpib_board *board, u8 *result) -{ - struct pc2_priv *priv = board->private_data; - - return nec7210_parallel_poll(board, &priv->nec7210_priv, result); -} - -static void pc2_parallel_poll_configure(struct gpib_board *board, u8 config) -{ - struct pc2_priv *priv = board->private_data; - - nec7210_parallel_poll_configure(board, &priv->nec7210_priv, config); -} - -static void pc2_parallel_poll_response(struct gpib_board *board, int ist) -{ - struct pc2_priv *priv = board->private_data; - - nec7210_parallel_poll_response(board, &priv->nec7210_priv, ist); -} - -static void pc2_serial_poll_response(struct gpib_board *board, u8 status) -{ - struct pc2_priv *priv = board->private_data; - - nec7210_serial_poll_response(board, &priv->nec7210_priv, status); -} - -static u8 pc2_serial_poll_status(struct gpib_board *board) -{ - struct pc2_priv *priv = board->private_data; - - return nec7210_serial_poll_status(board, &priv->nec7210_priv); -} - -static int pc2_t1_delay(struct gpib_board *board, unsigned int nano_sec) -{ - struct pc2_priv *priv = board->private_data; - - return nec7210_t1_delay(board, &priv->nec7210_priv, nano_sec); -} - -static void pc2_return_to_local(struct gpib_board *board) -{ - struct pc2_priv *priv = board->private_data; - - nec7210_return_to_local(board, &priv->nec7210_priv); -} - -static int allocate_private(struct gpib_board *board) -{ - struct pc2_priv *priv; - - board->private_data = kmalloc(sizeof(struct pc2_priv), GFP_KERNEL); - if (!board->private_data) - return -1; - priv = board->private_data; - memset(priv, 0, sizeof(struct pc2_priv)); - init_nec7210_private(&priv->nec7210_priv); - return 0; -} - -static void free_private(struct gpib_board *board) -{ - kfree(board->private_data); - board->private_data = NULL; -} - -static int pc2_generic_attach(struct gpib_board *board, const struct gpib_board_config *config, - enum nec7210_chipset chipset) -{ - struct pc2_priv *pc2_priv; - struct nec7210_priv *nec_priv; - - board->status = 0; - if (allocate_private(board)) - return -ENOMEM; - pc2_priv = board->private_data; - nec_priv = &pc2_priv->nec7210_priv; - nec_priv->read_byte = nec7210_ioport_read_byte; - nec_priv->write_byte = nec7210_ioport_write_byte; - nec_priv->type = chipset; - -#ifndef PC2_DMA - /* - * board->dev hasn't been initialized, so forget about DMA until this driver - * is adapted to use isa_register_driver. - */ - if (config->ibdma) - // driver needs to be adapted to use isa_register_driver to get a struct device* - dev_err(board->gpib_dev, "DMA disabled for pc2 gpib"); -#else - if (config->ibdma) { - nec_priv->dma_buffer_length = 0x1000; - nec_priv->dma_buffer = dma_alloc_coherent(board->dev, - nec_priv->dma_buffer_length, & - nec_priv->dma_buffer_addr, GFP_ATOMIC); - if (!nec_priv->dma_buffer) - return -ENOMEM; - - // request isa dma channel - if (request_dma(config->ibdma, "pc2")) { - dev_err(board->gpib_dev, "can't request DMA %d\n", config->ibdma); - return -1; - } - nec_priv->dma_channel = config->ibdma; - } -#endif - - return 0; -} - -static int pc2_attach(struct gpib_board *board, const struct gpib_board_config *config) -{ - int isr_flags = 0; - struct pc2_priv *pc2_priv; - struct nec7210_priv *nec_priv; - int retval; - - retval = pc2_generic_attach(board, config, NEC7210); - if (retval) - return retval; - - pc2_priv = board->private_data; - nec_priv = &pc2_priv->nec7210_priv; - nec_priv->offset = pc2_reg_offset; - - if (!request_region(config->ibbase, pc2_iosize, "pc2")) { - dev_err(board->gpib_dev, "ioports are already in use\n"); - return -EBUSY; - } - nec_priv->iobase = config->ibbase; - - nec7210_board_reset(nec_priv, board); - - // install interrupt handler - if (config->ibirq) { - if (request_irq(config->ibirq, pc2_interrupt, isr_flags, "pc2", board)) { - dev_err(board->gpib_dev, "can't request IRQ %d\n", config->ibirq); - return -EBUSY; - } - } - pc2_priv->irq = config->ibirq; - /* poll so we can detect assertion of ATN */ - if (gpib_request_pseudo_irq(board, pc2_interrupt)) { - dev_err(board->gpib_dev, "failed to allocate pseudo_irq\n"); - return -1; - } - /* set internal counter register for 8 MHz input clock */ - write_byte(nec_priv, ICR | 8, AUXMR); - - nec7210_board_online(nec_priv, board); - - return 0; -} - -static void pc2_detach(struct gpib_board *board) -{ - struct pc2_priv *pc2_priv = board->private_data; - struct nec7210_priv *nec_priv; - - if (pc2_priv) { - nec_priv = &pc2_priv->nec7210_priv; -#ifdef PC2_DMA - if (nec_priv->dma_channel) - free_dma(nec_priv->dma_channel); -#endif - gpib_free_pseudo_irq(board); - if (pc2_priv->irq) - free_irq(pc2_priv->irq, board); - if (nec_priv->iobase) { - nec7210_board_reset(nec_priv, board); - release_region(nec_priv->iobase, pc2_iosize); - } - if (nec_priv->dma_buffer) { - dma_free_coherent(board->dev, nec_priv->dma_buffer_length, - nec_priv->dma_buffer, nec_priv->dma_buffer_addr); - nec_priv->dma_buffer = NULL; - } - } - free_private(board); -} - -static int pc2a_common_attach(struct gpib_board *board, const struct gpib_board_config *config, - unsigned int num_registers, enum nec7210_chipset chipset) -{ - unsigned int i, j; - struct pc2_priv *pc2_priv; - struct nec7210_priv *nec_priv; - int retval; - - retval = pc2_generic_attach(board, config, chipset); - if (retval) - return retval; - - pc2_priv = board->private_data; - nec_priv = &pc2_priv->nec7210_priv; - nec_priv->offset = pc2a_reg_offset; - - switch (config->ibbase) { - case 0x02e1: - case 0x22e1: - case 0x42e1: - case 0x62e1: - break; - default: - dev_err(board->gpib_dev, "PCIIa base range invalid, must be one of 0x[0246]2e1, but is 0x%x\n", - config->ibbase); - return -1; - } - - if (config->ibirq) { - if (config->ibirq < 2 || config->ibirq > 7) { - dev_err(board->gpib_dev, "illegal interrupt level %i\n", - config->ibirq); - return -1; - } - } else { - dev_err(board->gpib_dev, "interrupt disabled, using polling mode (slow)\n"); - } -#ifdef CHECK_IOPORTS - unsigned int err = 0; - - for (i = 0; i < num_registers; i++) { - if (check_region(config->ibbase + i * pc2a_reg_offset, 1)) - err++; - } - if (config->ibirq && check_region(pc2a_clear_intr_iobase + config->ibirq, 1)) - err++; - if (err) { - dev_err(board->gpib_dev, "ioports are already in use"); - return -EBUSY; - } -#endif - for (i = 0; i < num_registers; i++) { - if (!request_region(config->ibbase + - i * pc2a_reg_offset, 1, "pc2a")) { - dev_err(board->gpib_dev, "ioports are already in use"); - for (j = 0; j < i; j++) - release_region(config->ibbase + - j * pc2a_reg_offset, 1); - return -EBUSY; - } - } - nec_priv->iobase = config->ibbase; - if (config->ibirq) { - if (!request_region(pc2a_clear_intr_iobase + config->ibirq, 1, "pc2a")) { - dev_err(board->gpib_dev, "ioports are already in use"); - return -1; - } - pc2_priv->clear_intr_addr = pc2a_clear_intr_iobase + config->ibirq; - if (request_irq(config->ibirq, pc2a_interrupt, 0, "pc2a", board)) { - dev_err(board->gpib_dev, "can't request IRQ %d\n", config->ibirq); - return -EBUSY; - } - } - pc2_priv->irq = config->ibirq; - /* poll so we can detect assertion of ATN */ - if (gpib_request_pseudo_irq(board, pc2_interrupt)) { - dev_err(board->gpib_dev, "failed to allocate pseudo_irq\n"); - return -1; - } - - // make sure interrupt is clear - if (pc2_priv->irq) - outb(0xff, CLEAR_INTR_REG(pc2_priv->irq)); - - nec7210_board_reset(nec_priv, board); - - /* set internal counter register for 8 MHz input clock */ - write_byte(nec_priv, ICR | 8, AUXMR); - - nec7210_board_online(nec_priv, board); - - return 0; -} - -static int pc2a_attach(struct gpib_board *board, const struct gpib_board_config *config) -{ - return pc2a_common_attach(board, config, pc2a_iosize, NEC7210); -} - -static int pc2a_cb7210_attach(struct gpib_board *board, const struct gpib_board_config *config) -{ - return pc2a_common_attach(board, config, pc2a_iosize, CB7210); -} - -static int pc2_2a_attach(struct gpib_board *board, const struct gpib_board_config *config) -{ - return pc2a_common_attach(board, config, pc2_2a_iosize, NAT4882); -} - -static void pc2a_common_detach(struct gpib_board *board, unsigned int num_registers) -{ - int i; - struct pc2_priv *pc2_priv = board->private_data; - struct nec7210_priv *nec_priv; - - if (pc2_priv) { - nec_priv = &pc2_priv->nec7210_priv; -#ifdef PC2_DMA - if (nec_priv->dma_channel) - free_dma(nec_priv->dma_channel); -#endif - gpib_free_pseudo_irq(board); - if (pc2_priv->irq) - free_irq(pc2_priv->irq, board); - if (nec_priv->iobase) { - nec7210_board_reset(nec_priv, board); - for (i = 0; i < num_registers; i++) - release_region(nec_priv->iobase + - i * pc2a_reg_offset, 1); - } - if (pc2_priv->clear_intr_addr) - release_region(pc2_priv->clear_intr_addr, 1); - if (nec_priv->dma_buffer) { - dma_free_coherent(board->dev, nec_priv->dma_buffer_length, - nec_priv->dma_buffer, - nec_priv->dma_buffer_addr); - nec_priv->dma_buffer = NULL; - } - } - free_private(board); -} - -static void pc2a_detach(struct gpib_board *board) -{ - pc2a_common_detach(board, pc2a_iosize); -} - -static void pc2_2a_detach(struct gpib_board *board) -{ - pc2a_common_detach(board, pc2_2a_iosize); -} - -static struct gpib_interface pc2_interface = { - .name = "pcII", - .attach = pc2_attach, - .detach = pc2_detach, - .read = pc2_read, - .write = pc2_write, - .command = pc2_command, - .take_control = pc2_take_control, - .go_to_standby = pc2_go_to_standby, - .request_system_control = pc2_request_system_control, - .interface_clear = pc2_interface_clear, - .remote_enable = pc2_remote_enable, - .enable_eos = pc2_enable_eos, - .disable_eos = pc2_disable_eos, - .parallel_poll = pc2_parallel_poll, - .parallel_poll_configure = pc2_parallel_poll_configure, - .parallel_poll_response = pc2_parallel_poll_response, - .local_parallel_poll_mode = NULL, // XXX - .line_status = NULL, - .update_status = pc2_update_status, - .primary_address = pc2_primary_address, - .secondary_address = pc2_secondary_address, - .serial_poll_response = pc2_serial_poll_response, - .serial_poll_status = pc2_serial_poll_status, - .t1_delay = pc2_t1_delay, - .return_to_local = pc2_return_to_local, -}; - -static struct gpib_interface pc2a_interface = { - .name = "pcIIa", - .attach = pc2a_attach, - .detach = pc2a_detach, - .read = pc2_read, - .write = pc2_write, - .command = pc2_command, - .take_control = pc2_take_control, - .go_to_standby = pc2_go_to_standby, - .request_system_control = pc2_request_system_control, - .interface_clear = pc2_interface_clear, - .remote_enable = pc2_remote_enable, - .enable_eos = pc2_enable_eos, - .disable_eos = pc2_disable_eos, - .parallel_poll = pc2_parallel_poll, - .parallel_poll_configure = pc2_parallel_poll_configure, - .parallel_poll_response = pc2_parallel_poll_response, - .local_parallel_poll_mode = NULL, // XXX - .line_status = NULL, - .update_status = pc2_update_status, - .primary_address = pc2_primary_address, - .secondary_address = pc2_secondary_address, - .serial_poll_response = pc2_serial_poll_response, - .serial_poll_status = pc2_serial_poll_status, - .t1_delay = pc2_t1_delay, - .return_to_local = pc2_return_to_local, -}; - -static struct gpib_interface pc2a_cb7210_interface = { - .name = "pcIIa_cb7210", - .attach = pc2a_cb7210_attach, - .detach = pc2a_detach, - .read = pc2_read, - .write = pc2_write, - .command = pc2_command, - .take_control = pc2_take_control, - .go_to_standby = pc2_go_to_standby, - .request_system_control = pc2_request_system_control, - .interface_clear = pc2_interface_clear, - .remote_enable = pc2_remote_enable, - .enable_eos = pc2_enable_eos, - .disable_eos = pc2_disable_eos, - .parallel_poll = pc2_parallel_poll, - .parallel_poll_configure = pc2_parallel_poll_configure, - .parallel_poll_response = pc2_parallel_poll_response, - .local_parallel_poll_mode = NULL, // XXX - .line_status = NULL, // XXX - .update_status = pc2_update_status, - .primary_address = pc2_primary_address, - .secondary_address = pc2_secondary_address, - .serial_poll_response = pc2_serial_poll_response, - .serial_poll_status = pc2_serial_poll_status, - .t1_delay = pc2_t1_delay, - .return_to_local = pc2_return_to_local, -}; - -static struct gpib_interface pc2_2a_interface = { - .name = "pcII_IIa", - .attach = pc2_2a_attach, - .detach = pc2_2a_detach, - .read = pc2_read, - .write = pc2_write, - .command = pc2_command, - .take_control = pc2_take_control, - .go_to_standby = pc2_go_to_standby, - .request_system_control = pc2_request_system_control, - .interface_clear = pc2_interface_clear, - .remote_enable = pc2_remote_enable, - .enable_eos = pc2_enable_eos, - .disable_eos = pc2_disable_eos, - .parallel_poll = pc2_parallel_poll, - .parallel_poll_configure = pc2_parallel_poll_configure, - .parallel_poll_response = pc2_parallel_poll_response, - .local_parallel_poll_mode = NULL, // XXX - .line_status = NULL, - .update_status = pc2_update_status, - .primary_address = pc2_primary_address, - .secondary_address = pc2_secondary_address, - .serial_poll_response = pc2_serial_poll_response, - .serial_poll_status = pc2_serial_poll_status, - .t1_delay = pc2_t1_delay, - .return_to_local = pc2_return_to_local, -}; - -static int __init pc2_init_module(void) -{ - int ret; - - ret = gpib_register_driver(&pc2_interface, THIS_MODULE); - if (ret) { - pr_err("gpib_register_driver failed: error = %d\n", ret); - return ret; - } - - ret = gpib_register_driver(&pc2a_interface, THIS_MODULE); - if (ret) { - pr_err("gpib_register_driver failed: error = %d\n", ret); - goto err_pc2a; - } - - ret = gpib_register_driver(&pc2a_cb7210_interface, THIS_MODULE); - if (ret) { - pr_err("gpib_register_driver failed: error = %d\n", ret); - goto err_cb7210; - } - - ret = gpib_register_driver(&pc2_2a_interface, THIS_MODULE); - if (ret) { - pr_err("gpib_register_driver failed: error = %d\n", ret); - goto err_pc2_2a; - } - - return 0; - -err_pc2_2a: - gpib_unregister_driver(&pc2a_cb7210_interface); -err_cb7210: - gpib_unregister_driver(&pc2a_interface); -err_pc2a: - gpib_unregister_driver(&pc2_interface); - - return ret; -} - -static void __exit pc2_exit_module(void) -{ - gpib_unregister_driver(&pc2_interface); - gpib_unregister_driver(&pc2a_interface); - gpib_unregister_driver(&pc2a_cb7210_interface); - gpib_unregister_driver(&pc2_2a_interface); -} - -module_init(pc2_init_module); -module_exit(pc2_exit_module); - diff --git a/drivers/staging/gpib/tms9914/Makefile b/drivers/staging/gpib/tms9914/Makefile deleted file mode 100644 index 4705ab07f413..000000000000 --- a/drivers/staging/gpib/tms9914/Makefile +++ /dev/null @@ -1,6 +0,0 @@ - -obj-$(CONFIG_GPIB_TMS9914) += tms9914.o - - - - diff --git a/drivers/staging/gpib/tms9914/tms9914.c b/drivers/staging/gpib/tms9914/tms9914.c deleted file mode 100644 index 72a11596a35e..000000000000 --- a/drivers/staging/gpib/tms9914/tms9914.c +++ /dev/null @@ -1,914 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 - -/*************************************************************************** - * copyright : (C) 2001, 2002 by Frank Mori Hess - ***************************************************************************/ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -#define dev_fmt pr_fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "gpibP.h" -#include "tms9914.h" - -MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("GPIB library for tms9914"); - -static unsigned int update_status_nolock(struct gpib_board *board, struct tms9914_priv *priv); - -int tms9914_take_control(struct gpib_board *board, struct tms9914_priv *priv, int synchronous) -{ - int i; - const int timeout = 100; - - if (synchronous) - write_byte(priv, AUX_TCS, AUXCR); - else - write_byte(priv, AUX_TCA, AUXCR); - // busy wait until ATN is asserted - for (i = 0; i < timeout; i++) { - if ((read_byte(priv, ADSR) & HR_ATN)) - break; - udelay(1); - } - if (i == timeout) - return -ETIMEDOUT; - - clear_bit(WRITE_READY_BN, &priv->state); - - return 0; -} -EXPORT_SYMBOL_GPL(tms9914_take_control); - -/* - * The agilent 82350B has a buggy implementation of tcs which interferes with the - * operation of tca. It appears to be based on the controller state machine - * described in the TI 9900 TMS9914A data manual published in 1982. This - * manual describes tcs as putting the controller into a CWAS - * state where it waits indefinitely for ANRS and ignores tca. Since a - * functioning tca is far more important than tcs, we work around the - * problem by never issuing tcs. - * - * I don't know if this problem exists in the real tms9914a or just in the fpga - * of the 82350B. For now, only the agilent_82350b uses this workaround. - * The rest of the tms9914 based drivers still use tms9914_take_control - * directly (which does issue tcs). - */ -int tms9914_take_control_workaround(struct gpib_board *board, - struct tms9914_priv *priv, int synchronous) -{ - if (synchronous) - return -ETIMEDOUT; - return tms9914_take_control(board, priv, synchronous); -} -EXPORT_SYMBOL_GPL(tms9914_take_control_workaround); - -int tms9914_go_to_standby(struct gpib_board *board, struct tms9914_priv *priv) -{ - int i; - const int timeout = 1000; - - write_byte(priv, AUX_GTS, AUXCR); - // busy wait until ATN is released - for (i = 0; i < timeout; i++) { - if ((read_byte(priv, ADSR) & HR_ATN) == 0) - break; - udelay(1); - } - if (i == timeout) - return -ETIMEDOUT; - - clear_bit(COMMAND_READY_BN, &priv->state); - - return 0; -} -EXPORT_SYMBOL_GPL(tms9914_go_to_standby); - -void tms9914_interface_clear(struct gpib_board *board, struct tms9914_priv *priv, int assert) -{ - if (assert) { - write_byte(priv, AUX_SIC | AUX_CS, AUXCR); - - set_bit(CIC_NUM, &board->status); - } else { - write_byte(priv, AUX_SIC, AUXCR); - } -} -EXPORT_SYMBOL_GPL(tms9914_interface_clear); - -void tms9914_remote_enable(struct gpib_board *board, struct tms9914_priv *priv, int enable) -{ - if (enable) - write_byte(priv, AUX_SRE | AUX_CS, AUXCR); - else - write_byte(priv, AUX_SRE, AUXCR); -} -EXPORT_SYMBOL_GPL(tms9914_remote_enable); - -int tms9914_request_system_control(struct gpib_board *board, struct tms9914_priv *priv, - int request_control) -{ - if (request_control) { - write_byte(priv, AUX_RQC, AUXCR); - } else { - clear_bit(CIC_NUM, &board->status); - write_byte(priv, AUX_RLC, AUXCR); - } - return 0; -} -EXPORT_SYMBOL_GPL(tms9914_request_system_control); - -unsigned int tms9914_t1_delay(struct gpib_board *board, struct tms9914_priv *priv, - unsigned int nano_sec) -{ - static const int clock_period = 200; // assuming 5Mhz input clock - int num_cycles; - - num_cycles = 12; - - if (nano_sec <= 8 * clock_period) { - write_byte(priv, AUX_STDL | AUX_CS, AUXCR); - num_cycles = 8; - } else { - write_byte(priv, AUX_STDL, AUXCR); - } - - if (nano_sec <= 4 * clock_period) { - write_byte(priv, AUX_VSTDL | AUX_CS, AUXCR); - num_cycles = 4; - } else { - write_byte(priv, AUX_VSTDL, AUXCR); - } - - return num_cycles * clock_period; -} -EXPORT_SYMBOL_GPL(tms9914_t1_delay); - -void tms9914_return_to_local(const struct gpib_board *board, struct tms9914_priv *priv) -{ - write_byte(priv, AUX_RTL, AUXCR); -} -EXPORT_SYMBOL_GPL(tms9914_return_to_local); - -void tms9914_set_holdoff_mode(struct tms9914_priv *priv, enum tms9914_holdoff_mode mode) -{ - switch (mode) { - case TMS9914_HOLDOFF_NONE: - write_byte(priv, AUX_HLDE, AUXCR); - write_byte(priv, AUX_HLDA, AUXCR); - break; - case TMS9914_HOLDOFF_EOI: - write_byte(priv, AUX_HLDE | AUX_CS, AUXCR); - write_byte(priv, AUX_HLDA, AUXCR); - break; - case TMS9914_HOLDOFF_ALL: - write_byte(priv, AUX_HLDE, AUXCR); - write_byte(priv, AUX_HLDA | AUX_CS, AUXCR); - break; - default: - pr_err("bug! bad holdoff mode %i\n", mode); - break; - } - priv->holdoff_mode = mode; -} -EXPORT_SYMBOL_GPL(tms9914_set_holdoff_mode); - -void tms9914_release_holdoff(struct tms9914_priv *priv) -{ - if (priv->holdoff_active) { - write_byte(priv, AUX_RHDF, AUXCR); - priv->holdoff_active = 0; - } -} -EXPORT_SYMBOL_GPL(tms9914_release_holdoff); - -int tms9914_enable_eos(struct gpib_board *board, struct tms9914_priv *priv, u8 eos_byte, - int compare_8_bits) -{ - priv->eos = eos_byte; - priv->eos_flags = REOS; - if (compare_8_bits) - priv->eos_flags |= BIN; - return 0; -} -EXPORT_SYMBOL(tms9914_enable_eos); - -void tms9914_disable_eos(struct gpib_board *board, struct tms9914_priv *priv) -{ - priv->eos_flags &= ~REOS; -} -EXPORT_SYMBOL(tms9914_disable_eos); - -int tms9914_parallel_poll(struct gpib_board *board, struct tms9914_priv *priv, u8 *result) -{ - // execute parallel poll - write_byte(priv, AUX_CS | AUX_RPP, AUXCR); - udelay(2); - *result = read_byte(priv, CPTR); - // clear parallel poll state - write_byte(priv, AUX_RPP, AUXCR); - return 0; -} -EXPORT_SYMBOL(tms9914_parallel_poll); - -static void set_ppoll_reg(struct tms9914_priv *priv, int enable, - unsigned int dio_line, int sense, int ist) -{ - u8 dio_byte; - - if (enable && ((sense && ist) || (!sense && !ist))) { - dio_byte = 1 << (dio_line - 1); - write_byte(priv, dio_byte, PPR); - } else { - write_byte(priv, 0, PPR); - } -} - -void tms9914_parallel_poll_configure(struct gpib_board *board, - struct tms9914_priv *priv, u8 config) -{ - priv->ppoll_enable = (config & PPC_DISABLE) == 0; - priv->ppoll_line = (config & PPC_DIO_MASK) + 1; - priv->ppoll_sense = (config & PPC_SENSE) != 0; - set_ppoll_reg(priv, priv->ppoll_enable, priv->ppoll_line, priv->ppoll_sense, board->ist); -} -EXPORT_SYMBOL(tms9914_parallel_poll_configure); - -void tms9914_parallel_poll_response(struct gpib_board *board, - struct tms9914_priv *priv, int ist) -{ - set_ppoll_reg(priv, priv->ppoll_enable, priv->ppoll_line, priv->ppoll_sense, ist); -} -EXPORT_SYMBOL(tms9914_parallel_poll_response); - -void tms9914_serial_poll_response(struct gpib_board *board, - struct tms9914_priv *priv, u8 status) -{ - unsigned long flags; - - spin_lock_irqsave(&board->spinlock, flags); - write_byte(priv, status, SPMR); - priv->spoll_status = status; - if (status & request_service_bit) - write_byte(priv, AUX_RSV2 | AUX_CS, AUXCR); - else - write_byte(priv, AUX_RSV2, AUXCR); - spin_unlock_irqrestore(&board->spinlock, flags); -} -EXPORT_SYMBOL(tms9914_serial_poll_response); - -u8 tms9914_serial_poll_status(struct gpib_board *board, struct tms9914_priv *priv) -{ - u8 status; - unsigned long flags; - - spin_lock_irqsave(&board->spinlock, flags); - status = priv->spoll_status; - spin_unlock_irqrestore(&board->spinlock, flags); - - return status; -} -EXPORT_SYMBOL(tms9914_serial_poll_status); - -int tms9914_primary_address(struct gpib_board *board, - struct tms9914_priv *priv, unsigned int address) -{ - // put primary address in address0 - write_byte(priv, address & ADDRESS_MASK, ADR); - return 0; -} -EXPORT_SYMBOL(tms9914_primary_address); - -int tms9914_secondary_address(struct gpib_board *board, struct tms9914_priv *priv, - unsigned int address, int enable) -{ - if (enable) - priv->imr1_bits |= HR_APTIE; - else - priv->imr1_bits &= ~HR_APTIE; - - write_byte(priv, priv->imr1_bits, IMR1); - return 0; -} -EXPORT_SYMBOL(tms9914_secondary_address); - -unsigned int tms9914_update_status(struct gpib_board *board, struct tms9914_priv *priv, - unsigned int clear_mask) -{ - unsigned long flags; - unsigned int retval; - - spin_lock_irqsave(&board->spinlock, flags); - retval = update_status_nolock(board, priv); - board->status &= ~clear_mask; - spin_unlock_irqrestore(&board->spinlock, flags); - - return retval; -} -EXPORT_SYMBOL(tms9914_update_status); - -static void update_talker_state(struct tms9914_priv *priv, unsigned int address_status_bits) -{ - if (address_status_bits & HR_TA) { - if (address_status_bits & HR_ATN) - priv->talker_state = talker_addressed; - else - /* - * this could also be serial_poll_active, but the tms9914 provides no - * way to distinguish, so we'll assume talker_active - */ - priv->talker_state = talker_active; - } else { - priv->talker_state = talker_idle; - } -} - -static void update_listener_state(struct tms9914_priv *priv, unsigned int address_status_bits) -{ - if (address_status_bits & HR_LA) { - if (address_status_bits & HR_ATN) - priv->listener_state = listener_addressed; - else - priv->listener_state = listener_active; - } else { - priv->listener_state = listener_idle; - } -} - -static unsigned int update_status_nolock(struct gpib_board *board, struct tms9914_priv *priv) -{ - int address_status; - int bsr_bits; - - address_status = read_byte(priv, ADSR); - - // check for remote/local - if (address_status & HR_REM) - set_bit(REM_NUM, &board->status); - else - clear_bit(REM_NUM, &board->status); - // check for lockout - if (address_status & HR_LLO) - set_bit(LOK_NUM, &board->status); - else - clear_bit(LOK_NUM, &board->status); - // check for ATN - if (address_status & HR_ATN) - set_bit(ATN_NUM, &board->status); - else - clear_bit(ATN_NUM, &board->status); - // check for talker/listener addressed - update_talker_state(priv, address_status); - if (priv->talker_state == talker_active || priv->talker_state == talker_addressed) - set_bit(TACS_NUM, &board->status); - else - clear_bit(TACS_NUM, &board->status); - - update_listener_state(priv, address_status); - if (priv->listener_state == listener_active || priv->listener_state == listener_addressed) - set_bit(LACS_NUM, &board->status); - else - clear_bit(LACS_NUM, &board->status); - // Check for SRQI - not reset elsewhere except in autospoll - if (board->status & SRQI) { - bsr_bits = read_byte(priv, BSR); - if (!(bsr_bits & BSR_SRQ_BIT)) - clear_bit(SRQI_NUM, &board->status); - } - - dev_dbg(board->gpib_dev, "status 0x%lx, state 0x%lx\n", board->status, priv->state); - - return board->status; -} - -int tms9914_line_status(const struct gpib_board *board, struct tms9914_priv *priv) -{ - int bsr_bits; - int status = VALID_ALL; - - bsr_bits = read_byte(priv, BSR); - - if (bsr_bits & BSR_REN_BIT) - status |= BUS_REN; - if (bsr_bits & BSR_IFC_BIT) - status |= BUS_IFC; - if (bsr_bits & BSR_SRQ_BIT) - status |= BUS_SRQ; - if (bsr_bits & BSR_EOI_BIT) - status |= BUS_EOI; - if (bsr_bits & BSR_NRFD_BIT) - status |= BUS_NRFD; - if (bsr_bits & BSR_NDAC_BIT) - status |= BUS_NDAC; - if (bsr_bits & BSR_DAV_BIT) - status |= BUS_DAV; - if (bsr_bits & BSR_ATN_BIT) - status |= BUS_ATN; - - return status; -} -EXPORT_SYMBOL(tms9914_line_status); - -static int check_for_eos(struct tms9914_priv *priv, u8 byte) -{ - static const u8 seven_bit_compare_mask = 0x7f; - - if ((priv->eos_flags & REOS) == 0) - return 0; - - if (priv->eos_flags & BIN) { - if (priv->eos == byte) - return 1; - } else { - if ((priv->eos & seven_bit_compare_mask) == (byte & seven_bit_compare_mask)) - return 1; - } - return 0; -} - -static int wait_for_read_byte(struct gpib_board *board, struct tms9914_priv *priv) -{ - if (wait_event_interruptible(board->wait, - test_bit(READ_READY_BN, &priv->state) || - test_bit(DEV_CLEAR_BN, &priv->state) || - test_bit(TIMO_NUM, &board->status))) - return -ERESTARTSYS; - - if (test_bit(TIMO_NUM, &board->status)) - return -ETIMEDOUT; - - if (test_bit(DEV_CLEAR_BN, &priv->state)) - return -EINTR; - return 0; -} - -static inline u8 tms9914_read_data_in(struct gpib_board *board, - struct tms9914_priv *priv, int *end) -{ - unsigned long flags; - u8 data; - - spin_lock_irqsave(&board->spinlock, flags); - clear_bit(READ_READY_BN, &priv->state); - data = read_byte(priv, DIR); - if (test_and_clear_bit(RECEIVED_END_BN, &priv->state)) - *end = 1; - else - *end = 0; - switch (priv->holdoff_mode) { - case TMS9914_HOLDOFF_EOI: - if (*end) - priv->holdoff_active = 1; - break; - case TMS9914_HOLDOFF_ALL: - priv->holdoff_active = 1; - break; - case TMS9914_HOLDOFF_NONE: - break; - default: - dev_err(board->gpib_dev, "bug! bad holdoff mode %i\n", priv->holdoff_mode); - break; - } - spin_unlock_irqrestore(&board->spinlock, flags); - - return data; -} - -static int pio_read(struct gpib_board *board, struct tms9914_priv *priv, u8 *buffer, - size_t length, int *end, size_t *bytes_read) -{ - ssize_t retval = 0; - - *bytes_read = 0; - *end = 0; - while (*bytes_read < length && *end == 0) { - tms9914_release_holdoff(priv); - retval = wait_for_read_byte(board, priv); - if (retval < 0) - return retval; - buffer[(*bytes_read)++] = tms9914_read_data_in(board, priv, end); - - if (check_for_eos(priv, buffer[*bytes_read - 1])) - *end = 1; - } - - return retval; -} - -int tms9914_read(struct gpib_board *board, struct tms9914_priv *priv, u8 *buffer, - size_t length, int *end, size_t *bytes_read) -{ - ssize_t retval = 0; - size_t num_bytes; - - *end = 0; - *bytes_read = 0; - if (length == 0) - return 0; - - clear_bit(DEV_CLEAR_BN, &priv->state); - - // transfer data (except for last byte) - if (length > 1) { - if (priv->eos_flags & REOS) - tms9914_set_holdoff_mode(priv, TMS9914_HOLDOFF_ALL); - else - tms9914_set_holdoff_mode(priv, TMS9914_HOLDOFF_EOI); - // PIO transfer - retval = pio_read(board, priv, buffer, length - 1, end, &num_bytes); - *bytes_read += num_bytes; - if (retval < 0) - return retval; - buffer += num_bytes; - length -= num_bytes; - } - // read last bytes if we haven't received an END yet - if (*end == 0) { - // make sure we holdoff after last byte read - tms9914_set_holdoff_mode(priv, TMS9914_HOLDOFF_ALL); - retval = pio_read(board, priv, buffer, length, end, &num_bytes); - *bytes_read += num_bytes; - if (retval < 0) - return retval; - } - return 0; -} -EXPORT_SYMBOL(tms9914_read); - -static int pio_write_wait(struct gpib_board *board, struct tms9914_priv *priv) -{ - // wait until next byte is ready to be sent - if (wait_event_interruptible(board->wait, - test_bit(WRITE_READY_BN, &priv->state) || - test_bit(BUS_ERROR_BN, &priv->state) || - test_bit(DEV_CLEAR_BN, &priv->state) || - test_bit(TIMO_NUM, &board->status))) - return -ERESTARTSYS; - - if (test_bit(TIMO_NUM, &board->status)) - return -ETIMEDOUT; - if (test_bit(BUS_ERROR_BN, &priv->state)) - return -EIO; - if (test_bit(DEV_CLEAR_BN, &priv->state)) - return -EINTR; - - return 0; -} - -static int pio_write(struct gpib_board *board, struct tms9914_priv *priv, u8 *buffer, - size_t length, size_t *bytes_written) -{ - ssize_t retval = 0; - unsigned long flags; - - *bytes_written = 0; - while (*bytes_written < length) { - retval = pio_write_wait(board, priv); - if (retval < 0) - break; - - spin_lock_irqsave(&board->spinlock, flags); - clear_bit(WRITE_READY_BN, &priv->state); - write_byte(priv, buffer[(*bytes_written)++], CDOR); - spin_unlock_irqrestore(&board->spinlock, flags); - } - retval = pio_write_wait(board, priv); - if (retval < 0) - return retval; - - return length; -} - -int tms9914_write(struct gpib_board *board, struct tms9914_priv *priv, - u8 *buffer, size_t length, int send_eoi, size_t *bytes_written) -{ - ssize_t retval = 0; - - *bytes_written = 0; - if (length == 0) - return 0; - - clear_bit(BUS_ERROR_BN, &priv->state); - clear_bit(DEV_CLEAR_BN, &priv->state); - - if (send_eoi) - length-- ; /* save the last byte for sending EOI */ - - if (length > 0) { - size_t num_bytes; - // PIO transfer - retval = pio_write(board, priv, buffer, length, &num_bytes); - *bytes_written += num_bytes; - if (retval < 0) - return retval; - } - if (send_eoi) { - size_t num_bytes; - /*send EOI */ - write_byte(priv, AUX_SEOI, AUXCR); - - retval = pio_write(board, priv, &buffer[*bytes_written], 1, &num_bytes); - *bytes_written += num_bytes; - } - return retval; -} -EXPORT_SYMBOL(tms9914_write); - -static void check_my_address_state(struct gpib_board *board, - struct tms9914_priv *priv, int cmd_byte) -{ - if (cmd_byte == MLA(board->pad)) { - priv->primary_listen_addressed = 1; - // become active listener - if (board->sad < 0) - write_byte(priv, AUX_LON | AUX_CS, AUXCR); - } else if (board->sad >= 0 && priv->primary_listen_addressed && - cmd_byte == MSA(board->sad)) { - // become active listener - write_byte(priv, AUX_LON | AUX_CS, AUXCR); - } else if (cmd_byte != MLA(board->pad) && (cmd_byte & 0xe0) == LAD) { - priv->primary_listen_addressed = 0; - } else if (cmd_byte == UNL) { - priv->primary_listen_addressed = 0; - write_byte(priv, AUX_LON, AUXCR); - } else if (cmd_byte == MTA(board->pad)) { - priv->primary_talk_addressed = 1; - if (board->sad < 0) - // make active talker - write_byte(priv, AUX_TON | AUX_CS, AUXCR); - } else if (board->sad >= 0 && priv->primary_talk_addressed && - cmd_byte == MSA(board->sad)) { - // become active talker - write_byte(priv, AUX_TON | AUX_CS, AUXCR); - } else if (cmd_byte != MTA(board->pad) && (cmd_byte & 0xe0) == TAD) { - // Other Talk Address - priv->primary_talk_addressed = 0; - write_byte(priv, AUX_TON, AUXCR); - } else if (cmd_byte == UNT) { - priv->primary_talk_addressed = 0; - write_byte(priv, AUX_TON, AUXCR); - } -} - -int tms9914_command(struct gpib_board *board, struct tms9914_priv *priv, u8 *buffer, - size_t length, size_t *bytes_written) -{ - int retval = 0; - unsigned long flags; - - *bytes_written = 0; - while (*bytes_written < length) { - if (wait_event_interruptible(board->wait, - test_bit(COMMAND_READY_BN, - &priv->state) || - test_bit(TIMO_NUM, &board->status))) - break; - if (test_bit(TIMO_NUM, &board->status)) - break; - - spin_lock_irqsave(&board->spinlock, flags); - clear_bit(COMMAND_READY_BN, &priv->state); - write_byte(priv, buffer[*bytes_written], CDOR); - spin_unlock_irqrestore(&board->spinlock, flags); - - check_my_address_state(board, priv, buffer[*bytes_written]); - - ++(*bytes_written); - } - // wait until last command byte is written - if (wait_event_interruptible(board->wait, - test_bit(COMMAND_READY_BN, - &priv->state) || test_bit(TIMO_NUM, &board->status))) - retval = -ERESTARTSYS; - if (test_bit(TIMO_NUM, &board->status)) - retval = -ETIMEDOUT; - - return retval; -} -EXPORT_SYMBOL(tms9914_command); - -irqreturn_t tms9914_interrupt(struct gpib_board *board, struct tms9914_priv *priv) -{ - int status0, status1; - - // read interrupt status (also clears status) - status0 = read_byte(priv, ISR0); - status1 = read_byte(priv, ISR1); - return tms9914_interrupt_have_status(board, priv, status0, status1); -} -EXPORT_SYMBOL(tms9914_interrupt); - -irqreturn_t tms9914_interrupt_have_status(struct gpib_board *board, struct tms9914_priv *priv, - int status0, int status1) -{ - // record reception of END - if (status0 & HR_END) - set_bit(RECEIVED_END_BN, &priv->state); - // get incoming data in PIO mode - if ((status0 & HR_BI)) - set_bit(READ_READY_BN, &priv->state); - if ((status0 & HR_BO)) { - if (read_byte(priv, ADSR) & HR_ATN) - set_bit(COMMAND_READY_BN, &priv->state); - else - set_bit(WRITE_READY_BN, &priv->state); - } - - if (status0 & HR_SPAS) { - priv->spoll_status &= ~request_service_bit; - write_byte(priv, priv->spoll_status, SPMR); - // FIXME: set SPOLL status bit - } - // record service request in status - if (status1 & HR_SRQ) - set_bit(SRQI_NUM, &board->status); - // have been addressed (with secondary addressing disabled) - if (status1 & HR_MA) - // clear dac holdoff - write_byte(priv, AUX_VAL, AUXCR); - // unrecognized command received - if (status1 & HR_UNC) { - unsigned short command_byte = read_byte(priv, CPTR) & gpib_command_mask; - - switch (command_byte) { - case PP_CONFIG: - priv->ppoll_configure_state = 1; - /* - * AUX_PTS generates another UNC interrupt on the next command byte - * if it is in the secondary address group (such as PPE and PPD). - */ - write_byte(priv, AUX_PTS, AUXCR); - write_byte(priv, AUX_VAL, AUXCR); - break; - case PPU: - tms9914_parallel_poll_configure(board, priv, command_byte); - write_byte(priv, AUX_VAL, AUXCR); - break; - default: - if (is_PPE(command_byte) || is_PPD(command_byte)) { - if (priv->ppoll_configure_state) { - tms9914_parallel_poll_configure(board, priv, command_byte); - write_byte(priv, AUX_VAL, AUXCR); - } else {// bad parallel poll configure byte - // clear dac holdoff - write_byte(priv, AUX_INVAL, AUXCR); - } - } else { - // clear dac holdoff - write_byte(priv, AUX_INVAL, AUXCR); - } - break; - } - - if (in_primary_command_group(command_byte) && command_byte != PP_CONFIG) - priv->ppoll_configure_state = 0; - } - - if (status1 & HR_ERR) { - dev_dbg(board->gpib_dev, "gpib bus error\n"); - set_bit(BUS_ERROR_BN, &priv->state); - } - - if (status1 & HR_IFC) { - push_gpib_event(board, EVENT_IFC); - clear_bit(CIC_NUM, &board->status); - } - - if (status1 & HR_GET) { - push_gpib_event(board, EVENT_DEV_TRG); - // clear dac holdoff - write_byte(priv, AUX_VAL, AUXCR); - } - - if (status1 & HR_DCAS) { - push_gpib_event(board, EVENT_DEV_CLR); - // clear dac holdoff - write_byte(priv, AUX_VAL, AUXCR); - set_bit(DEV_CLEAR_BN, &priv->state); - } - - // check for being addressed with secondary addressing - if (status1 & HR_APT) { - if (board->sad < 0) - dev_err(board->gpib_dev, "bug, APT interrupt without secondary addressing?\n"); - if ((read_byte(priv, CPTR) & gpib_command_mask) == MSA(board->sad)) - write_byte(priv, AUX_VAL, AUXCR); - else - write_byte(priv, AUX_INVAL, AUXCR); - } - - if ((status0 & priv->imr0_bits) || (status1 & priv->imr1_bits)) { - dev_dbg(board->gpib_dev, "isr0 0x%x, imr0 0x%x, isr1 0x%x, imr1 0x%x\n", - status0, priv->imr0_bits, status1, priv->imr1_bits); - update_status_nolock(board, priv); - wake_up_interruptible(&board->wait); - } - return IRQ_HANDLED; -} -EXPORT_SYMBOL(tms9914_interrupt_have_status); - -void tms9914_board_reset(struct tms9914_priv *priv) -{ - /* chip reset */ - write_byte(priv, AUX_CHIP_RESET | AUX_CS, AUXCR); - - /* disable all interrupts */ - priv->imr0_bits = 0; - write_byte(priv, priv->imr0_bits, IMR0); - priv->imr1_bits = 0; - write_byte(priv, priv->imr1_bits, IMR1); - write_byte(priv, AUX_DAI | AUX_CS, AUXCR); - - /* clear registers by reading */ - read_byte(priv, CPTR); - read_byte(priv, ISR0); - read_byte(priv, ISR1); - - write_byte(priv, 0, SPMR); - - /* parallel poll unconfigure */ - write_byte(priv, 0, PPR); - /* request for data holdoff */ - tms9914_set_holdoff_mode(priv, TMS9914_HOLDOFF_ALL); -} -EXPORT_SYMBOL_GPL(tms9914_board_reset); - -void tms9914_online(struct gpib_board *board, struct tms9914_priv *priv) -{ - /* set GPIB address */ - tms9914_primary_address(board, priv, board->pad); - tms9914_secondary_address(board, priv, board->sad, board->sad >= 0); - - /* enable tms9914 interrupts */ - priv->imr0_bits |= HR_MACIE | HR_RLCIE | HR_ENDIE | HR_BOIE | HR_BIIE | - HR_SPASIE; - priv->imr1_bits |= HR_MAIE | HR_SRQIE | HR_UNCIE | HR_ERRIE | HR_IFCIE | - HR_GETIE | HR_DCASIE; - write_byte(priv, priv->imr0_bits, IMR0); - write_byte(priv, priv->imr1_bits, IMR1); - write_byte(priv, AUX_DAI, AUXCR); - - /* turn off reset state */ - write_byte(priv, AUX_CHIP_RESET, AUXCR); -} -EXPORT_SYMBOL_GPL(tms9914_online); - -#ifdef CONFIG_HAS_IOPORT -// wrapper for inb -u8 tms9914_ioport_read_byte(struct tms9914_priv *priv, unsigned int register_num) -{ - return inb(priv->iobase + register_num * priv->offset); -} -EXPORT_SYMBOL_GPL(tms9914_ioport_read_byte); - -// wrapper for outb -void tms9914_ioport_write_byte(struct tms9914_priv *priv, u8 data, unsigned int register_num) -{ - outb(data, priv->iobase + register_num * priv->offset); - if (register_num == AUXCR) - udelay(1); -} -EXPORT_SYMBOL_GPL(tms9914_ioport_write_byte); -#endif - -// wrapper for readb -u8 tms9914_iomem_read_byte(struct tms9914_priv *priv, unsigned int register_num) -{ - return readb(priv->mmiobase + register_num * priv->offset); -} -EXPORT_SYMBOL_GPL(tms9914_iomem_read_byte); - -// wrapper for writeb -void tms9914_iomem_write_byte(struct tms9914_priv *priv, u8 data, unsigned int register_num) -{ - writeb(data, priv->mmiobase + register_num * priv->offset); - if (register_num == AUXCR) - udelay(1); -} -EXPORT_SYMBOL_GPL(tms9914_iomem_write_byte); - -static int __init tms9914_init_module(void) -{ - return 0; -} - -static void __exit tms9914_exit_module(void) -{ -} - -module_init(tms9914_init_module); -module_exit(tms9914_exit_module); - diff --git a/drivers/staging/gpib/tnt4882/Makefile b/drivers/staging/gpib/tnt4882/Makefile deleted file mode 100644 index fa1687ad0d1b..000000000000 --- a/drivers/staging/gpib/tnt4882/Makefile +++ /dev/null @@ -1,6 +0,0 @@ -obj-$(CONFIG_GPIB_NI_PCI_ISA) += tnt4882.o - -tnt4882-objs := tnt4882_gpib.o mite.o - - - diff --git a/drivers/staging/gpib/tnt4882/mite.c b/drivers/staging/gpib/tnt4882/mite.c deleted file mode 100644 index 847b96f411bd..000000000000 --- a/drivers/staging/gpib/tnt4882/mite.c +++ /dev/null @@ -1,133 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only - -/* - * Hardware driver for NI Mite PCI interface chip, - * adapted from COMEDI - * - * Copyright (C) 1997-8 David A. Schleef - * Copyright (C) 2002 Frank Mori Hess - * - * The PCI-MIO E series driver was originally written by - * Tomasz Motylewski <...>, and ported to comedi by ds. - * - * References for specifications: - * - * 321747b.pdf Register Level Programmer Manual (obsolete) - * 321747c.pdf Register Level Programmer Manual (new) - * DAQ-STC reference manual - * - * Other possibly relevant info: - * - * 320517c.pdf User manual (obsolete) - * 320517f.pdf User manual (new) - * 320889a.pdf delete - * 320906c.pdf maximum signal ratings - * 321066a.pdf about 16x - * 321791a.pdf discontinuation of at-mio-16e-10 rev. c - * 321808a.pdf about at-mio-16e-10 rev P - * 321837a.pdf discontinuation of at-mio-16de-10 rev d - * 321838a.pdf about at-mio-16de-10 rev N - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "mite.h" - -#define PCI_MITE_SIZE 4096 -#define PCI_DAQ_SIZE 4096 - -struct mite_struct *mite_devices; - -#define TOP_OF_PAGE(x) ((x) | (~(PAGE_MASK))) - -void mite_init(void) -{ - struct pci_dev *pcidev; - struct mite_struct *mite; - - for (pcidev = pci_get_device(PCI_VENDOR_ID_NATINST, PCI_ANY_ID, NULL); - pcidev; - pcidev = pci_get_device(PCI_VENDOR_ID_NATINST, PCI_ANY_ID, pcidev)) { - mite = kzalloc(sizeof(*mite), GFP_KERNEL); - if (!mite) - return; - - mite->pcidev = pcidev; - pci_dev_get(mite->pcidev); - mite->next = mite_devices; - mite_devices = mite; - } -} - -int mite_setup(struct mite_struct *mite) -{ - u32 addr; - - if (pci_enable_device(mite->pcidev)) { - pr_err("mite: error enabling mite.\n"); - return -EIO; - } - pci_set_master(mite->pcidev); - if (pci_request_regions(mite->pcidev, "mite")) { - pr_err("mite: failed to request mite io regions.\n"); - return -EIO; - } - addr = pci_resource_start(mite->pcidev, 0); - mite->mite_phys_addr = addr; - mite->mite_io_addr = ioremap(addr, pci_resource_len(mite->pcidev, 0)); - if (!mite->mite_io_addr) { - pr_err("mite: failed to remap mite io memory address.\n"); - return -ENOMEM; - } - addr = pci_resource_start(mite->pcidev, 1); - mite->daq_phys_addr = addr; - mite->daq_io_addr = ioremap(mite->daq_phys_addr, pci_resource_len(mite->pcidev, 1)); - if (!mite->daq_io_addr) { - pr_err("mite: failed to remap daq io memory address.\n"); - return -ENOMEM; - } - writel(mite->daq_phys_addr | WENAB, mite->mite_io_addr + MITE_IODWBSR); - mite->used = 1; - return 0; -} - -void mite_cleanup(void) -{ - struct mite_struct *mite, *next; - - for (mite = mite_devices; mite; mite = next) { - next = mite->next; - if (mite->pcidev) - pci_dev_put(mite->pcidev); - kfree(mite); - } -} - -void mite_unsetup(struct mite_struct *mite) -{ - if (!mite) - return; - if (mite->mite_io_addr) { - iounmap(mite->mite_io_addr); - mite->mite_io_addr = NULL; - } - if (mite->daq_io_addr) { - iounmap(mite->daq_io_addr); - mite->daq_io_addr = NULL; - } - if (mite->mite_phys_addr) { - pci_release_regions(mite->pcidev); - pci_disable_device(mite->pcidev); - mite->mite_phys_addr = 0; - } - mite->used = 0; -} diff --git a/drivers/staging/gpib/tnt4882/mite.h b/drivers/staging/gpib/tnt4882/mite.h deleted file mode 100644 index a1fdba9672a0..000000000000 --- a/drivers/staging/gpib/tnt4882/mite.h +++ /dev/null @@ -1,234 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ - -/* - * Hardware driver for NI Mite PCI interface chip - * - * Copyright (C) 1999 David A. Schleef - */ - -#ifndef _MITE_H_ -#define _MITE_H_ - -#include - -#define PCI_VENDOR_ID_NATINST 0x1093 - -//#define DEBUG_MITE - -#ifdef DEBUG_MITE -#define MDPRINTK(format, args...) pr_debug(format, ## args) -#else -#define MDPRINTK(args...) -#endif - -#define MITE_RING_SIZE 3000 -struct mite_dma_chain { - u32 count; - u32 addr; - u32 next; -}; - -struct mite_struct { - struct mite_struct *next; - int used; - - struct pci_dev *pcidev; - unsigned long mite_phys_addr; - void __iomem *mite_io_addr; - unsigned long daq_phys_addr; - void __iomem *daq_io_addr; - - int DMA_CheckNearEnd; - - struct mite_dma_chain ring[MITE_RING_SIZE]; -}; - -extern struct mite_struct *mite_devices; - -extern inline unsigned int mite_irq(struct mite_struct *mite) -{ - return mite->pcidev->irq; -}; - -extern inline unsigned int mite_device_id(struct mite_struct *mite) -{ - return mite->pcidev->device; -}; - -void mite_init(void); -void mite_cleanup(void); -int mite_setup(struct mite_struct *mite); -void mite_unsetup(struct mite_struct *mite); -void mite_list_devices(void); - -#define CHAN_OFFSET(x) (0x100 * (x)) - -/* DMA base for chan 0 is 0x500, chan 1 is 0x600 */ - -#define MITE_CHOR 0x500 -#define CHOR_DMARESET BIT(31) -#define CHOR_SET_SEND_TC BIT(11) -#define CHOR_CLR_SEND_TC BIT(10) -#define CHOR_SET_LPAUSE BIT(9) -#define CHOR_CLR_LPAUSE BIT(8) -#define CHOR_CLRDONE BIT(7) -#define CHOR_CLRRB BIT(6) -#define CHOR_CLRLC BIT(5) -#define CHOR_FRESET BIT(4) -#define CHOR_ABORT BIT(3) -#define CHOR_STOP BIT(2) -#define CHOR_CONT BIT(1) -#define CHOR_START BIT(0) -#define CHOR_PON (CHOR_CLR_SEND_TC | CHOR_CLR_LPAUSE) - -#define MITE_CHCR 0x504 -#define CHCR_SET_DMA_IE BIT(31) -#define CHCR_CLR_DMA_IE BIT(30) -#define CHCR_SET_LINKP_IE BIT(29) -#define CHCR_CLR_LINKP_IE BIT(28) -#define CHCR_SET_SAR_IE BIT(27) -#define CHCR_CLR_SAR_IE BIT(26) -#define CHCR_SET_DONE_IE BIT(25) -#define CHCR_CLR_DONE_IE BIT(24) -#define CHCR_SET_MRDY_IE BIT(23) -#define CHCR_CLR_MRDY_IE BIT(22) -#define CHCR_SET_DRDY_IE BIT(21) -#define CHCR_CLR_DRDY_IE BIT(20) -#define CHCR_SET_LC_IE BIT(19) -#define CHCR_CLR_LC_IE BIT(18) -#define CHCR_SET_CONT_RB_IE BIT(17) -#define CHCR_CLR_CONT_RB_IE BIT(16) -#define CHCR_FIFODIS BIT(15) -#define CHCR_FIFO_ON 0 -#define CHCR_BURSTEN BIT(14) -#define CHCR_NO_BURSTEN 0 -#define CHCR_NFTP(x) ((x) << 11) -#define CHCR_NFTP0 CHCR_NFTP(0) -#define CHCR_NFTP1 CHCR_NFTP(1) -#define CHCR_NFTP2 CHCR_NFTP(2) -#define CHCR_NFTP4 CHCR_NFTP(3) -#define CHCR_NFTP8 CHCR_NFTP(4) -#define CHCR_NFTP16 CHCR_NFTP(5) -#define CHCR_NETP(x) ((x) << 11) -#define CHCR_NETP0 CHCR_NETP(0) -#define CHCR_NETP1 CHCR_NETP(1) -#define CHCR_NETP2 CHCR_NETP(2) -#define CHCR_NETP4 CHCR_NETP(3) -#define CHCR_NETP8 CHCR_NETP(4) -#define CHCR_CHEND1 BIT(5) -#define CHCR_CHEND0 BIT(4) -#define CHCR_DIR BIT(3) -#define CHCR_DEV_TO_MEM CHCR_DIR -#define CHCR_MEM_TO_DEV 0 -#define CHCR_NORMAL ((0) << 0) -#define CHCR_CONTINUE ((1) << 0) -#define CHCR_RINGBUFF ((2) << 0) -#define CHCR_LINKSHORT ((4) << 0) -#define CHCR_LINKLONG ((5) << 0) -#define CHCRPON (CHCR_CLR_DMA_IE | CHCR_CLR_LINKP_IE | CHCR_CLR_SAR_IE | \ - CHCR_CLR_DONE_IE | CHCR_CLR_MRDY_IE | CHCR_CLR_DRDY_IE | \ - CHCR_CLR_LC_IE | CHCR_CLR_CONT_IE) - -#define MITE_TCR 0x508 - -/* CR bits */ -#define CR_RL(x) ((x) << 21) -#define CR_RL0 CR_RL(0) -#define CR_RL1 CR_RL(1) -#define CR_RL2 CR_RL(2) -#define CR_RL4 CR_RL(3) -#define CR_RL8 CR_RL(4) -#define CR_RL16 CR_RL(5) -#define CR_RL32 CR_RL(6) -#define CR_RL64 CR_RL(7) -#define CR_RD(x) ((x) << 19) -#define CR_RD0 CR_RD(0) -#define CR_RD32 CR_RD(1) -#define CR_RD512 CR_RD(2) -#define CR_RD8192 CR_RD(3) -#define CR_REQS(x) ((x) << 16) -#define CR_REQSDRQ0 CR_REQS(4) -#define CR_REQSDRQ1 CR_REQS(5) -#define CR_REQSDRQ2 CR_REQS(6) -#define CR_REQSDRQ3 CR_REQS(7) -#define CR_ASEQX(x) ((x) << 10) -#define CR_ASEQX0 CR_ASEQX(0) -#define CR_ASEQDONT CR_ASEQX0 -#define CR_ASEQXP1 CR_ASEQX(1) -#define CR_ASEQUP CR_ASEQXP1 -#define CR_ASEQXP2 CR_ASEQX(2) -#define CR_ASEQDOWN CR_ASEQXP2 -#define CR_ASEQXP4 CR_ASEQX(3) -#define CR_ASEQXP8 CR_ASEQX(4) -#define CR_ASEQXP16 CR_ASEQX(5) -#define CR_ASEQXP32 CR_ASEQX(6) -#define CR_ASEQXP64 CR_ASEQX(7) -#define CR_ASEQXM1 CR_ASEQX(9) -#define CR_ASEQXM2 CR_ASEQX(10) -#define CR_ASEQXM4 CR_ASEQX(11) -#define CR_ASEQXM8 CR_ASEQX(12) -#define CR_ASEQXM16 CR_ASEQX(13) -#define CR_ASEQXM32 CR_ASEQX(14) -#define CR_ASEQXM64 CR_ASEQX(15) -#define CR_PSIZEBYTE BIT(8) -#define CR_PSIZEHALF (2 << 8) -#define CR_PSIZEWORD (3 << 8) -#define CR_PORTCPU (0 << 6) -#define CR_PORTIO BIT(6) -#define CR_PORTVXI (2 << 6) -#define CR_PORTMXI (3 << 6) -#define CR_AMDEVICE BIT(0) - -#define CHSR_INT 0x80000000 -#define CHSR_DONE 0x02000000 -#define CHSR_LINKC 0x00080000 - -#define MITE_MCR 0x50c -#define MCRPON 0 - -#define MITE_MAR 0x510 - -#define MITE_DCR 0x514 -#define DCR_NORMAL BIT(29) -#define DCRPON 0 - -#define MITE_DAR 0x518 - -#define MITE_LKCR 0x51c - -#define MITE_LKAR 0x520 -#define MITE_LLKAR 0x524 -#define MITE_BAR 0x528 -#define MITE_BCR 0x52c -#define MITE_SAR 0x530 -#define MITE_WSCR 0x534 -#define MITE_WSER 0x538 -#define MITE_CHSR 0x53c -#define MITE_FCR 0x540 - -#define MITE_FIFO 0x80 -#define MITE_FIFOEND 0xff - -#define MITE_AMRAM 0x00 -#define MITE_AMDEVICE 0x01 -#define MITE_AMHOST_A32_SINGLE 0x09 -#define MITE_AMHOST_A24_SINGLE 0x39 -#define MITE_AMHOST_A16_SINGLE 0x29 -#define MITE_AMHOST_A32_BLOCK 0x0b -#define MITE_AMHOST_A32D64_BLOCK 0x08 -#define MITE_AMHOST_A24_BLOCK 0x3b - -enum mite_registers { - MITE_IODWBSR = 0xc0, // IO Device Window Base Size Register - MITE_CSIGR = 0x460, // chip signature - MITE_IODWBSR_1 = 0xc4, // IO Device Window Base Size Register 1 (used by 6602 boards) - MITE_IODWCR_1 = 0xf4 -}; - -enum MITE_IODWBSR_bits { - WENAB = 0x80, // window enable - WENAB_6602 = 0x8c // window enable for 6602 boards -}; - -#endif - diff --git a/drivers/staging/gpib/tnt4882/tnt4882_gpib.c b/drivers/staging/gpib/tnt4882/tnt4882_gpib.c deleted file mode 100644 index c03a976b7380..000000000000 --- a/drivers/staging/gpib/tnt4882/tnt4882_gpib.c +++ /dev/null @@ -1,1838 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 - -/*************************************************************************** - * National Instruments boards using tnt4882 or compatible chips (at-gpib, etc). - * copyright : (C) 2001, 2002 by Frank Mori Hess - ***************************************************************************/ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -#define dev_fmt pr_fmt -#define DRV_NAME KBUILD_MODNAME - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "nec7210.h" -#include "gpibP.h" -#include "mite.h" -#include "tnt4882_registers.h" - -static const int ISAPNP_VENDOR_ID_NI = ISAPNP_VENDOR('N', 'I', 'C'); -static const int ISAPNP_ID_NI_ATGPIB_TNT = 0xc601; -enum { - PCI_DEVICE_ID_NI_GPIB = 0xc801, - PCI_DEVICE_ID_NI_GPIB_PLUS = 0xc811, - PCI_DEVICE_ID_NI_GPIB_PLUS2 = 0x71ad, - PCI_DEVICE_ID_NI_PXIGPIB = 0xc821, - PCI_DEVICE_ID_NI_PMCGPIB = 0xc831, - PCI_DEVICE_ID_NI_PCIEGPIB = 0x70cf, - PCI_DEVICE_ID_NI_PCIE2GPIB = 0x710e, -// Measurement Computing PCI-488 same design as PCI-GPIB with TNT5004 - PCI_DEVICE_ID_MC_PCI488 = 0x7259, - PCI_DEVICE_ID_CEC_NI_GPIB = 0x7258 -}; - -// struct which defines private_data for tnt4882 devices -struct tnt4882_priv { - struct nec7210_priv nec7210_priv; - struct mite_struct *mite; - struct pnp_dev *pnp_dev; - unsigned int irq; - unsigned short imr0_bits; - unsigned short imr3_bits; - unsigned short auxg_bits; // bits written to auxiliary register G -}; - -static irqreturn_t tnt4882_internal_interrupt(struct gpib_board *board); - -// register offset for nec7210 compatible registers -static const int atgpib_reg_offset = 2; - -// number of ioports used -static const int atgpib_iosize = 32; - -/* paged io */ -static inline unsigned int tnt_paged_readb(struct tnt4882_priv *priv, unsigned long offset) -{ - iowrite8(AUX_PAGEIN, priv->nec7210_priv.mmiobase + AUXMR * priv->nec7210_priv.offset); - udelay(1); - return ioread8(priv->nec7210_priv.mmiobase + offset); -} - -static inline void tnt_paged_writeb(struct tnt4882_priv *priv, unsigned int value, - unsigned long offset) -{ - iowrite8(AUX_PAGEIN, priv->nec7210_priv.mmiobase + AUXMR * priv->nec7210_priv.offset); - udelay(1); - iowrite8(value, priv->nec7210_priv.mmiobase + offset); -} - -/* readb/writeb wrappers */ -static inline unsigned short tnt_readb(struct tnt4882_priv *priv, unsigned long offset) -{ - void __iomem *address = priv->nec7210_priv.mmiobase + offset; - unsigned long flags; - unsigned short retval; - spinlock_t *register_lock = &priv->nec7210_priv.register_page_lock; - - spin_lock_irqsave(register_lock, flags); - switch (offset) { - case CSR: - case SASR: - case ISR0: - case BSR: - switch (priv->nec7210_priv.type) { - case TNT4882: - case TNT5004: - retval = ioread8(address); - break; - case NAT4882: - retval = tnt_paged_readb(priv, offset - tnt_pagein_offset); - break; - case NEC7210: - retval = 0; - break; - default: - retval = 0; - break; - } - break; - default: - retval = ioread8(address); - break; - } - spin_unlock_irqrestore(register_lock, flags); - return retval; -} - -static inline void tnt_writeb(struct tnt4882_priv *priv, unsigned short value, unsigned long offset) -{ - void __iomem *address = priv->nec7210_priv.mmiobase + offset; - unsigned long flags; - spinlock_t *register_lock = &priv->nec7210_priv.register_page_lock; - - spin_lock_irqsave(register_lock, flags); - switch (offset) { - case KEYREG: - case IMR0: - case BCR: - switch (priv->nec7210_priv.type) { - case TNT4882: - case TNT5004: - iowrite8(value, address); - break; - case NAT4882: - tnt_paged_writeb(priv, value, offset - tnt_pagein_offset); - break; - case NEC7210: - break; - default: - break; - } - break; - default: - iowrite8(value, address); - break; - } - spin_unlock_irqrestore(register_lock, flags); -} - -MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("GPIB driver for National Instruments boards using tnt4882 or compatible chips"); - -static int tnt4882_line_status(const struct gpib_board *board) -{ - int status = VALID_ALL; - int bcsr_bits; - struct tnt4882_priv *tnt_priv; - - tnt_priv = board->private_data; - - bcsr_bits = tnt_readb(tnt_priv, BSR); - - if (bcsr_bits & BCSR_REN_BIT) - status |= BUS_REN; - if (bcsr_bits & BCSR_IFC_BIT) - status |= BUS_IFC; - if (bcsr_bits & BCSR_SRQ_BIT) - status |= BUS_SRQ; - if (bcsr_bits & BCSR_EOI_BIT) - status |= BUS_EOI; - if (bcsr_bits & BCSR_NRFD_BIT) - status |= BUS_NRFD; - if (bcsr_bits & BCSR_NDAC_BIT) - status |= BUS_NDAC; - if (bcsr_bits & BCSR_DAV_BIT) - status |= BUS_DAV; - if (bcsr_bits & BCSR_ATN_BIT) - status |= BUS_ATN; - - return status; -} - -static int tnt4882_t1_delay(struct gpib_board *board, unsigned int nano_sec) -{ - struct tnt4882_priv *tnt_priv = board->private_data; - struct nec7210_priv *nec_priv = &tnt_priv->nec7210_priv; - unsigned int retval; - - retval = nec7210_t1_delay(board, nec_priv, nano_sec); - if (nec_priv->type == NEC7210) - return retval; - - if (nano_sec <= 350) { - tnt_writeb(tnt_priv, MSTD, KEYREG); - retval = 350; - } else { - tnt_writeb(tnt_priv, 0, KEYREG); - } - if (nano_sec > 500 && nano_sec <= 1100) { - write_byte(nec_priv, AUXRI | USTD, AUXMR); - retval = 1100; - } else { - write_byte(nec_priv, AUXRI, AUXMR); - } - return retval; -} - -static int fifo_word_available(struct tnt4882_priv *tnt_priv) -{ - int status2; - int retval; - - status2 = tnt_readb(tnt_priv, STS2); - retval = (status2 & AEFN) && (status2 & BEFN); - - return retval; -} - -static int fifo_byte_available(struct tnt4882_priv *tnt_priv) -{ - int status2; - int retval; - - status2 = tnt_readb(tnt_priv, STS2); - retval = (status2 & AEFN) || (status2 & BEFN); - - return retval; -} - -static int fifo_xfer_done(struct tnt4882_priv *tnt_priv) -{ - int status1; - int retval; - - status1 = tnt_readb(tnt_priv, STS1); - retval = status1 & (S_DONE | S_HALT); - - return retval; -} - -static int drain_fifo_words(struct tnt4882_priv *tnt_priv, u8 *buffer, int num_bytes) -{ - int count = 0; - struct nec7210_priv *nec_priv = &tnt_priv->nec7210_priv; - - while (fifo_word_available(tnt_priv) && count + 2 <= num_bytes) { - short word; - - word = ioread16(nec_priv->mmiobase + FIFOB); - buffer[count++] = word & 0xff; - buffer[count++] = (word >> 8) & 0xff; - } - return count; -} - -static void tnt4882_release_holdoff(struct gpib_board *board, struct tnt4882_priv *tnt_priv) -{ - struct nec7210_priv *nec_priv = &tnt_priv->nec7210_priv; - unsigned short sasr_bits; - - sasr_bits = tnt_readb(tnt_priv, SASR); - - /* - * tnt4882 not in one-chip mode won't always release holdoff unless we - * are in the right mode when release handshake command is given - */ - if (sasr_bits & AEHS_BIT) /* holding off due to holdoff on end mode*/ { - nec7210_set_handshake_mode(board, nec_priv, HR_HLDE); - write_byte(nec_priv, AUX_FH, AUXMR); - } else if (sasr_bits & ANHS1_BIT) { /* held off due to holdoff on all data mode*/ - nec7210_set_handshake_mode(board, nec_priv, HR_HLDA); - write_byte(nec_priv, AUX_FH, AUXMR); - nec7210_set_handshake_mode(board, nec_priv, HR_HLDE); - } else { /* held off due to holdoff immediately command */ - nec7210_set_handshake_mode(board, nec_priv, HR_HLDE); - write_byte(nec_priv, AUX_FH, AUXMR); - } -} - -static int tnt4882_accel_read(struct gpib_board *board, u8 *buffer, size_t length, int *end, - size_t *bytes_read) -{ - size_t count = 0; - ssize_t retval = 0; - struct tnt4882_priv *tnt_priv = board->private_data; - struct nec7210_priv *nec_priv = &tnt_priv->nec7210_priv; - unsigned int bits; - s32 hw_count; - unsigned long flags; - - *bytes_read = 0; - // FIXME: really, DEV_CLEAR_BN should happen elsewhere to prevent race - clear_bit(DEV_CLEAR_BN, &nec_priv->state); - clear_bit(ADR_CHANGE_BN, &nec_priv->state); - - nec7210_set_reg_bits(nec_priv, IMR1, HR_ENDIE, HR_ENDIE); - if (nec_priv->type != TNT4882 && nec_priv->type != TNT5004) - nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAI, HR_DMAI); - else - nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAI, 0); - tnt_writeb(tnt_priv, nec_priv->auxa_bits | HR_HLDA, CCR); - bits = TNT_B_16BIT | TNT_IN | TNT_CCEN; - tnt_writeb(tnt_priv, bits, CFG); - tnt_writeb(tnt_priv, RESET_FIFO, CMDR); - udelay(1); - // load 2's complement of count into hardware counters - hw_count = -length; - tnt_writeb(tnt_priv, hw_count & 0xff, CNT0); - tnt_writeb(tnt_priv, (hw_count >> 8) & 0xff, CNT1); - tnt_writeb(tnt_priv, (hw_count >> 16) & 0xff, CNT2); - tnt_writeb(tnt_priv, (hw_count >> 24) & 0xff, CNT3); - - tnt4882_release_holdoff(board, tnt_priv); - - tnt_writeb(tnt_priv, GO, CMDR); - udelay(1); - - spin_lock_irqsave(&board->spinlock, flags); - tnt_priv->imr3_bits |= HR_DONE | HR_NEF; - tnt_writeb(tnt_priv, tnt_priv->imr3_bits, IMR3); - spin_unlock_irqrestore(&board->spinlock, flags); - - while (count + 2 <= length && - test_bit(RECEIVED_END_BN, &nec_priv->state) == 0 && - fifo_xfer_done(tnt_priv) == 0) { - // wait until a word is ready - if (wait_event_interruptible(board->wait, - fifo_word_available(tnt_priv) || - fifo_xfer_done(tnt_priv) || - test_bit(RECEIVED_END_BN, &nec_priv->state) || - test_bit(DEV_CLEAR_BN, &nec_priv->state) || - test_bit(ADR_CHANGE_BN, &nec_priv->state) || - test_bit(TIMO_NUM, &board->status))) { - retval = -ERESTARTSYS; - break; - } - if (test_bit(TIMO_NUM, &board->status)) { - retval = -ETIMEDOUT; - break; - } - if (test_bit(DEV_CLEAR_BN, &nec_priv->state)) { - retval = -EINTR; - break; - } - if (test_bit(ADR_CHANGE_BN, &nec_priv->state)) { - retval = -EINTR; - break; - } - - spin_lock_irqsave(&board->spinlock, flags); - count += drain_fifo_words(tnt_priv, &buffer[count], length - count); - tnt_priv->imr3_bits |= HR_NEF; - tnt_writeb(tnt_priv, tnt_priv->imr3_bits, IMR3); - spin_unlock_irqrestore(&board->spinlock, flags); - - if (need_resched()) - schedule(); - } - // wait for last byte - if (count < length) { - spin_lock_irqsave(&board->spinlock, flags); - tnt_priv->imr3_bits |= HR_DONE | HR_NEF; - tnt_writeb(tnt_priv, tnt_priv->imr3_bits, IMR3); - spin_unlock_irqrestore(&board->spinlock, flags); - - if (wait_event_interruptible(board->wait, - fifo_xfer_done(tnt_priv) || - test_bit(RECEIVED_END_BN, &nec_priv->state) || - test_bit(DEV_CLEAR_BN, &nec_priv->state) || - test_bit(ADR_CHANGE_BN, &nec_priv->state) || - test_bit(TIMO_NUM, &board->status))) { - retval = -ERESTARTSYS; - } - if (test_bit(TIMO_NUM, &board->status)) - retval = -ETIMEDOUT; - if (test_bit(DEV_CLEAR_BN, &nec_priv->state)) - retval = -EINTR; - if (test_bit(ADR_CHANGE_BN, &nec_priv->state)) - retval = -EINTR; - count += drain_fifo_words(tnt_priv, &buffer[count], length - count); - if (fifo_byte_available(tnt_priv) && count < length) - buffer[count++] = tnt_readb(tnt_priv, FIFOB); - } - if (count < length) - tnt_writeb(tnt_priv, STOP, CMDR); - udelay(1); - - nec7210_set_reg_bits(nec_priv, IMR1, HR_ENDIE, 0); - nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAI, 0); - /* - * force handling of any pending interrupts (seems to be needed - * to keep interrupts from getting hosed, plus for syncing - * with RECEIVED_END below) - */ - tnt4882_internal_interrupt(board); - /* RECEIVED_END should be in sync now */ - if (test_and_clear_bit(RECEIVED_END_BN, &nec_priv->state)) - *end = 1; - if (retval < 0) { - // force immediate holdoff - write_byte(nec_priv, AUX_HLDI, AUXMR); - - set_bit(RFD_HOLDOFF_BN, &nec_priv->state); - } - *bytes_read = count; - - return retval; -} - -static int fifo_space_available(struct tnt4882_priv *tnt_priv) -{ - int status2; - int retval; - - status2 = tnt_readb(tnt_priv, STS2); - retval = (status2 & AFFN) && (status2 & BFFN); - - return retval; -} - -static unsigned int tnt_transfer_count(struct tnt4882_priv *tnt_priv) -{ - unsigned int count = 0; - - count |= tnt_readb(tnt_priv, CNT0) & 0xff; - count |= (tnt_readb(tnt_priv, CNT1) << 8) & 0xff00; - count |= (tnt_readb(tnt_priv, CNT2) << 16) & 0xff0000; - count |= (tnt_readb(tnt_priv, CNT3) << 24) & 0xff000000; - // return two's complement - return -count; -}; - -static int write_wait(struct gpib_board *board, struct tnt4882_priv *tnt_priv, - int wait_for_done, int send_commands) -{ - struct nec7210_priv *nec_priv = &tnt_priv->nec7210_priv; - - if (wait_event_interruptible(board->wait, - (!wait_for_done && fifo_space_available(tnt_priv)) || - fifo_xfer_done(tnt_priv) || - test_bit(BUS_ERROR_BN, &nec_priv->state) || - test_bit(DEV_CLEAR_BN, &nec_priv->state) || - test_bit(TIMO_NUM, &board->status))) - return -ERESTARTSYS; - - if (test_bit(TIMO_NUM, &board->status)) - return -ETIMEDOUT; - if (test_and_clear_bit(BUS_ERROR_BN, &nec_priv->state)) - return (send_commands) ? -ENOTCONN : -ECOMM; - if (test_bit(DEV_CLEAR_BN, &nec_priv->state)) - return -EINTR; - return 0; -} - -static int generic_write(struct gpib_board *board, u8 *buffer, size_t length, - int send_eoi, int send_commands, size_t *bytes_written) -{ - size_t count = 0; - ssize_t retval = 0; - struct tnt4882_priv *tnt_priv = board->private_data; - struct nec7210_priv *nec_priv = &tnt_priv->nec7210_priv; - unsigned int bits; - s32 hw_count; - unsigned long flags; - - *bytes_written = 0; - // FIXME: really, DEV_CLEAR_BN should happen elsewhere to prevent race - clear_bit(DEV_CLEAR_BN, &nec_priv->state); - - nec7210_set_reg_bits(nec_priv, IMR1, HR_ERRIE, HR_ERRIE); - - if (nec_priv->type != TNT4882 && nec_priv->type != TNT5004) - nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAO, HR_DMAO); - else - nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAO, 0); - - tnt_writeb(tnt_priv, RESET_FIFO, CMDR); - udelay(1); - - bits = TNT_B_16BIT; - if (send_eoi) { - bits |= TNT_CCEN; - if (nec_priv->type != TNT4882 && nec_priv->type != TNT5004) - tnt_writeb(tnt_priv, AUX_SEOI, CCR); - } - if (send_commands) - bits |= TNT_COMMAND; - tnt_writeb(tnt_priv, bits, CFG); - - // load 2's complement of count into hardware counters - hw_count = -length; - tnt_writeb(tnt_priv, hw_count & 0xff, CNT0); - tnt_writeb(tnt_priv, (hw_count >> 8) & 0xff, CNT1); - tnt_writeb(tnt_priv, (hw_count >> 16) & 0xff, CNT2); - tnt_writeb(tnt_priv, (hw_count >> 24) & 0xff, CNT3); - - tnt_writeb(tnt_priv, GO, CMDR); - udelay(1); - - spin_lock_irqsave(&board->spinlock, flags); - tnt_priv->imr3_bits |= HR_DONE; - tnt_writeb(tnt_priv, tnt_priv->imr3_bits, IMR3); - spin_unlock_irqrestore(&board->spinlock, flags); - - while (count < length) { - // wait until byte is ready to be sent - retval = write_wait(board, tnt_priv, 0, send_commands); - if (retval < 0) - break; - if (fifo_xfer_done(tnt_priv)) - break; - spin_lock_irqsave(&board->spinlock, flags); - while (fifo_space_available(tnt_priv) && count < length) { - u16 word; - - word = buffer[count++] & 0xff; - if (count < length) - word |= (buffer[count++] << 8) & 0xff00; - iowrite16(word, nec_priv->mmiobase + FIFOB); - } -// avoid unnecessary HR_NFF interrupts -// tnt_priv->imr3_bits |= HR_NFF; -// tnt_writeb(tnt_priv, tnt_priv->imr3_bits, IMR3); - spin_unlock_irqrestore(&board->spinlock, flags); - - if (need_resched()) - schedule(); - } - // wait last byte has been sent - if (retval == 0) - retval = write_wait(board, tnt_priv, 1, send_commands); - - tnt_writeb(tnt_priv, STOP, CMDR); - udelay(1); - - nec7210_set_reg_bits(nec_priv, IMR1, HR_ERR, 0x0); - nec7210_set_reg_bits(nec_priv, IMR2, HR_DMAO, 0x0); - /* - * force handling of any interrupts that happened - * while they were masked (this appears to be needed) - */ - tnt4882_internal_interrupt(board); - *bytes_written = length - tnt_transfer_count(tnt_priv); - return retval; -} - -static int tnt4882_accel_write(struct gpib_board *board, u8 *buffer, - size_t length, int send_eoi, size_t *bytes_written) -{ - return generic_write(board, buffer, length, send_eoi, 0, bytes_written); -} - -static int tnt4882_command(struct gpib_board *board, u8 *buffer, size_t length, - size_t *bytes_written) -{ - return generic_write(board, buffer, length, 0, 1, bytes_written); -} - -static irqreturn_t tnt4882_internal_interrupt(struct gpib_board *board) -{ - struct tnt4882_priv *priv = board->private_data; - int isr0_bits, isr3_bits, imr3_bits; - unsigned long flags; - - spin_lock_irqsave(&board->spinlock, flags); - - nec7210_interrupt(board, &priv->nec7210_priv); - - isr0_bits = tnt_readb(priv, ISR0); - isr3_bits = tnt_readb(priv, ISR3); - imr3_bits = priv->imr3_bits; - - if (isr0_bits & TNT_IFCI_BIT) - push_gpib_event(board, EVENT_IFC); - // XXX don't need this wakeup, one below should do? -// wake_up_interruptible(&board->wait); - - if (isr3_bits & HR_NFF) - priv->imr3_bits &= ~HR_NFF; - if (isr3_bits & HR_NEF) - priv->imr3_bits &= ~HR_NEF; - if (isr3_bits & HR_DONE) - priv->imr3_bits &= ~HR_DONE; - if (isr3_bits & (HR_INTR | HR_TLCI)) { - dev_dbg(board->gpib_dev, "minor %i isr0 0x%x imr0 0x%x isr3 0x%x imr3 0x%x\n", - board->minor, isr0_bits, priv->imr0_bits, isr3_bits, imr3_bits); - tnt_writeb(priv, priv->imr3_bits, IMR3); - wake_up_interruptible(&board->wait); - } - spin_unlock_irqrestore(&board->spinlock, flags); - return IRQ_HANDLED; -} - -static irqreturn_t tnt4882_interrupt(int irq, void *arg) -{ - return tnt4882_internal_interrupt(arg); -} - -// wrappers for interface functions -static int tnt4882_read(struct gpib_board *board, u8 *buffer, size_t length, int *end, - size_t *bytes_read) -{ - struct tnt4882_priv *priv = board->private_data; - struct nec7210_priv *nec_priv = &priv->nec7210_priv; - int retval; - int dummy; - - retval = nec7210_read(board, &priv->nec7210_priv, buffer, length, end, bytes_read); - - if (retval < 0) { // force immediate holdoff - write_byte(nec_priv, AUX_HLDI, AUXMR); - - set_bit(RFD_HOLDOFF_BN, &nec_priv->state); - - nec7210_read_data_in(board, nec_priv, &dummy); - } - return retval; -} - -static int tnt4882_write(struct gpib_board *board, u8 *buffer, size_t length, int send_eoi, - size_t *bytes_written) -{ - struct tnt4882_priv *priv = board->private_data; - - return nec7210_write(board, &priv->nec7210_priv, buffer, length, send_eoi, bytes_written); -} - -static int tnt4882_command_unaccel(struct gpib_board *board, u8 *buffer, - size_t length, size_t *bytes_written) -{ - struct tnt4882_priv *priv = board->private_data; - - return nec7210_command(board, &priv->nec7210_priv, buffer, length, bytes_written); -} - -static int tnt4882_take_control(struct gpib_board *board, int synchronous) -{ - struct tnt4882_priv *priv = board->private_data; - - return nec7210_take_control(board, &priv->nec7210_priv, synchronous); -} - -static int tnt4882_go_to_standby(struct gpib_board *board) -{ - struct tnt4882_priv *priv = board->private_data; - - return nec7210_go_to_standby(board, &priv->nec7210_priv); -} - -static int tnt4882_request_system_control(struct gpib_board *board, int request_control) -{ - struct tnt4882_priv *priv = board->private_data; - int retval; - - if (request_control) { - tnt_writeb(priv, SETSC, CMDR); - udelay(1); - } - retval = nec7210_request_system_control(board, &priv->nec7210_priv, request_control); - if (!request_control) { - tnt_writeb(priv, CLRSC, CMDR); - udelay(1); - } - return retval; -} - -static void tnt4882_interface_clear(struct gpib_board *board, int assert) -{ - struct tnt4882_priv *priv = board->private_data; - - nec7210_interface_clear(board, &priv->nec7210_priv, assert); -} - -static void tnt4882_remote_enable(struct gpib_board *board, int enable) -{ - struct tnt4882_priv *priv = board->private_data; - - nec7210_remote_enable(board, &priv->nec7210_priv, enable); -} - -static int tnt4882_enable_eos(struct gpib_board *board, u8 eos_byte, int compare_8_bits) -{ - struct tnt4882_priv *priv = board->private_data; - - return nec7210_enable_eos(board, &priv->nec7210_priv, eos_byte, compare_8_bits); -} - -static void tnt4882_disable_eos(struct gpib_board *board) -{ - struct tnt4882_priv *priv = board->private_data; - - nec7210_disable_eos(board, &priv->nec7210_priv); -} - -static unsigned int tnt4882_update_status(struct gpib_board *board, unsigned int clear_mask) -{ - unsigned long flags; - u8 line_status; - struct tnt4882_priv *priv = board->private_data; - - spin_lock_irqsave(&board->spinlock, flags); - board->status &= ~clear_mask; - nec7210_update_status_nolock(board, &priv->nec7210_priv); - /* set / clear SRQ state since it is not cleared by interrupt */ - line_status = tnt_readb(priv, BSR); - if (line_status & BCSR_SRQ_BIT) - set_bit(SRQI_NUM, &board->status); - else - clear_bit(SRQI_NUM, &board->status); - spin_unlock_irqrestore(&board->spinlock, flags); - return board->status; -} - -static int tnt4882_primary_address(struct gpib_board *board, unsigned int address) -{ - struct tnt4882_priv *priv = board->private_data; - - return nec7210_primary_address(board, &priv->nec7210_priv, address); -} - -static int tnt4882_secondary_address(struct gpib_board *board, unsigned int address, int enable) -{ - struct tnt4882_priv *priv = board->private_data; - - return nec7210_secondary_address(board, &priv->nec7210_priv, address, enable); -} - -static int tnt4882_parallel_poll(struct gpib_board *board, u8 *result) -{ - struct tnt4882_priv *tnt_priv = board->private_data; - - if (tnt_priv->nec7210_priv.type != NEC7210) { - tnt_priv->auxg_bits |= RPP2_BIT; - write_byte(&tnt_priv->nec7210_priv, tnt_priv->auxg_bits, AUXMR); - udelay(2); // FIXME use parallel poll timeout - *result = read_byte(&tnt_priv->nec7210_priv, CPTR); - tnt_priv->auxg_bits &= ~RPP2_BIT; - write_byte(&tnt_priv->nec7210_priv, tnt_priv->auxg_bits, AUXMR); - return 0; - } else { - return nec7210_parallel_poll(board, &tnt_priv->nec7210_priv, result); - } -} - -static void tnt4882_parallel_poll_configure(struct gpib_board *board, u8 config) -{ - struct tnt4882_priv *priv = board->private_data; - - if (priv->nec7210_priv.type == TNT5004) { - /* configure locally */ - write_byte(&priv->nec7210_priv, AUXRI | 0x4, AUXMR); - if (config) - /* set response + clear sense */ - write_byte(&priv->nec7210_priv, PPR | config, AUXMR); - else - /* disable ppoll */ - write_byte(&priv->nec7210_priv, PPR | 0x10, AUXMR); - } else { - nec7210_parallel_poll_configure(board, &priv->nec7210_priv, config); - } -} - -static void tnt4882_parallel_poll_response(struct gpib_board *board, int ist) -{ - struct tnt4882_priv *priv = board->private_data; - - nec7210_parallel_poll_response(board, &priv->nec7210_priv, ist); -} - -/* - * this is just used by the old nec7210 isa interfaces, the newer - * boards use tnt4882_serial_poll_response2 - */ -static void tnt4882_serial_poll_response(struct gpib_board *board, u8 status) -{ - struct tnt4882_priv *priv = board->private_data; - - nec7210_serial_poll_response(board, &priv->nec7210_priv, status); -} - -static void tnt4882_serial_poll_response2(struct gpib_board *board, u8 status, - int new_reason_for_service) -{ - struct tnt4882_priv *priv = board->private_data; - unsigned long flags; - const int MSS = status & request_service_bit; - const int reqt = MSS && new_reason_for_service; - const int reqf = MSS == 0; - - spin_lock_irqsave(&board->spinlock, flags); - if (reqt) { - priv->nec7210_priv.srq_pending = 1; - clear_bit(SPOLL_NUM, &board->status); - } else { - if (reqf) - priv->nec7210_priv.srq_pending = 0; - } - if (reqt) - /* - * It may seem like a race to issue reqt before updating - * the status byte, but it is not. The chip does not - * issue the reqt until the SPMR is written to at - * a later time. - */ - write_byte(&priv->nec7210_priv, AUX_REQT, AUXMR); - else if (reqf) - write_byte(&priv->nec7210_priv, AUX_REQF, AUXMR); - /* - * We need to always zero bit 6 of the status byte before writing it to - * the SPMR to insure we are using - * serial poll mode SP1, and not accidentally triggering mode SP3. - */ - write_byte(&priv->nec7210_priv, status & ~request_service_bit, SPMR); - spin_unlock_irqrestore(&board->spinlock, flags); -} - -static u8 tnt4882_serial_poll_status(struct gpib_board *board) -{ - struct tnt4882_priv *priv = board->private_data; - - return nec7210_serial_poll_status(board, &priv->nec7210_priv); -} - -static void tnt4882_return_to_local(struct gpib_board *board) -{ - struct tnt4882_priv *priv = board->private_data; - - nec7210_return_to_local(board, &priv->nec7210_priv); -} - -static void tnt4882_board_reset(struct tnt4882_priv *tnt_priv, struct gpib_board *board) -{ - struct nec7210_priv *nec_priv = &tnt_priv->nec7210_priv; - - tnt_priv->imr0_bits = 0; - tnt_writeb(tnt_priv, tnt_priv->imr0_bits, IMR0); - tnt_priv->imr3_bits = 0; - tnt_writeb(tnt_priv, tnt_priv->imr3_bits, IMR3); - tnt_readb(tnt_priv, IMR0); - tnt_readb(tnt_priv, IMR3); - nec7210_board_reset(nec_priv, board); -} - -static int tnt4882_allocate_private(struct gpib_board *board) -{ - struct tnt4882_priv *tnt_priv; - - board->private_data = kmalloc(sizeof(struct tnt4882_priv), GFP_KERNEL); - if (!board->private_data) - return -1; - tnt_priv = board->private_data; - memset(tnt_priv, 0, sizeof(struct tnt4882_priv)); - init_nec7210_private(&tnt_priv->nec7210_priv); - return 0; -} - -static void tnt4882_free_private(struct gpib_board *board) -{ - kfree(board->private_data); - board->private_data = NULL; -} - -static void tnt4882_init(struct tnt4882_priv *tnt_priv, const struct gpib_board *board) -{ - struct nec7210_priv *nec_priv = &tnt_priv->nec7210_priv; - - /* Turbo488 software reset */ - tnt_writeb(tnt_priv, SOFT_RESET, CMDR); - udelay(1); - - // turn off one-chip mode - tnt_writeb(tnt_priv, NODMA, HSSEL); - tnt_writeb(tnt_priv, 0, ACCWR); - // make sure we are in 7210 mode - tnt_writeb(tnt_priv, AUX_7210, AUXCR); - udelay(1); - // registers might be swapped, so write it to the swapped address too - tnt_writeb(tnt_priv, AUX_7210, SWAPPED_AUXCR); - udelay(1); - // turn on one-chip mode - if (nec_priv->type == TNT4882 || nec_priv->type == TNT5004) - tnt_writeb(tnt_priv, NODMA | TNT_ONE_CHIP_BIT, HSSEL); - else - tnt_writeb(tnt_priv, NODMA, HSSEL); - - nec7210_board_reset(nec_priv, board); - // read-clear isr0 - tnt_readb(tnt_priv, ISR0); - - // enable passing of nat4882 interrupts - tnt_priv->imr3_bits = HR_TLCI; - tnt_writeb(tnt_priv, tnt_priv->imr3_bits, IMR3); - - // enable interrupt - tnt_writeb(tnt_priv, 0x1, INTRT); - - // force immediate holdoff - write_byte(&tnt_priv->nec7210_priv, AUX_HLDI, AUXMR); - - set_bit(RFD_HOLDOFF_BN, &nec_priv->state); - - tnt_priv->auxg_bits = AUXRG | NTNL_BIT; - write_byte(&tnt_priv->nec7210_priv, tnt_priv->auxg_bits, AUXMR); - - nec7210_board_online(nec_priv, board); - // enable interface clear interrupt for event queue - tnt_priv->imr0_bits = TNT_IMR0_ALWAYS_BITS | TNT_ATNI_BIT | TNT_IFCIE_BIT; - tnt_writeb(tnt_priv, tnt_priv->imr0_bits, IMR0); -} - -static int ni_pci_attach(struct gpib_board *board, const struct gpib_board_config *config) -{ - struct tnt4882_priv *tnt_priv; - struct nec7210_priv *nec_priv; - int isr_flags = IRQF_SHARED; - int retval; - struct mite_struct *mite; - - board->status = 0; - - if (tnt4882_allocate_private(board)) - return -ENOMEM; - tnt_priv = board->private_data; - nec_priv = &tnt_priv->nec7210_priv; - nec_priv->type = TNT4882; - nec_priv->read_byte = nec7210_locking_iomem_read_byte; - nec_priv->write_byte = nec7210_locking_iomem_write_byte; - nec_priv->offset = atgpib_reg_offset; - - if (!mite_devices) - return -ENODEV; - - for (mite = mite_devices; mite; mite = mite->next) { - short found_board; - - if (mite->used) - continue; - if (config->pci_bus >= 0 && config->pci_bus != mite->pcidev->bus->number) - continue; - if (config->pci_slot >= 0 && config->pci_slot != PCI_SLOT(mite->pcidev->devfn)) - continue; - switch (mite_device_id(mite)) { - case PCI_DEVICE_ID_NI_GPIB: - case PCI_DEVICE_ID_NI_GPIB_PLUS: - case PCI_DEVICE_ID_NI_GPIB_PLUS2: - case PCI_DEVICE_ID_NI_PXIGPIB: - case PCI_DEVICE_ID_NI_PMCGPIB: - case PCI_DEVICE_ID_NI_PCIEGPIB: - case PCI_DEVICE_ID_NI_PCIE2GPIB: -// support for Measurement Computing PCI-488 - case PCI_DEVICE_ID_MC_PCI488: - case PCI_DEVICE_ID_CEC_NI_GPIB: - found_board = 1; - break; - default: - found_board = 0; - break; - } - if (found_board) - break; - } - if (!mite) - return -ENODEV; - - tnt_priv->mite = mite; - retval = mite_setup(tnt_priv->mite); - if (retval < 0) - return retval; - - nec_priv->mmiobase = tnt_priv->mite->daq_io_addr; - - // get irq - retval = request_irq(mite_irq(tnt_priv->mite), tnt4882_interrupt, isr_flags, "ni-pci-gpib", - board); - if (retval) { - dev_err(board->gpib_dev, "failed to obtain pci irq %d\n", mite_irq(tnt_priv->mite)); - return retval; - } - tnt_priv->irq = mite_irq(tnt_priv->mite); - - // TNT5004 detection - switch (tnt_readb(tnt_priv, CSR) & 0xf0) { - case 0x30: - nec_priv->type = TNT4882; - break; - case 0x40: - nec_priv->type = TNT5004; - break; - } - tnt4882_init(tnt_priv, board); - - return 0; -} - -static void ni_pci_detach(struct gpib_board *board) -{ - struct tnt4882_priv *tnt_priv = board->private_data; - struct nec7210_priv *nec_priv; - - if (tnt_priv) { - nec_priv = &tnt_priv->nec7210_priv; - - if (nec_priv->mmiobase) - tnt4882_board_reset(tnt_priv, board); - if (tnt_priv->irq) - free_irq(tnt_priv->irq, board); - if (tnt_priv->mite) - mite_unsetup(tnt_priv->mite); - } - tnt4882_free_private(board); -} - -static int ni_isapnp_find(struct pnp_dev **dev) -{ - *dev = pnp_find_dev(NULL, ISAPNP_VENDOR_ID_NI, - ISAPNP_FUNCTION(ISAPNP_ID_NI_ATGPIB_TNT), NULL); - if (!*dev || !(*dev)->card) - return -ENODEV; - if (pnp_device_attach(*dev) < 0) - return -EBUSY; - if (pnp_activate_dev(*dev) < 0) { - pnp_device_detach(*dev); - return -EAGAIN; - } - if (!pnp_port_valid(*dev, 0) || !pnp_irq_valid(*dev, 0)) { - pnp_device_detach(*dev); - return -EINVAL; - } - return 0; -} - -static int ni_isa_attach_common(struct gpib_board *board, const struct gpib_board_config *config, - enum nec7210_chipset chipset) -{ - struct tnt4882_priv *tnt_priv; - struct nec7210_priv *nec_priv; - int isr_flags = 0; - u32 iobase; - int irq; - int retval; - - board->status = 0; - - if (tnt4882_allocate_private(board)) - return -ENOMEM; - tnt_priv = board->private_data; - nec_priv = &tnt_priv->nec7210_priv; - nec_priv->type = chipset; - nec_priv->read_byte = nec7210_locking_ioport_read_byte; - nec_priv->write_byte = nec7210_locking_ioport_write_byte; - nec_priv->offset = atgpib_reg_offset; - - // look for plug-n-play board - if (config->ibbase == 0) { - struct pnp_dev *dev; - - retval = ni_isapnp_find(&dev); - if (retval < 0) - return retval; - tnt_priv->pnp_dev = dev; - iobase = pnp_port_start(dev, 0); - irq = pnp_irq(dev, 0); - } else { - iobase = config->ibbase; - irq = config->ibirq; - } - // allocate ioports - if (!request_region(iobase, atgpib_iosize, "atgpib")) - return -EBUSY; - - nec_priv->mmiobase = ioport_map(iobase, atgpib_iosize); - if (!nec_priv->mmiobase) - return -EBUSY; - - // get irq - retval = request_irq(irq, tnt4882_interrupt, isr_flags, "atgpib", board); - if (retval) { - dev_err(board->gpib_dev, "failed to request ISA irq %d\n", irq); - return retval; - } - tnt_priv->irq = irq; - - tnt4882_init(tnt_priv, board); - - return 0; -} - -static int ni_tnt_isa_attach(struct gpib_board *board, const struct gpib_board_config *config) -{ - return ni_isa_attach_common(board, config, TNT4882); -} - -static int ni_nat4882_isa_attach(struct gpib_board *board, const struct gpib_board_config *config) -{ - return ni_isa_attach_common(board, config, NAT4882); -} - -static int ni_nec_isa_attach(struct gpib_board *board, const struct gpib_board_config *config) -{ - return ni_isa_attach_common(board, config, NEC7210); -} - -static void ni_isa_detach(struct gpib_board *board) -{ - struct tnt4882_priv *tnt_priv = board->private_data; - struct nec7210_priv *nec_priv; - - if (tnt_priv) { - nec_priv = &tnt_priv->nec7210_priv; - if (nec_priv->iobase) - tnt4882_board_reset(tnt_priv, board); - if (tnt_priv->irq) - free_irq(tnt_priv->irq, board); - if (nec_priv->mmiobase) - ioport_unmap(nec_priv->mmiobase); - if (nec_priv->iobase) - release_region(nec_priv->iobase, atgpib_iosize); - if (tnt_priv->pnp_dev) - pnp_device_detach(tnt_priv->pnp_dev); - } - tnt4882_free_private(board); -} - -static int tnt4882_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) -{ - return 0; -} - -static struct gpib_interface ni_pci_interface = { - .name = "ni_pci", - .attach = ni_pci_attach, - .detach = ni_pci_detach, - .read = tnt4882_accel_read, - .write = tnt4882_accel_write, - .command = tnt4882_command, - .take_control = tnt4882_take_control, - .go_to_standby = tnt4882_go_to_standby, - .request_system_control = tnt4882_request_system_control, - .interface_clear = tnt4882_interface_clear, - .remote_enable = tnt4882_remote_enable, - .enable_eos = tnt4882_enable_eos, - .disable_eos = tnt4882_disable_eos, - .parallel_poll = tnt4882_parallel_poll, - .parallel_poll_configure = tnt4882_parallel_poll_configure, - .parallel_poll_response = tnt4882_parallel_poll_response, - .local_parallel_poll_mode = NULL, // XXX - .line_status = tnt4882_line_status, - .update_status = tnt4882_update_status, - .primary_address = tnt4882_primary_address, - .secondary_address = tnt4882_secondary_address, - .serial_poll_response2 = tnt4882_serial_poll_response2, - .serial_poll_status = tnt4882_serial_poll_status, - .t1_delay = tnt4882_t1_delay, - .return_to_local = tnt4882_return_to_local, -}; - -static struct gpib_interface ni_pci_accel_interface = { - .name = "ni_pci_accel", - .attach = ni_pci_attach, - .detach = ni_pci_detach, - .read = tnt4882_accel_read, - .write = tnt4882_accel_write, - .command = tnt4882_command, - .take_control = tnt4882_take_control, - .go_to_standby = tnt4882_go_to_standby, - .request_system_control = tnt4882_request_system_control, - .interface_clear = tnt4882_interface_clear, - .remote_enable = tnt4882_remote_enable, - .enable_eos = tnt4882_enable_eos, - .disable_eos = tnt4882_disable_eos, - .parallel_poll = tnt4882_parallel_poll, - .parallel_poll_configure = tnt4882_parallel_poll_configure, - .parallel_poll_response = tnt4882_parallel_poll_response, - .local_parallel_poll_mode = NULL, // XXX - .line_status = tnt4882_line_status, - .update_status = tnt4882_update_status, - .primary_address = tnt4882_primary_address, - .secondary_address = tnt4882_secondary_address, - .serial_poll_response2 = tnt4882_serial_poll_response2, - .serial_poll_status = tnt4882_serial_poll_status, - .t1_delay = tnt4882_t1_delay, - .return_to_local = tnt4882_return_to_local, -}; - -static struct gpib_interface ni_isa_interface = { - .name = "ni_isa", - .attach = ni_tnt_isa_attach, - .detach = ni_isa_detach, - .read = tnt4882_accel_read, - .write = tnt4882_accel_write, - .command = tnt4882_command, - .take_control = tnt4882_take_control, - .go_to_standby = tnt4882_go_to_standby, - .request_system_control = tnt4882_request_system_control, - .interface_clear = tnt4882_interface_clear, - .remote_enable = tnt4882_remote_enable, - .enable_eos = tnt4882_enable_eos, - .disable_eos = tnt4882_disable_eos, - .parallel_poll = tnt4882_parallel_poll, - .parallel_poll_configure = tnt4882_parallel_poll_configure, - .parallel_poll_response = tnt4882_parallel_poll_response, - .local_parallel_poll_mode = NULL, // XXX - .line_status = tnt4882_line_status, - .update_status = tnt4882_update_status, - .primary_address = tnt4882_primary_address, - .secondary_address = tnt4882_secondary_address, - .serial_poll_response2 = tnt4882_serial_poll_response2, - .serial_poll_status = tnt4882_serial_poll_status, - .t1_delay = tnt4882_t1_delay, - .return_to_local = tnt4882_return_to_local, -}; - -static struct gpib_interface ni_nat4882_isa_interface = { - .name = "ni_nat4882_isa", - .attach = ni_nat4882_isa_attach, - .detach = ni_isa_detach, - .read = tnt4882_read, - .write = tnt4882_write, - .command = tnt4882_command_unaccel, - .take_control = tnt4882_take_control, - .go_to_standby = tnt4882_go_to_standby, - .request_system_control = tnt4882_request_system_control, - .interface_clear = tnt4882_interface_clear, - .remote_enable = tnt4882_remote_enable, - .enable_eos = tnt4882_enable_eos, - .disable_eos = tnt4882_disable_eos, - .parallel_poll = tnt4882_parallel_poll, - .parallel_poll_configure = tnt4882_parallel_poll_configure, - .parallel_poll_response = tnt4882_parallel_poll_response, - .local_parallel_poll_mode = NULL, // XXX - .line_status = tnt4882_line_status, - .update_status = tnt4882_update_status, - .primary_address = tnt4882_primary_address, - .secondary_address = tnt4882_secondary_address, - .serial_poll_response2 = tnt4882_serial_poll_response2, - .serial_poll_status = tnt4882_serial_poll_status, - .t1_delay = tnt4882_t1_delay, - .return_to_local = tnt4882_return_to_local, -}; - -static struct gpib_interface ni_nec_isa_interface = { - .name = "ni_nec_isa", - .attach = ni_nec_isa_attach, - .detach = ni_isa_detach, - .read = tnt4882_read, - .write = tnt4882_write, - .command = tnt4882_command_unaccel, - .take_control = tnt4882_take_control, - .go_to_standby = tnt4882_go_to_standby, - .request_system_control = tnt4882_request_system_control, - .interface_clear = tnt4882_interface_clear, - .remote_enable = tnt4882_remote_enable, - .enable_eos = tnt4882_enable_eos, - .disable_eos = tnt4882_disable_eos, - .parallel_poll = tnt4882_parallel_poll, - .parallel_poll_configure = tnt4882_parallel_poll_configure, - .parallel_poll_response = tnt4882_parallel_poll_response, - .local_parallel_poll_mode = NULL, // XXX - .line_status = NULL, - .update_status = tnt4882_update_status, - .primary_address = tnt4882_primary_address, - .secondary_address = tnt4882_secondary_address, - .serial_poll_response = tnt4882_serial_poll_response, - .serial_poll_status = tnt4882_serial_poll_status, - .t1_delay = tnt4882_t1_delay, - .return_to_local = tnt4882_return_to_local, -}; - -static struct gpib_interface ni_isa_accel_interface = { - .name = "ni_isa_accel", - .attach = ni_tnt_isa_attach, - .detach = ni_isa_detach, - .read = tnt4882_accel_read, - .write = tnt4882_accel_write, - .command = tnt4882_command, - .take_control = tnt4882_take_control, - .go_to_standby = tnt4882_go_to_standby, - .request_system_control = tnt4882_request_system_control, - .interface_clear = tnt4882_interface_clear, - .remote_enable = tnt4882_remote_enable, - .enable_eos = tnt4882_enable_eos, - .disable_eos = tnt4882_disable_eos, - .parallel_poll = tnt4882_parallel_poll, - .parallel_poll_configure = tnt4882_parallel_poll_configure, - .parallel_poll_response = tnt4882_parallel_poll_response, - .local_parallel_poll_mode = NULL, // XXX - .line_status = tnt4882_line_status, - .update_status = tnt4882_update_status, - .primary_address = tnt4882_primary_address, - .secondary_address = tnt4882_secondary_address, - .serial_poll_response2 = tnt4882_serial_poll_response2, - .serial_poll_status = tnt4882_serial_poll_status, - .t1_delay = tnt4882_t1_delay, - .return_to_local = tnt4882_return_to_local, -}; - -static struct gpib_interface ni_nat4882_isa_accel_interface = { - .name = "ni_nat4882_isa_accel", - .attach = ni_nat4882_isa_attach, - .detach = ni_isa_detach, - .read = tnt4882_accel_read, - .write = tnt4882_accel_write, - .command = tnt4882_command_unaccel, - .take_control = tnt4882_take_control, - .go_to_standby = tnt4882_go_to_standby, - .request_system_control = tnt4882_request_system_control, - .interface_clear = tnt4882_interface_clear, - .remote_enable = tnt4882_remote_enable, - .enable_eos = tnt4882_enable_eos, - .disable_eos = tnt4882_disable_eos, - .parallel_poll = tnt4882_parallel_poll, - .parallel_poll_configure = tnt4882_parallel_poll_configure, - .parallel_poll_response = tnt4882_parallel_poll_response, - .local_parallel_poll_mode = NULL, // XXX - .line_status = tnt4882_line_status, - .update_status = tnt4882_update_status, - .primary_address = tnt4882_primary_address, - .secondary_address = tnt4882_secondary_address, - .serial_poll_response2 = tnt4882_serial_poll_response2, - .serial_poll_status = tnt4882_serial_poll_status, - .t1_delay = tnt4882_t1_delay, - .return_to_local = tnt4882_return_to_local, -}; - -static struct gpib_interface ni_nec_isa_accel_interface = { - .name = "ni_nec_isa_accel", - .attach = ni_nec_isa_attach, - .detach = ni_isa_detach, - .read = tnt4882_accel_read, - .write = tnt4882_accel_write, - .command = tnt4882_command_unaccel, - .take_control = tnt4882_take_control, - .go_to_standby = tnt4882_go_to_standby, - .request_system_control = tnt4882_request_system_control, - .interface_clear = tnt4882_interface_clear, - .remote_enable = tnt4882_remote_enable, - .enable_eos = tnt4882_enable_eos, - .disable_eos = tnt4882_disable_eos, - .parallel_poll = tnt4882_parallel_poll, - .parallel_poll_configure = tnt4882_parallel_poll_configure, - .parallel_poll_response = tnt4882_parallel_poll_response, - .local_parallel_poll_mode = NULL, // XXX - .line_status = NULL, - .update_status = tnt4882_update_status, - .primary_address = tnt4882_primary_address, - .secondary_address = tnt4882_secondary_address, - .serial_poll_response = tnt4882_serial_poll_response, - .serial_poll_status = tnt4882_serial_poll_status, - .t1_delay = tnt4882_t1_delay, - .return_to_local = tnt4882_return_to_local, -}; - -static const struct pci_device_id tnt4882_pci_table[] = { - {PCI_DEVICE(PCI_VENDOR_ID_NATINST, PCI_DEVICE_ID_NI_GPIB)}, - {PCI_DEVICE(PCI_VENDOR_ID_NATINST, PCI_DEVICE_ID_NI_GPIB_PLUS)}, - {PCI_DEVICE(PCI_VENDOR_ID_NATINST, PCI_DEVICE_ID_NI_GPIB_PLUS2)}, - {PCI_DEVICE(PCI_VENDOR_ID_NATINST, PCI_DEVICE_ID_NI_PXIGPIB)}, - {PCI_DEVICE(PCI_VENDOR_ID_NATINST, PCI_DEVICE_ID_NI_PMCGPIB)}, - {PCI_DEVICE(PCI_VENDOR_ID_NATINST, PCI_DEVICE_ID_NI_PCIEGPIB)}, - {PCI_DEVICE(PCI_VENDOR_ID_NATINST, PCI_DEVICE_ID_NI_PCIE2GPIB)}, - // support for Measurement Computing PCI-488 - {PCI_DEVICE(PCI_VENDOR_ID_NATINST, PCI_DEVICE_ID_MC_PCI488)}, - {PCI_DEVICE(PCI_VENDOR_ID_NATINST, PCI_DEVICE_ID_CEC_NI_GPIB)}, - { 0 } -}; -MODULE_DEVICE_TABLE(pci, tnt4882_pci_table); - -static struct pci_driver tnt4882_pci_driver = { - .name = DRV_NAME, - .id_table = tnt4882_pci_table, - .probe = &tnt4882_pci_probe -}; - -#if 0 -/* unused, will be needed when the driver is turned into a pnp_driver */ -static const struct pnp_device_id tnt4882_pnp_table[] = { - {.id = "NICC601"}, - {.id = ""} -}; -MODULE_DEVICE_TABLE(pnp, tnt4882_pnp_table); -#endif - -#ifdef CONFIG_GPIB_PCMCIA -static struct gpib_interface ni_pcmcia_interface; -static struct gpib_interface ni_pcmcia_accel_interface; -static int __init init_ni_gpib_cs(void); -static void __exit exit_ni_gpib_cs(void); -#endif - -static int __init tnt4882_init_module(void) -{ - int result; - - result = pci_register_driver(&tnt4882_pci_driver); - if (result) { - pr_err("pci_register_driver failed: error = %d\n", result); - return result; - } - - result = gpib_register_driver(&ni_isa_interface, THIS_MODULE); - if (result) { - pr_err("gpib_register_driver failed: error = %d\n", result); - goto err_isa; - } - - result = gpib_register_driver(&ni_isa_accel_interface, THIS_MODULE); - if (result) { - pr_err("gpib_register_driver failed: error = %d\n", result); - goto err_isa_accel; - } - - result = gpib_register_driver(&ni_nat4882_isa_interface, THIS_MODULE); - if (result) { - pr_err("gpib_register_driver failed: error = %d\n", result); - goto err_nat4882_isa; - } - - result = gpib_register_driver(&ni_nat4882_isa_accel_interface, THIS_MODULE); - if (result) { - pr_err("gpib_register_driver failed: error = %d\n", result); - goto err_nat4882_isa_accel; - } - - result = gpib_register_driver(&ni_nec_isa_interface, THIS_MODULE); - if (result) { - pr_err("gpib_register_driver failed: error = %d\n", result); - goto err_nec_isa; - } - - result = gpib_register_driver(&ni_nec_isa_accel_interface, THIS_MODULE); - if (result) { - pr_err("gpib_register_driver failed: error = %d\n", result); - goto err_nec_isa_accel; - } - - result = gpib_register_driver(&ni_pci_interface, THIS_MODULE); - if (result) { - pr_err("gpib_register_driver failed: error = %d\n", result); - goto err_pci; - } - - result = gpib_register_driver(&ni_pci_accel_interface, THIS_MODULE); - if (result) { - pr_err("gpib_register_driver failed: error = %d\n", result); - goto err_pci_accel; - } - -#ifdef CONFIG_GPIB_PCMCIA - result = gpib_register_driver(&ni_pcmcia_interface, THIS_MODULE); - if (result) { - pr_err("gpib_register_driver failed: error = %d\n", result); - goto err_pcmcia; - } - - result = gpib_register_driver(&ni_pcmcia_accel_interface, THIS_MODULE); - if (result) { - pr_err("gpib_register_driver failed: error = %d\n", result); - goto err_pcmcia_accel; - } - - result = init_ni_gpib_cs(); - if (result) { - pr_err("pcmcia_register_driver failed: error = %d\n", result); - goto err_pcmcia_driver; - } -#endif - - mite_init(); - - return 0; - -#ifdef CONFIG_GPIB_PCMCIA -err_pcmcia_driver: - gpib_unregister_driver(&ni_pcmcia_accel_interface); -err_pcmcia_accel: - gpib_unregister_driver(&ni_pcmcia_interface); -err_pcmcia: -#endif - gpib_unregister_driver(&ni_pci_accel_interface); -err_pci_accel: - gpib_unregister_driver(&ni_pci_interface); -err_pci: - gpib_unregister_driver(&ni_nec_isa_accel_interface); -err_nec_isa_accel: - gpib_unregister_driver(&ni_nec_isa_interface); -err_nec_isa: - gpib_unregister_driver(&ni_nat4882_isa_accel_interface); -err_nat4882_isa_accel: - gpib_unregister_driver(&ni_nat4882_isa_interface); -err_nat4882_isa: - gpib_unregister_driver(&ni_isa_accel_interface); -err_isa_accel: - gpib_unregister_driver(&ni_isa_interface); -err_isa: - pci_unregister_driver(&tnt4882_pci_driver); - - return result; -} - -static void __exit tnt4882_exit_module(void) -{ - gpib_unregister_driver(&ni_isa_interface); - gpib_unregister_driver(&ni_isa_accel_interface); - gpib_unregister_driver(&ni_nat4882_isa_interface); - gpib_unregister_driver(&ni_nat4882_isa_accel_interface); - gpib_unregister_driver(&ni_nec_isa_interface); - gpib_unregister_driver(&ni_nec_isa_accel_interface); - gpib_unregister_driver(&ni_pci_interface); - gpib_unregister_driver(&ni_pci_accel_interface); -#ifdef CONFIG_GPIB_PCMCIA - gpib_unregister_driver(&ni_pcmcia_interface); - gpib_unregister_driver(&ni_pcmcia_accel_interface); - exit_ni_gpib_cs(); -#endif - - mite_cleanup(); - - pci_unregister_driver(&tnt4882_pci_driver); -} - -#ifdef CONFIG_GPIB_PCMCIA - -#include -#include -#include -#include -#include - -#include -#include -#include - -static int ni_gpib_config(struct pcmcia_device *link); -static void ni_gpib_release(struct pcmcia_device *link); -static void ni_pcmcia_detach(struct gpib_board *board); - -/* - * A linked list of "instances" of the dummy device. Each actual - * PCMCIA card corresponds to one device instance, and is described - * by one dev_link_t structure (defined in ds.h). - * - * You may not want to use a linked list for this -- for example, the - * memory card driver uses an array of dev_link_t pointers, where minor - * device numbers are used to derive the corresponding array index. - * - * I think this dev_list is obsolete but the pointer is needed to keep - * the module instance for the ni_pcmcia_attach function. - */ - -static struct pcmcia_device *curr_dev; - -struct local_info_t { - struct pcmcia_device *p_dev; - struct gpib_board *dev; - int stop; - struct bus_operations *bus; -}; - -/* - * ni_gpib_probe() creates an "instance" of the driver, allocating - * local data structures for one device. The device is registered - * with Card Services. - */ - -static int ni_gpib_probe(struct pcmcia_device *link) -{ - struct local_info_t *info; - //struct struct gpib_board *dev; - - /* Allocate space for private device-specific data */ - info = kzalloc(sizeof(*info), GFP_KERNEL); - if (!info) - return -ENOMEM; - - info->p_dev = link; - link->priv = info; - - /* - * General socket configuration defaults can go here. In this - * client, we assume very little, and rely on the CIS for almost - * everything. In most clients, many details (i.e., number, sizes, - * and attributes of IO windows) are fixed by the nature of the - * device, and can be hard-wired here. - */ - link->config_flags = CONF_ENABLE_IRQ | CONF_AUTO_SET_IO; - - /* Register with Card Services */ - curr_dev = link; - return ni_gpib_config(link); -} - -/* - * This deletes a driver "instance". The device is de-registered - * with Card Services. If it has been released, all local data - * structures are freed. Otherwise, the structures will be freed - * when the device is released. - */ -static void ni_gpib_remove(struct pcmcia_device *link) -{ - struct local_info_t *info = link->priv; - //struct struct gpib_board *dev = info->dev; - - if (info->dev) - ni_pcmcia_detach(info->dev); - ni_gpib_release(link); - - //free_netdev(dev); - kfree(info); -} - -static int ni_gpib_config_iteration(struct pcmcia_device *link, void *priv_data) -{ - int retval; - - retval = pcmcia_request_io(link); - if (retval != 0) - return retval; - - return 0; -} - -/* - * ni_gpib_config() is scheduled to run after a CARD_INSERTION event - * is received, to configure the PCMCIA socket, and to make the - * device available to the system. - */ -static int ni_gpib_config(struct pcmcia_device *link) -{ - //struct local_info_t *info = link->priv; - //struct gpib_board *dev = info->dev; - int last_ret; - - last_ret = pcmcia_loop_config(link, &ni_gpib_config_iteration, NULL); - if (last_ret) { - dev_warn(&link->dev, "no configuration found\n"); - ni_gpib_release(link); - return last_ret; - } - - last_ret = pcmcia_enable_device(link); - if (last_ret) { - ni_gpib_release(link); - return last_ret; - } - return 0; -} /* ni_gpib_config */ - -/* - * After a card is removed, ni_gpib_release() will unregister the - * device, and release the PCMCIA configuration. If the device is - * still open, this will be postponed until it is closed. - */ -static void ni_gpib_release(struct pcmcia_device *link) -{ - pcmcia_disable_device(link); -} /* ni_gpib_release */ - -static int ni_gpib_suspend(struct pcmcia_device *link) -{ - //struct local_info_t *info = link->priv; - //struct struct gpib_board *dev = info->dev; - - if (link->open) - dev_warn(&link->dev, "Device still open\n"); - //netif_device_detach(dev); - - return 0; -} - -static int ni_gpib_resume(struct pcmcia_device *link) -{ - //struct local_info_t *info = link->priv; - //struct struct gpib_board *dev = info->dev; - - /*if (link->open) { - * ni_gpib_probe(dev); / really? - * //netif_device_attach(dev); - *} - */ - return ni_gpib_config(link); -} - -static struct pcmcia_device_id ni_pcmcia_ids[] = { - PCMCIA_DEVICE_MANF_CARD(0x010b, 0x4882), - PCMCIA_DEVICE_MANF_CARD(0x010b, 0x0c71), // NI PCMCIA-GPIB+ - PCMCIA_DEVICE_NULL -}; - -MODULE_DEVICE_TABLE(pcmcia, ni_pcmcia_ids); - -static struct pcmcia_driver ni_gpib_cs_driver = { - .name = "ni_gpib_cs", - .owner = THIS_MODULE, - .drv = { .name = "ni_gpib_cs", }, - .id_table = ni_pcmcia_ids, - .probe = ni_gpib_probe, - .remove = ni_gpib_remove, - .suspend = ni_gpib_suspend, - .resume = ni_gpib_resume, -}; - -static int __init init_ni_gpib_cs(void) -{ - return pcmcia_register_driver(&ni_gpib_cs_driver); -} - -static void __exit exit_ni_gpib_cs(void) -{ - pcmcia_unregister_driver(&ni_gpib_cs_driver); -} - -static const int pcmcia_gpib_iosize = 32; - -static int ni_pcmcia_attach(struct gpib_board *board, const struct gpib_board_config *config) -{ - struct local_info_t *info; - struct tnt4882_priv *tnt_priv; - struct nec7210_priv *nec_priv; - int isr_flags = IRQF_SHARED; - int retval; - - if (!curr_dev) - return -ENODEV; - - info = curr_dev->priv; - info->dev = board; - - board->status = 0; - - if (tnt4882_allocate_private(board)) - return -ENOMEM; - - tnt_priv = board->private_data; - nec_priv = &tnt_priv->nec7210_priv; - nec_priv->type = TNT4882; - nec_priv->read_byte = nec7210_locking_ioport_read_byte; - nec_priv->write_byte = nec7210_locking_ioport_write_byte; - nec_priv->offset = atgpib_reg_offset; - - if (!request_region(curr_dev->resource[0]->start, resource_size(curr_dev->resource[0]), - DRV_NAME)) - return -ENOMEM; - - nec_priv->mmiobase = ioport_map(curr_dev->resource[0]->start, - resource_size(curr_dev->resource[0])); - if (!nec_priv->mmiobase) - return -ENOMEM; - - // get irq - retval = request_irq(curr_dev->irq, tnt4882_interrupt, isr_flags, DRV_NAME, board); - if (retval) { - dev_err(board->gpib_dev, "failed to obtain PCMCIA irq %d\n", curr_dev->irq); - return retval; - } - tnt_priv->irq = curr_dev->irq; - - tnt4882_init(tnt_priv, board); - - return 0; -} - -static void ni_pcmcia_detach(struct gpib_board *board) -{ - struct tnt4882_priv *tnt_priv = board->private_data; - struct nec7210_priv *nec_priv; - - if (tnt_priv) { - nec_priv = &tnt_priv->nec7210_priv; - if (tnt_priv->irq) - free_irq(tnt_priv->irq, board); - if (nec_priv->mmiobase) - ioport_unmap(nec_priv->mmiobase); - if (nec_priv->iobase) { - tnt4882_board_reset(tnt_priv, board); - release_region(nec_priv->iobase, pcmcia_gpib_iosize); - } - } - tnt4882_free_private(board); -} - -static struct gpib_interface ni_pcmcia_interface = { - .name = "ni_pcmcia", - .attach = ni_pcmcia_attach, - .detach = ni_pcmcia_detach, - .read = tnt4882_accel_read, - .write = tnt4882_accel_write, - .command = tnt4882_command, - .take_control = tnt4882_take_control, - .go_to_standby = tnt4882_go_to_standby, - .request_system_control = tnt4882_request_system_control, - .interface_clear = tnt4882_interface_clear, - .remote_enable = tnt4882_remote_enable, - .enable_eos = tnt4882_enable_eos, - .disable_eos = tnt4882_disable_eos, - .parallel_poll = tnt4882_parallel_poll, - .parallel_poll_configure = tnt4882_parallel_poll_configure, - .parallel_poll_response = tnt4882_parallel_poll_response, - .local_parallel_poll_mode = NULL, // XXX - .line_status = tnt4882_line_status, - .update_status = tnt4882_update_status, - .primary_address = tnt4882_primary_address, - .secondary_address = tnt4882_secondary_address, - .serial_poll_response = tnt4882_serial_poll_response, - .serial_poll_status = tnt4882_serial_poll_status, - .t1_delay = tnt4882_t1_delay, - .return_to_local = tnt4882_return_to_local, -}; - -static struct gpib_interface ni_pcmcia_accel_interface = { - .name = "ni_pcmcia_accel", - .attach = ni_pcmcia_attach, - .detach = ni_pcmcia_detach, - .read = tnt4882_accel_read, - .write = tnt4882_accel_write, - .command = tnt4882_command, - .take_control = tnt4882_take_control, - .go_to_standby = tnt4882_go_to_standby, - .request_system_control = tnt4882_request_system_control, - .interface_clear = tnt4882_interface_clear, - .remote_enable = tnt4882_remote_enable, - .enable_eos = tnt4882_enable_eos, - .disable_eos = tnt4882_disable_eos, - .parallel_poll = tnt4882_parallel_poll, - .parallel_poll_configure = tnt4882_parallel_poll_configure, - .parallel_poll_response = tnt4882_parallel_poll_response, - .local_parallel_poll_mode = NULL, // XXX - .line_status = tnt4882_line_status, - .update_status = tnt4882_update_status, - .primary_address = tnt4882_primary_address, - .secondary_address = tnt4882_secondary_address, - .serial_poll_response = tnt4882_serial_poll_response, - .serial_poll_status = tnt4882_serial_poll_status, - .t1_delay = tnt4882_t1_delay, - .return_to_local = tnt4882_return_to_local, -}; - -#endif // CONFIG_GPIB_PCMCIA - -module_init(tnt4882_init_module); -module_exit(tnt4882_exit_module); diff --git a/drivers/staging/gpib/uapi/gpib.h b/drivers/staging/gpib/uapi/gpib.h deleted file mode 100644 index 2a7f5eeb9777..000000000000 --- a/drivers/staging/gpib/uapi/gpib.h +++ /dev/null @@ -1,104 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ - -/*************************************************************************** - * copyright : (C) 2002 by Frank Mori Hess - ***************************************************************************/ - -#ifndef _GPIB_H -#define _GPIB_H - -#define GPIB_MAX_NUM_BOARDS 16 -#define GPIB_MAX_NUM_DESCRIPTORS 0x1000 - -enum ibsta_bit_numbers { - DCAS_NUM = 0, - DTAS_NUM = 1, - LACS_NUM = 2, - TACS_NUM = 3, - ATN_NUM = 4, - CIC_NUM = 5, - REM_NUM = 6, - LOK_NUM = 7, - CMPL_NUM = 8, - EVENT_NUM = 9, - SPOLL_NUM = 10, - RQS_NUM = 11, - SRQI_NUM = 12, - END_NUM = 13, - TIMO_NUM = 14, - ERR_NUM = 15 -}; - -/* IBSTA status bits (returned by all functions) */ -enum ibsta_bits { - DCAS = (1 << DCAS_NUM), /* device clear state */ - DTAS = (1 << DTAS_NUM), /* device trigger state */ - LACS = (1 << LACS_NUM), /* GPIB interface is addressed as Listener */ - TACS = (1 << TACS_NUM), /* GPIB interface is addressed as Talker */ - ATN = (1 << ATN_NUM), /* Attention is asserted */ - CIC = (1 << CIC_NUM), /* GPIB interface is Controller-in-Charge */ - REM = (1 << REM_NUM), /* remote state */ - LOK = (1 << LOK_NUM), /* lockout state */ - CMPL = (1 << CMPL_NUM), /* I/O is complete */ - EVENT = (1 << EVENT_NUM), /* DCAS, DTAS, or IFC has occurred */ - SPOLL = (1 << SPOLL_NUM), /* board serial polled by busmaster */ - RQS = (1 << RQS_NUM), /* Device requesting service */ - SRQI = (1 << SRQI_NUM), /* SRQ is asserted */ - END = (1 << END_NUM), /* EOI or EOS encountered */ - TIMO = (1 << TIMO_NUM), /* Time limit on I/O or wait function exceeded */ - ERR = (1 << ERR_NUM), /* Function call terminated on error */ - - device_status_mask = ERR | TIMO | END | CMPL | RQS, - board_status_mask = ERR | TIMO | END | CMPL | SPOLL | - EVENT | LOK | REM | CIC | ATN | TACS | LACS | DTAS | DCAS | SRQI, -}; - -/* End-of-string (EOS) modes for use with ibeos */ - -enum eos_flags { - EOS_MASK = 0x1c00, - REOS = 0x0400, /* Terminate reads on EOS */ - XEOS = 0x800, /* assert EOI when EOS char is sent */ - BIN = 0x1000 /* Do 8-bit compare on EOS */ -}; - -/* GPIB Bus Control Lines bit vector */ -enum bus_control_line { - VALID_DAV = 0x01, - VALID_NDAC = 0x02, - VALID_NRFD = 0x04, - VALID_IFC = 0x08, - VALID_REN = 0x10, - VALID_SRQ = 0x20, - VALID_ATN = 0x40, - VALID_EOI = 0x80, - VALID_ALL = 0xff, - BUS_DAV = 0x0100, /* DAV line status bit */ - BUS_NDAC = 0x0200, /* NDAC line status bit */ - BUS_NRFD = 0x0400, /* NRFD line status bit */ - BUS_IFC = 0x0800, /* IFC line status bit */ - BUS_REN = 0x1000, /* REN line status bit */ - BUS_SRQ = 0x2000, /* SRQ line status bit */ - BUS_ATN = 0x4000, /* ATN line status bit */ - BUS_EOI = 0x8000 /* EOI line status bit */ -}; - -enum ppe_bits { - PPC_DISABLE = 0x10, - PPC_SENSE = 0x8, /* parallel poll sense bit */ - PPC_DIO_MASK = 0x7 -}; - -enum { - request_service_bit = 0x40, -}; - -enum gpib_events { - EVENT_NONE = 0, - EVENT_DEV_TRG = 1, - EVENT_DEV_CLR = 2, - EVENT_IFC = 3 -}; - -#endif /* _GPIB_H */ - diff --git a/drivers/staging/gpib/uapi/gpib_ioctl.h b/drivers/staging/gpib/uapi/gpib_ioctl.h deleted file mode 100644 index d544d8e4362c..000000000000 --- a/drivers/staging/gpib/uapi/gpib_ioctl.h +++ /dev/null @@ -1,167 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ - -/*************************************************************************** - * copyright : (C) 2002 by Frank Mori Hess - ***************************************************************************/ - -#ifndef _GPIB_IOCTL_H -#define _GPIB_IOCTL_H - -#include -#include - -#define GPIB_CODE 160 - -struct gpib_board_type_ioctl { - char name[100]; -}; - -/* argument for read/write/command ioctls */ -struct gpib_read_write_ioctl { - __u64 buffer_ptr; - __u32 requested_transfer_count; - __u32 completed_transfer_count; - __s32 end; /* end flag return for reads, end io suppression request for cmd*/ - __s32 handle; -}; - -struct gpib_open_dev_ioctl { - __u32 handle; - __u32 pad; - __s32 sad; - __u32 is_board; -}; - -struct gpib_close_dev_ioctl { - __u32 handle; -}; - -struct gpib_serial_poll_ioctl { - __u32 pad; - __s32 sad; - __u8 status_byte; - __u8 padding[3]; /* align to 32 bit boundary */ -}; - -struct gpib_eos_ioctl { - __s32 eos; - __s32 eos_flags; -}; - -struct gpib_wait_ioctl { - __s32 handle; - __s32 wait_mask; - __s32 clear_mask; - __s32 set_mask; - __s32 ibsta; - __s32 pad; - __s32 sad; - __u32 usec_timeout; -}; - -struct gpib_online_ioctl { - __u64 init_data_ptr; - __s32 init_data_length; - __s32 online; -}; - -struct gpib_spoll_bytes_ioctl { - __u32 num_bytes; - __u32 pad; - __s32 sad; -}; - -struct gpib_board_info_ioctl { - __u32 pad; - __s32 sad; - __s32 parallel_poll_configuration; - __s32 autopolling; - __s32 is_system_controller; - __u32 t1_delay; - unsigned ist : 1; - unsigned no_7_bit_eos : 1; - unsigned padding :30; /* align to 32 bit boundary */ -}; - -struct gpib_select_pci_ioctl { - __s32 pci_bus; - __s32 pci_slot; -}; - -struct gpib_ppoll_config_ioctl { - __u8 config; - unsigned set_ist : 1; - unsigned clear_ist : 1; - unsigned padding :22; /* align to 32 bit boundary */ -}; - -struct gpib_pad_ioctl { - __u32 handle; - __u32 pad; -}; - -struct gpib_sad_ioctl { - __u32 handle; - __s32 sad; -}; - -/* select a piece of hardware to attach by its sysfs device path */ -struct gpib_select_device_path_ioctl { - char device_path[0x1000]; -}; - -/* update status byte and request service */ -struct gpib_request_service2 { - __u8 status_byte; - __u8 padding[3]; /* align to 32 bit boundary */ - __s32 new_reason_for_service; -}; - -/* Standard functions. */ -enum gpib_ioctl { - IBRD = _IOWR(GPIB_CODE, 100, struct gpib_read_write_ioctl), - IBWRT = _IOWR(GPIB_CODE, 101, struct gpib_read_write_ioctl), - IBCMD = _IOWR(GPIB_CODE, 102, struct gpib_read_write_ioctl), - IBOPENDEV = _IOWR(GPIB_CODE, 3, struct gpib_open_dev_ioctl), - IBCLOSEDEV = _IOW(GPIB_CODE, 4, struct gpib_close_dev_ioctl), - IBWAIT = _IOWR(GPIB_CODE, 5, struct gpib_wait_ioctl), - IBRPP = _IOWR(GPIB_CODE, 6, __u8), - - IBSIC = _IOW(GPIB_CODE, 9, __u32), - IBSRE = _IOW(GPIB_CODE, 10, __s32), - IBGTS = _IO(GPIB_CODE, 11), - IBCAC = _IOW(GPIB_CODE, 12, __s32), - IBLINES = _IOR(GPIB_CODE, 14, __s16), - IBPAD = _IOW(GPIB_CODE, 15, struct gpib_pad_ioctl), - IBSAD = _IOW(GPIB_CODE, 16, struct gpib_sad_ioctl), - IBTMO = _IOW(GPIB_CODE, 17, __u32), - IBRSP = _IOWR(GPIB_CODE, 18, struct gpib_serial_poll_ioctl), - IBEOS = _IOW(GPIB_CODE, 19, struct gpib_eos_ioctl), - IBRSV = _IOW(GPIB_CODE, 20, __u8), - CFCBASE = _IOW(GPIB_CODE, 21, __u64), - CFCIRQ = _IOW(GPIB_CODE, 22, __u32), - CFCDMA = _IOW(GPIB_CODE, 23, __u32), - CFCBOARDTYPE = _IOW(GPIB_CODE, 24, struct gpib_board_type_ioctl), - - IBMUTEX = _IOW(GPIB_CODE, 26, __s32), - IBSPOLL_BYTES = _IOWR(GPIB_CODE, 27, struct gpib_spoll_bytes_ioctl), - IBPPC = _IOW(GPIB_CODE, 28, struct gpib_ppoll_config_ioctl), - IBBOARD_INFO = _IOR(GPIB_CODE, 29, struct gpib_board_info_ioctl), - - IBQUERY_BOARD_RSV = _IOR(GPIB_CODE, 31, __s32), - IBSELECT_PCI = _IOWR(GPIB_CODE, 32, struct gpib_select_pci_ioctl), - IBEVENT = _IOR(GPIB_CODE, 33, __s16), - IBRSC = _IOW(GPIB_CODE, 34, __s32), - IB_T1_DELAY = _IOW(GPIB_CODE, 35, __u32), - IBLOC = _IO(GPIB_CODE, 36), - - IBAUTOSPOLL = _IOW(GPIB_CODE, 38, __s16), - IBONL = _IOW(GPIB_CODE, 39, struct gpib_online_ioctl), - IBPP2_SET = _IOW(GPIB_CODE, 40, __s16), - IBPP2_GET = _IOR(GPIB_CODE, 41, __s16), - IBSELECT_DEVICE_PATH = _IOW(GPIB_CODE, 43, struct gpib_select_device_path_ioctl), - /* 44 was IBSELECT_SERIAL_NUMBER */ - IBRSV2 = _IOW(GPIB_CODE, 45, struct gpib_request_service2) -}; - -#endif /* _GPIB_IOCTL_H */ diff --git a/include/uapi/linux/gpib.h b/include/uapi/linux/gpib.h new file mode 100644 index 000000000000..2a7f5eeb9777 --- /dev/null +++ b/include/uapi/linux/gpib.h @@ -0,0 +1,104 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ + +/*************************************************************************** + * copyright : (C) 2002 by Frank Mori Hess + ***************************************************************************/ + +#ifndef _GPIB_H +#define _GPIB_H + +#define GPIB_MAX_NUM_BOARDS 16 +#define GPIB_MAX_NUM_DESCRIPTORS 0x1000 + +enum ibsta_bit_numbers { + DCAS_NUM = 0, + DTAS_NUM = 1, + LACS_NUM = 2, + TACS_NUM = 3, + ATN_NUM = 4, + CIC_NUM = 5, + REM_NUM = 6, + LOK_NUM = 7, + CMPL_NUM = 8, + EVENT_NUM = 9, + SPOLL_NUM = 10, + RQS_NUM = 11, + SRQI_NUM = 12, + END_NUM = 13, + TIMO_NUM = 14, + ERR_NUM = 15 +}; + +/* IBSTA status bits (returned by all functions) */ +enum ibsta_bits { + DCAS = (1 << DCAS_NUM), /* device clear state */ + DTAS = (1 << DTAS_NUM), /* device trigger state */ + LACS = (1 << LACS_NUM), /* GPIB interface is addressed as Listener */ + TACS = (1 << TACS_NUM), /* GPIB interface is addressed as Talker */ + ATN = (1 << ATN_NUM), /* Attention is asserted */ + CIC = (1 << CIC_NUM), /* GPIB interface is Controller-in-Charge */ + REM = (1 << REM_NUM), /* remote state */ + LOK = (1 << LOK_NUM), /* lockout state */ + CMPL = (1 << CMPL_NUM), /* I/O is complete */ + EVENT = (1 << EVENT_NUM), /* DCAS, DTAS, or IFC has occurred */ + SPOLL = (1 << SPOLL_NUM), /* board serial polled by busmaster */ + RQS = (1 << RQS_NUM), /* Device requesting service */ + SRQI = (1 << SRQI_NUM), /* SRQ is asserted */ + END = (1 << END_NUM), /* EOI or EOS encountered */ + TIMO = (1 << TIMO_NUM), /* Time limit on I/O or wait function exceeded */ + ERR = (1 << ERR_NUM), /* Function call terminated on error */ + + device_status_mask = ERR | TIMO | END | CMPL | RQS, + board_status_mask = ERR | TIMO | END | CMPL | SPOLL | + EVENT | LOK | REM | CIC | ATN | TACS | LACS | DTAS | DCAS | SRQI, +}; + +/* End-of-string (EOS) modes for use with ibeos */ + +enum eos_flags { + EOS_MASK = 0x1c00, + REOS = 0x0400, /* Terminate reads on EOS */ + XEOS = 0x800, /* assert EOI when EOS char is sent */ + BIN = 0x1000 /* Do 8-bit compare on EOS */ +}; + +/* GPIB Bus Control Lines bit vector */ +enum bus_control_line { + VALID_DAV = 0x01, + VALID_NDAC = 0x02, + VALID_NRFD = 0x04, + VALID_IFC = 0x08, + VALID_REN = 0x10, + VALID_SRQ = 0x20, + VALID_ATN = 0x40, + VALID_EOI = 0x80, + VALID_ALL = 0xff, + BUS_DAV = 0x0100, /* DAV line status bit */ + BUS_NDAC = 0x0200, /* NDAC line status bit */ + BUS_NRFD = 0x0400, /* NRFD line status bit */ + BUS_IFC = 0x0800, /* IFC line status bit */ + BUS_REN = 0x1000, /* REN line status bit */ + BUS_SRQ = 0x2000, /* SRQ line status bit */ + BUS_ATN = 0x4000, /* ATN line status bit */ + BUS_EOI = 0x8000 /* EOI line status bit */ +}; + +enum ppe_bits { + PPC_DISABLE = 0x10, + PPC_SENSE = 0x8, /* parallel poll sense bit */ + PPC_DIO_MASK = 0x7 +}; + +enum { + request_service_bit = 0x40, +}; + +enum gpib_events { + EVENT_NONE = 0, + EVENT_DEV_TRG = 1, + EVENT_DEV_CLR = 2, + EVENT_IFC = 3 +}; + +#endif /* _GPIB_H */ + diff --git a/include/uapi/linux/gpib_ioctl.h b/include/uapi/linux/gpib_ioctl.h new file mode 100644 index 000000000000..d544d8e4362c --- /dev/null +++ b/include/uapi/linux/gpib_ioctl.h @@ -0,0 +1,167 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ + +/*************************************************************************** + * copyright : (C) 2002 by Frank Mori Hess + ***************************************************************************/ + +#ifndef _GPIB_IOCTL_H +#define _GPIB_IOCTL_H + +#include +#include + +#define GPIB_CODE 160 + +struct gpib_board_type_ioctl { + char name[100]; +}; + +/* argument for read/write/command ioctls */ +struct gpib_read_write_ioctl { + __u64 buffer_ptr; + __u32 requested_transfer_count; + __u32 completed_transfer_count; + __s32 end; /* end flag return for reads, end io suppression request for cmd*/ + __s32 handle; +}; + +struct gpib_open_dev_ioctl { + __u32 handle; + __u32 pad; + __s32 sad; + __u32 is_board; +}; + +struct gpib_close_dev_ioctl { + __u32 handle; +}; + +struct gpib_serial_poll_ioctl { + __u32 pad; + __s32 sad; + __u8 status_byte; + __u8 padding[3]; /* align to 32 bit boundary */ +}; + +struct gpib_eos_ioctl { + __s32 eos; + __s32 eos_flags; +}; + +struct gpib_wait_ioctl { + __s32 handle; + __s32 wait_mask; + __s32 clear_mask; + __s32 set_mask; + __s32 ibsta; + __s32 pad; + __s32 sad; + __u32 usec_timeout; +}; + +struct gpib_online_ioctl { + __u64 init_data_ptr; + __s32 init_data_length; + __s32 online; +}; + +struct gpib_spoll_bytes_ioctl { + __u32 num_bytes; + __u32 pad; + __s32 sad; +}; + +struct gpib_board_info_ioctl { + __u32 pad; + __s32 sad; + __s32 parallel_poll_configuration; + __s32 autopolling; + __s32 is_system_controller; + __u32 t1_delay; + unsigned ist : 1; + unsigned no_7_bit_eos : 1; + unsigned padding :30; /* align to 32 bit boundary */ +}; + +struct gpib_select_pci_ioctl { + __s32 pci_bus; + __s32 pci_slot; +}; + +struct gpib_ppoll_config_ioctl { + __u8 config; + unsigned set_ist : 1; + unsigned clear_ist : 1; + unsigned padding :22; /* align to 32 bit boundary */ +}; + +struct gpib_pad_ioctl { + __u32 handle; + __u32 pad; +}; + +struct gpib_sad_ioctl { + __u32 handle; + __s32 sad; +}; + +/* select a piece of hardware to attach by its sysfs device path */ +struct gpib_select_device_path_ioctl { + char device_path[0x1000]; +}; + +/* update status byte and request service */ +struct gpib_request_service2 { + __u8 status_byte; + __u8 padding[3]; /* align to 32 bit boundary */ + __s32 new_reason_for_service; +}; + +/* Standard functions. */ +enum gpib_ioctl { + IBRD = _IOWR(GPIB_CODE, 100, struct gpib_read_write_ioctl), + IBWRT = _IOWR(GPIB_CODE, 101, struct gpib_read_write_ioctl), + IBCMD = _IOWR(GPIB_CODE, 102, struct gpib_read_write_ioctl), + IBOPENDEV = _IOWR(GPIB_CODE, 3, struct gpib_open_dev_ioctl), + IBCLOSEDEV = _IOW(GPIB_CODE, 4, struct gpib_close_dev_ioctl), + IBWAIT = _IOWR(GPIB_CODE, 5, struct gpib_wait_ioctl), + IBRPP = _IOWR(GPIB_CODE, 6, __u8), + + IBSIC = _IOW(GPIB_CODE, 9, __u32), + IBSRE = _IOW(GPIB_CODE, 10, __s32), + IBGTS = _IO(GPIB_CODE, 11), + IBCAC = _IOW(GPIB_CODE, 12, __s32), + IBLINES = _IOR(GPIB_CODE, 14, __s16), + IBPAD = _IOW(GPIB_CODE, 15, struct gpib_pad_ioctl), + IBSAD = _IOW(GPIB_CODE, 16, struct gpib_sad_ioctl), + IBTMO = _IOW(GPIB_CODE, 17, __u32), + IBRSP = _IOWR(GPIB_CODE, 18, struct gpib_serial_poll_ioctl), + IBEOS = _IOW(GPIB_CODE, 19, struct gpib_eos_ioctl), + IBRSV = _IOW(GPIB_CODE, 20, __u8), + CFCBASE = _IOW(GPIB_CODE, 21, __u64), + CFCIRQ = _IOW(GPIB_CODE, 22, __u32), + CFCDMA = _IOW(GPIB_CODE, 23, __u32), + CFCBOARDTYPE = _IOW(GPIB_CODE, 24, struct gpib_board_type_ioctl), + + IBMUTEX = _IOW(GPIB_CODE, 26, __s32), + IBSPOLL_BYTES = _IOWR(GPIB_CODE, 27, struct gpib_spoll_bytes_ioctl), + IBPPC = _IOW(GPIB_CODE, 28, struct gpib_ppoll_config_ioctl), + IBBOARD_INFO = _IOR(GPIB_CODE, 29, struct gpib_board_info_ioctl), + + IBQUERY_BOARD_RSV = _IOR(GPIB_CODE, 31, __s32), + IBSELECT_PCI = _IOWR(GPIB_CODE, 32, struct gpib_select_pci_ioctl), + IBEVENT = _IOR(GPIB_CODE, 33, __s16), + IBRSC = _IOW(GPIB_CODE, 34, __s32), + IB_T1_DELAY = _IOW(GPIB_CODE, 35, __u32), + IBLOC = _IO(GPIB_CODE, 36), + + IBAUTOSPOLL = _IOW(GPIB_CODE, 38, __s16), + IBONL = _IOW(GPIB_CODE, 39, struct gpib_online_ioctl), + IBPP2_SET = _IOW(GPIB_CODE, 40, __s16), + IBPP2_GET = _IOR(GPIB_CODE, 41, __s16), + IBSELECT_DEVICE_PATH = _IOW(GPIB_CODE, 43, struct gpib_select_device_path_ioctl), + /* 44 was IBSELECT_SERIAL_NUMBER */ + IBRSV2 = _IOW(GPIB_CODE, 45, struct gpib_request_service2) +}; + +#endif /* _GPIB_IOCTL_H */ -- cgit v1.2.3 From 6b1ac78dd0f29fe66421c460c12ec15e45af38c3 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Mon, 13 Oct 2025 10:22:04 +1030 Subject: btrfs: implement shutdown ioctl The shutdown ioctl should follow the XFS one, which use magic number 'X', and ioctl number 125, with a uint32 as flags. For now btrfs don't distinguish DEFAULT and LOGFLUSH flags (just like f2fs), both will freeze the fs first (implies committing the current transaction), setting the SHUTDOWN flag and finally thaw the fs. For NOLOGFLUSH flag, the freeze/thaw part is skipped thus the current transaction is aborted. The new shutdown ioctl is hidden behind experimental features for more testing. Reviewed-by: Johannes Thumshirn Reviewed-by: Anand Jain Tested-by: Anand Jain Signed-off-by: Qu Wenruo Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/ioctl.c | 41 +++++++++++++++++++++++++++++++++++++++++ include/uapi/linux/btrfs.h | 9 +++++++++ 2 files changed, 50 insertions(+) (limited to 'include/uapi/linux') diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index 803556ec0e18..127b5d8303a8 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -5223,6 +5223,43 @@ static int btrfs_ioctl_subvol_sync(struct btrfs_fs_info *fs_info, void __user *a return 0; } +#ifdef CONFIG_BTRFS_EXPERIMENTAL +static int btrfs_ioctl_shutdown(struct btrfs_fs_info *fs_info, unsigned long arg) +{ + int ret = 0; + u32 flags; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (get_user(flags, (u32 __user *)arg)) + return -EFAULT; + + if (flags >= BTRFS_SHUTDOWN_FLAGS_LAST) + return -EINVAL; + + if (btrfs_is_shutdown(fs_info)) + return 0; + + switch (flags) { + case BTRFS_SHUTDOWN_FLAGS_LOGFLUSH: + case BTRFS_SHUTDOWN_FLAGS_DEFAULT: + ret = freeze_super(fs_info->sb, FREEZE_HOLDER_KERNEL, NULL); + if (ret) + return ret; + btrfs_force_shutdown(fs_info); + ret = thaw_super(fs_info->sb, FREEZE_HOLDER_KERNEL, NULL); + if (ret) + return ret; + break; + case BTRFS_SHUTDOWN_FLAGS_NOLOGFLUSH: + btrfs_force_shutdown(fs_info); + break; + } + return ret; +} +#endif + long btrfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { @@ -5378,6 +5415,10 @@ long btrfs_ioctl(struct file *file, unsigned int #endif case BTRFS_IOC_SUBVOL_SYNC_WAIT: return btrfs_ioctl_subvol_sync(fs_info, argp); +#ifdef CONFIG_BTRFS_EXPERIMENTAL + case BTRFS_IOC_SHUTDOWN: + return btrfs_ioctl_shutdown(fs_info, arg); +#endif } return -ENOTTY; diff --git a/include/uapi/linux/btrfs.h b/include/uapi/linux/btrfs.h index 8e710bbb688e..e8fd92789423 100644 --- a/include/uapi/linux/btrfs.h +++ b/include/uapi/linux/btrfs.h @@ -1099,6 +1099,12 @@ enum btrfs_err_code { BTRFS_ERROR_DEV_RAID1C4_MIN_NOT_MET, }; +/* Flags for IOC_SHUTDOWN, must match XFS_FSOP_GOING_FLAGS_* flags. */ +#define BTRFS_SHUTDOWN_FLAGS_DEFAULT 0x0 +#define BTRFS_SHUTDOWN_FLAGS_LOGFLUSH 0x1 +#define BTRFS_SHUTDOWN_FLAGS_NOLOGFLUSH 0x2 +#define BTRFS_SHUTDOWN_FLAGS_LAST 0x3 + #define BTRFS_IOC_SNAP_CREATE _IOW(BTRFS_IOCTL_MAGIC, 1, \ struct btrfs_ioctl_vol_args) #define BTRFS_IOC_DEFRAG _IOW(BTRFS_IOCTL_MAGIC, 2, \ @@ -1220,6 +1226,9 @@ enum btrfs_err_code { #define BTRFS_IOC_SUBVOL_SYNC_WAIT _IOW(BTRFS_IOCTL_MAGIC, 65, \ struct btrfs_ioctl_subvol_wait) +/* Shutdown ioctl should follow XFS's interfaces, thus not using btrfs magic. */ +#define BTRFS_IOC_SHUTDOWN _IOR('X', 125, __u32) + #ifdef __cplusplus } #endif -- cgit v1.2.3 From 1c6a92a5a5de7ebf94526dee7068926e6d5b1b01 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Mon, 24 Nov 2025 18:28:34 -0800 Subject: wifi: nl80211: vendor-cmd: intel: fix a blank kernel-doc line warning Delete an empty line prevent a kernel-doc warning: Warning: ../include/uapi/linux/nl80211-vnd-intel.h:86 bad line: Fixes: 3d2a2544eae9 ("nl80211: vendor-cmd: add Intel vendor commands for iwlmei usage") Signed-off-by: Randy Dunlap Link: https://patch.msgid.link/20251125022834.3171742-1-rdunlap@infradead.org Signed-off-by: Johannes Berg --- include/uapi/linux/nl80211-vnd-intel.h | 1 - 1 file changed, 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/nl80211-vnd-intel.h b/include/uapi/linux/nl80211-vnd-intel.h index 4ed7d0b24512..79ccc9401d50 100644 --- a/include/uapi/linux/nl80211-vnd-intel.h +++ b/include/uapi/linux/nl80211-vnd-intel.h @@ -84,7 +84,6 @@ enum iwl_vendor_auth_akm_mode { * * @NUM_IWL_MVM_VENDOR_ATTR: number of vendor attributes * @MAX_IWL_MVM_VENDOR_ATTR: highest vendor attribute number - */ enum iwl_mvm_vendor_attr { __IWL_MVM_VENDOR_ATTR_INVALID = 0x00, -- cgit v1.2.3 From 68e83f3472667aac18d577587102f4bf77d0bd06 Mon Sep 17 00:00:00 2001 From: Asbjørn Sloth Tønnesen Date: Thu, 20 Nov 2025 17:44:27 +0000 Subject: tools: ynl-gen: add regeneration comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a comment on regeneration to the generated files. The comment is placed after the YNL-GEN line[1], as to not interfere with ynl-regen.sh's detection logic. [1] and after the optional YNL-ARG line. Link: https://lore.kernel.org/r/aR5m174O7pklKrMR@zx2c4.com/ Suggested-by: Jason A. Donenfeld Signed-off-by: Asbjørn Sloth Tønnesen Acked-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20251120174429.390574-3-ast@fiberby.net Signed-off-by: Jakub Kicinski --- drivers/android/binder_netlink.c | 1 + drivers/android/binder_netlink.h | 1 + drivers/dpll/dpll_nl.c | 1 + drivers/dpll/dpll_nl.h | 1 + drivers/net/ovpn/netlink-gen.c | 1 + drivers/net/ovpn/netlink-gen.h | 1 + drivers/net/team/team_nl.c | 1 + drivers/net/team/team_nl.h | 1 + fs/lockd/netlink.c | 1 + fs/lockd/netlink.h | 1 + fs/nfsd/netlink.c | 1 + fs/nfsd/netlink.h | 1 + include/uapi/linux/android/binder_netlink.h | 1 + include/uapi/linux/dpll.h | 1 + include/uapi/linux/ethtool_netlink_generated.h | 1 + include/uapi/linux/fou.h | 1 + include/uapi/linux/handshake.h | 1 + include/uapi/linux/if_team.h | 1 + include/uapi/linux/lockd_netlink.h | 1 + include/uapi/linux/mptcp_pm.h | 1 + include/uapi/linux/net_shaper.h | 1 + include/uapi/linux/netdev.h | 1 + include/uapi/linux/nfsd_netlink.h | 1 + include/uapi/linux/ovpn.h | 1 + include/uapi/linux/psp.h | 1 + net/core/netdev-genl-gen.c | 1 + net/core/netdev-genl-gen.h | 1 + net/devlink/netlink_gen.c | 1 + net/devlink/netlink_gen.h | 1 + net/handshake/genl.c | 1 + net/handshake/genl.h | 1 + net/ipv4/fou_nl.c | 1 + net/ipv4/fou_nl.h | 1 + net/mptcp/mptcp_pm_gen.c | 1 + net/mptcp/mptcp_pm_gen.h | 1 + net/psp/psp-nl-gen.c | 1 + net/psp/psp-nl-gen.h | 1 + net/shaper/shaper_nl_gen.c | 1 + net/shaper/shaper_nl_gen.h | 1 + tools/include/uapi/linux/netdev.h | 1 + tools/net/ynl/pyynl/ynl_gen_c.py | 1 + 41 files changed, 41 insertions(+) (limited to 'include/uapi/linux') diff --git a/drivers/android/binder_netlink.c b/drivers/android/binder_netlink.c index d05397a50ca6..81e8432b5904 100644 --- a/drivers/android/binder_netlink.c +++ b/drivers/android/binder_netlink.c @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/binder.yaml */ /* YNL-GEN kernel source */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #include #include diff --git a/drivers/android/binder_netlink.h b/drivers/android/binder_netlink.h index 882c7a6b537e..57399942a5e3 100644 --- a/drivers/android/binder_netlink.h +++ b/drivers/android/binder_netlink.h @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/binder.yaml */ /* YNL-GEN kernel header */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #ifndef _LINUX_BINDER_GEN_H #define _LINUX_BINDER_GEN_H diff --git a/drivers/dpll/dpll_nl.c b/drivers/dpll/dpll_nl.c index 3c6d570babf8..36d11ff195df 100644 --- a/drivers/dpll/dpll_nl.c +++ b/drivers/dpll/dpll_nl.c @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/dpll.yaml */ /* YNL-GEN kernel source */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #include #include diff --git a/drivers/dpll/dpll_nl.h b/drivers/dpll/dpll_nl.h index 3da10cfe9a6e..7419679b6977 100644 --- a/drivers/dpll/dpll_nl.h +++ b/drivers/dpll/dpll_nl.h @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/dpll.yaml */ /* YNL-GEN kernel header */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #ifndef _LINUX_DPLL_GEN_H #define _LINUX_DPLL_GEN_H diff --git a/drivers/net/ovpn/netlink-gen.c b/drivers/net/ovpn/netlink-gen.c index 14298188c5f1..ecbe9dcf4f7d 100644 --- a/drivers/net/ovpn/netlink-gen.c +++ b/drivers/net/ovpn/netlink-gen.c @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/ovpn.yaml */ /* YNL-GEN kernel source */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #include #include diff --git a/drivers/net/ovpn/netlink-gen.h b/drivers/net/ovpn/netlink-gen.h index 220b5b2fdd4f..b2301580770f 100644 --- a/drivers/net/ovpn/netlink-gen.h +++ b/drivers/net/ovpn/netlink-gen.h @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/ovpn.yaml */ /* YNL-GEN kernel header */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #ifndef _LINUX_OVPN_GEN_H #define _LINUX_OVPN_GEN_H diff --git a/drivers/net/team/team_nl.c b/drivers/net/team/team_nl.c index 208424ab78f5..6db21725f9cc 100644 --- a/drivers/net/team/team_nl.c +++ b/drivers/net/team/team_nl.c @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/team.yaml */ /* YNL-GEN kernel source */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #include #include diff --git a/drivers/net/team/team_nl.h b/drivers/net/team/team_nl.h index c9ec1b22ac4d..74816b193475 100644 --- a/drivers/net/team/team_nl.h +++ b/drivers/net/team/team_nl.h @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/team.yaml */ /* YNL-GEN kernel header */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #ifndef _LINUX_TEAM_GEN_H #define _LINUX_TEAM_GEN_H diff --git a/fs/lockd/netlink.c b/fs/lockd/netlink.c index 6e00b02cad90..880c42b4f8c3 100644 --- a/fs/lockd/netlink.c +++ b/fs/lockd/netlink.c @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/lockd.yaml */ /* YNL-GEN kernel source */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #include #include diff --git a/fs/lockd/netlink.h b/fs/lockd/netlink.h index 1920543a7955..d8408f077dd8 100644 --- a/fs/lockd/netlink.h +++ b/fs/lockd/netlink.h @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/lockd.yaml */ /* YNL-GEN kernel header */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #ifndef _LINUX_LOCKD_GEN_H #define _LINUX_LOCKD_GEN_H diff --git a/fs/nfsd/netlink.c b/fs/nfsd/netlink.c index ca54aa583530..ac51a44e1065 100644 --- a/fs/nfsd/netlink.c +++ b/fs/nfsd/netlink.c @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/nfsd.yaml */ /* YNL-GEN kernel source */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #include #include diff --git a/fs/nfsd/netlink.h b/fs/nfsd/netlink.h index 8eb903f24c41..478117ff6b8c 100644 --- a/fs/nfsd/netlink.h +++ b/fs/nfsd/netlink.h @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/nfsd.yaml */ /* YNL-GEN kernel header */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #ifndef _LINUX_NFSD_GEN_H #define _LINUX_NFSD_GEN_H diff --git a/include/uapi/linux/android/binder_netlink.h b/include/uapi/linux/android/binder_netlink.h index b218f96d6668..bf69833c9a19 100644 --- a/include/uapi/linux/android/binder_netlink.h +++ b/include/uapi/linux/android/binder_netlink.h @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/binder.yaml */ /* YNL-GEN uapi header */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #ifndef _UAPI_LINUX_ANDROID_BINDER_NETLINK_H #define _UAPI_LINUX_ANDROID_BINDER_NETLINK_H diff --git a/include/uapi/linux/dpll.h b/include/uapi/linux/dpll.h index 69d35570ac4f..b7ff9c44f9aa 100644 --- a/include/uapi/linux/dpll.h +++ b/include/uapi/linux/dpll.h @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/dpll.yaml */ /* YNL-GEN uapi header */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #ifndef _UAPI_LINUX_DPLL_H #define _UAPI_LINUX_DPLL_H diff --git a/include/uapi/linux/ethtool_netlink_generated.h b/include/uapi/linux/ethtool_netlink_generated.h index b71b175df46d..556a0c834df5 100644 --- a/include/uapi/linux/ethtool_netlink_generated.h +++ b/include/uapi/linux/ethtool_netlink_generated.h @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/ethtool.yaml */ /* YNL-GEN uapi header */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #ifndef _UAPI_LINUX_ETHTOOL_NETLINK_GENERATED_H #define _UAPI_LINUX_ETHTOOL_NETLINK_GENERATED_H diff --git a/include/uapi/linux/fou.h b/include/uapi/linux/fou.h index b5cd3e7b3775..bb6bef74d2d1 100644 --- a/include/uapi/linux/fou.h +++ b/include/uapi/linux/fou.h @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/fou.yaml */ /* YNL-GEN uapi header */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #ifndef _UAPI_LINUX_FOU_H #define _UAPI_LINUX_FOU_H diff --git a/include/uapi/linux/handshake.h b/include/uapi/linux/handshake.h index 662e7de46c54..d7e40f594888 100644 --- a/include/uapi/linux/handshake.h +++ b/include/uapi/linux/handshake.h @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/handshake.yaml */ /* YNL-GEN uapi header */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #ifndef _UAPI_LINUX_HANDSHAKE_H #define _UAPI_LINUX_HANDSHAKE_H diff --git a/include/uapi/linux/if_team.h b/include/uapi/linux/if_team.h index a5c06243a435..f4cd839ae725 100644 --- a/include/uapi/linux/if_team.h +++ b/include/uapi/linux/if_team.h @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/team.yaml */ /* YNL-GEN uapi header */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #ifndef _UAPI_LINUX_IF_TEAM_H #define _UAPI_LINUX_IF_TEAM_H diff --git a/include/uapi/linux/lockd_netlink.h b/include/uapi/linux/lockd_netlink.h index 21c65aec3bc6..2d766a0fa6ea 100644 --- a/include/uapi/linux/lockd_netlink.h +++ b/include/uapi/linux/lockd_netlink.h @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/lockd.yaml */ /* YNL-GEN uapi header */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #ifndef _UAPI_LINUX_LOCKD_NETLINK_H #define _UAPI_LINUX_LOCKD_NETLINK_H diff --git a/include/uapi/linux/mptcp_pm.h b/include/uapi/linux/mptcp_pm.h index bf44a5cf5b5a..c97d060ee90b 100644 --- a/include/uapi/linux/mptcp_pm.h +++ b/include/uapi/linux/mptcp_pm.h @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/mptcp_pm.yaml */ /* YNL-GEN uapi header */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #ifndef _UAPI_LINUX_MPTCP_PM_H #define _UAPI_LINUX_MPTCP_PM_H diff --git a/include/uapi/linux/net_shaper.h b/include/uapi/linux/net_shaper.h index d8834b59f7d7..3dd22c2930d9 100644 --- a/include/uapi/linux/net_shaper.h +++ b/include/uapi/linux/net_shaper.h @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/net_shaper.yaml */ /* YNL-GEN uapi header */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #ifndef _UAPI_LINUX_NET_SHAPER_H #define _UAPI_LINUX_NET_SHAPER_H diff --git a/include/uapi/linux/netdev.h b/include/uapi/linux/netdev.h index 048c8de1a130..e0b579a1df4f 100644 --- a/include/uapi/linux/netdev.h +++ b/include/uapi/linux/netdev.h @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/netdev.yaml */ /* YNL-GEN uapi header */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #ifndef _UAPI_LINUX_NETDEV_H #define _UAPI_LINUX_NETDEV_H diff --git a/include/uapi/linux/nfsd_netlink.h b/include/uapi/linux/nfsd_netlink.h index 887cbd12b695..e157e2009ea8 100644 --- a/include/uapi/linux/nfsd_netlink.h +++ b/include/uapi/linux/nfsd_netlink.h @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/nfsd.yaml */ /* YNL-GEN uapi header */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #ifndef _UAPI_LINUX_NFSD_NETLINK_H #define _UAPI_LINUX_NFSD_NETLINK_H diff --git a/include/uapi/linux/ovpn.h b/include/uapi/linux/ovpn.h index 680d1522dc87..959b41def61f 100644 --- a/include/uapi/linux/ovpn.h +++ b/include/uapi/linux/ovpn.h @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/ovpn.yaml */ /* YNL-GEN uapi header */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #ifndef _UAPI_LINUX_OVPN_H #define _UAPI_LINUX_OVPN_H diff --git a/include/uapi/linux/psp.h b/include/uapi/linux/psp.h index d8449c043ba1..a3a336488dc3 100644 --- a/include/uapi/linux/psp.h +++ b/include/uapi/linux/psp.h @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/psp.yaml */ /* YNL-GEN uapi header */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #ifndef _UAPI_LINUX_PSP_H #define _UAPI_LINUX_PSP_H diff --git a/net/core/netdev-genl-gen.c b/net/core/netdev-genl-gen.c index ff20435c45d2..ba673e81716f 100644 --- a/net/core/netdev-genl-gen.c +++ b/net/core/netdev-genl-gen.c @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/netdev.yaml */ /* YNL-GEN kernel source */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #include #include diff --git a/net/core/netdev-genl-gen.h b/net/core/netdev-genl-gen.h index cf3fad74511f..cffc08517a41 100644 --- a/net/core/netdev-genl-gen.h +++ b/net/core/netdev-genl-gen.h @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/netdev.yaml */ /* YNL-GEN kernel header */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #ifndef _LINUX_NETDEV_GEN_H #define _LINUX_NETDEV_GEN_H diff --git a/net/devlink/netlink_gen.c b/net/devlink/netlink_gen.c index 580985025f49..f4c61c2b4f22 100644 --- a/net/devlink/netlink_gen.c +++ b/net/devlink/netlink_gen.c @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/devlink.yaml */ /* YNL-GEN kernel source */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #include #include diff --git a/net/devlink/netlink_gen.h b/net/devlink/netlink_gen.h index 09cc6f264ccf..2817d53a0eba 100644 --- a/net/devlink/netlink_gen.h +++ b/net/devlink/netlink_gen.h @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/devlink.yaml */ /* YNL-GEN kernel header */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #ifndef _LINUX_DEVLINK_GEN_H #define _LINUX_DEVLINK_GEN_H diff --git a/net/handshake/genl.c b/net/handshake/genl.c index f55d14d7b726..870612609491 100644 --- a/net/handshake/genl.c +++ b/net/handshake/genl.c @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/handshake.yaml */ /* YNL-GEN kernel source */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #include #include diff --git a/net/handshake/genl.h b/net/handshake/genl.h index ae72a596f6cc..8d3e18672daf 100644 --- a/net/handshake/genl.h +++ b/net/handshake/genl.h @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/handshake.yaml */ /* YNL-GEN kernel header */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #ifndef _LINUX_HANDSHAKE_GEN_H #define _LINUX_HANDSHAKE_GEN_H diff --git a/net/ipv4/fou_nl.c b/net/ipv4/fou_nl.c index 506260b4a4dc..7a99639204b1 100644 --- a/net/ipv4/fou_nl.c +++ b/net/ipv4/fou_nl.c @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/fou.yaml */ /* YNL-GEN kernel source */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #include #include diff --git a/net/ipv4/fou_nl.h b/net/ipv4/fou_nl.h index 63a6c4ed803d..438342dc8507 100644 --- a/net/ipv4/fou_nl.h +++ b/net/ipv4/fou_nl.h @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/fou.yaml */ /* YNL-GEN kernel header */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #ifndef _LINUX_FOU_GEN_H #define _LINUX_FOU_GEN_H diff --git a/net/mptcp/mptcp_pm_gen.c b/net/mptcp/mptcp_pm_gen.c index dcffd847af33..c180930a8e0e 100644 --- a/net/mptcp/mptcp_pm_gen.c +++ b/net/mptcp/mptcp_pm_gen.c @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/mptcp_pm.yaml */ /* YNL-GEN kernel source */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #include #include diff --git a/net/mptcp/mptcp_pm_gen.h b/net/mptcp/mptcp_pm_gen.h index e24258f6f819..b9280bcb43f5 100644 --- a/net/mptcp/mptcp_pm_gen.h +++ b/net/mptcp/mptcp_pm_gen.h @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/mptcp_pm.yaml */ /* YNL-GEN kernel header */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #ifndef _LINUX_MPTCP_PM_GEN_H #define _LINUX_MPTCP_PM_GEN_H diff --git a/net/psp/psp-nl-gen.c b/net/psp/psp-nl-gen.c index 73f8b06d66f0..22a48d0fa378 100644 --- a/net/psp/psp-nl-gen.c +++ b/net/psp/psp-nl-gen.c @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/psp.yaml */ /* YNL-GEN kernel source */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #include #include diff --git a/net/psp/psp-nl-gen.h b/net/psp/psp-nl-gen.h index 5bc3b5d5a53e..599c5f1c82f2 100644 --- a/net/psp/psp-nl-gen.h +++ b/net/psp/psp-nl-gen.h @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/psp.yaml */ /* YNL-GEN kernel header */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #ifndef _LINUX_PSP_GEN_H #define _LINUX_PSP_GEN_H diff --git a/net/shaper/shaper_nl_gen.c b/net/shaper/shaper_nl_gen.c index 204c8ae8c7b1..e8cccc4c1180 100644 --- a/net/shaper/shaper_nl_gen.c +++ b/net/shaper/shaper_nl_gen.c @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/net_shaper.yaml */ /* YNL-GEN kernel source */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #include #include diff --git a/net/shaper/shaper_nl_gen.h b/net/shaper/shaper_nl_gen.h index cb7f9026fc23..ec41c90431a4 100644 --- a/net/shaper/shaper_nl_gen.h +++ b/net/shaper/shaper_nl_gen.h @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/net_shaper.yaml */ /* YNL-GEN kernel header */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #ifndef _LINUX_NET_SHAPER_GEN_H #define _LINUX_NET_SHAPER_GEN_H diff --git a/tools/include/uapi/linux/netdev.h b/tools/include/uapi/linux/netdev.h index 048c8de1a130..e0b579a1df4f 100644 --- a/tools/include/uapi/linux/netdev.h +++ b/tools/include/uapi/linux/netdev.h @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/netdev.yaml */ /* YNL-GEN uapi header */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #ifndef _UAPI_LINUX_NETDEV_H #define _UAPI_LINUX_NETDEV_H diff --git a/tools/net/ynl/pyynl/ynl_gen_c.py b/tools/net/ynl/pyynl/ynl_gen_c.py index 0dd589e502cb..b517d0c605ad 100755 --- a/tools/net/ynl/pyynl/ynl_gen_c.py +++ b/tools/net/ynl/pyynl/ynl_gen_c.py @@ -3442,6 +3442,7 @@ def main(): if args.fn_prefix: line += f' --function-prefix {args.fn_prefix}' cw.p(f'/* YNL-ARG{line} */') + cw.p('/* To regenerate run: tools/net/ynl/ynl-regen.sh */') cw.nl() if args.mode == 'uapi': -- cgit v1.2.3 From 60f511f443e552ef5b5cd79ec2b881f4323e19c9 Mon Sep 17 00:00:00 2001 From: Vincent Mailhol Date: Wed, 26 Nov 2025 11:16:05 +0100 Subject: can: netlink: add CAN_CTRLMODE_RESTRICTED ISO 11898-1:2024 adds a new restricted operation mode. This mode is added as a mandatory feature for nodes which support CAN XL and is retrofitted as optional for legacy nodes (i.e. the ones which only support Classical CAN and CAN FD). The restricted operation mode is nearly the same as the listen only mode: the node can not send data frames or remote frames and can not send dominant bits if an error occurs. The only exception is that the node shall still send the acknowledgment bit. A second niche exception is that the node may still send a data frame containing a time reference message if the node is a primary time provider, but because the time provider feature is not yet implemented in the kernel, this second exception is not relevant to us at the moment. Add the CAN_CTRLMODE_RESTRICTED control mode flag and update the can_dev_dropped_skb() helper function accordingly. Finally, bail out if both CAN_CTRLMODE_LISTENONLY and CAN_CTRLMODE_RESTRICTED are provided. Signed-off-by: Vincent Mailhol Signed-off-by: Oliver Hartkopp Link: https://patch.msgid.link/20251126-canxl-v8-4-e7e3eb74f889@pengutronix.de Signed-off-by: Marc Kleine-Budde --- drivers/net/can/dev/dev.c | 2 ++ drivers/net/can/dev/netlink.c | 7 ++++++ include/linux/can/dev.h | 50 +++++++++++++++++++++------------------- include/uapi/linux/can/netlink.h | 1 + 4 files changed, 36 insertions(+), 24 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/net/can/dev/dev.c b/drivers/net/can/dev/dev.c index b392483e4499..b6980d32e5b4 100644 --- a/drivers/net/can/dev/dev.c +++ b/drivers/net/can/dev/dev.c @@ -115,6 +115,8 @@ const char *can_get_ctrlmode_str(u32 ctrlmode) return "TDC-AUTO"; case CAN_CTRLMODE_TDC_MANUAL: return "TDC-MANUAL"; + case CAN_CTRLMODE_RESTRICTED: + return "RESTRICTED"; default: return ""; } diff --git a/drivers/net/can/dev/netlink.c b/drivers/net/can/dev/netlink.c index 6f83b87d54fc..87e731527dd7 100644 --- a/drivers/net/can/dev/netlink.c +++ b/drivers/net/can/dev/netlink.c @@ -188,6 +188,13 @@ static int can_validate(struct nlattr *tb[], struct nlattr *data[], struct can_ctrlmode *cm = nla_data(data[IFLA_CAN_CTRLMODE]); flags = cm->flags & cm->mask; + + if ((flags & CAN_CTRLMODE_LISTENONLY) && + (flags & CAN_CTRLMODE_RESTRICTED)) { + NL_SET_ERR_MSG(extack, + "LISTEN-ONLY and RESTRICTED modes are mutually exclusive"); + return -EOPNOTSUPP; + } } err = can_validate_bittiming(data, extack, IFLA_CAN_BITTIMING); diff --git a/include/linux/can/dev.h b/include/linux/can/dev.h index a7a39a6101d9..ab11c0e9111b 100644 --- a/include/linux/can/dev.h +++ b/include/linux/can/dev.h @@ -95,30 +95,6 @@ static inline bool can_is_canxl_dev_mtu(unsigned int mtu) return (mtu >= CANXL_MIN_MTU && mtu <= CANXL_MAX_MTU); } -/* drop skb if it does not contain a valid CAN frame for sending */ -static inline bool can_dev_dropped_skb(struct net_device *dev, struct sk_buff *skb) -{ - struct can_priv *priv = netdev_priv(dev); - - if (priv->ctrlmode & CAN_CTRLMODE_LISTENONLY) { - netdev_info_once(dev, - "interface in listen only mode, dropping skb\n"); - goto invalid_skb; - } - - if (!(priv->ctrlmode & CAN_CTRLMODE_FD) && can_is_canfd_skb(skb)) { - netdev_info_once(dev, "CAN FD is disabled, dropping skb\n"); - goto invalid_skb; - } - - return can_dropped_invalid_skb(dev, skb); - -invalid_skb: - kfree_skb(skb); - dev->stats.tx_dropped++; - return true; -} - void can_setup(struct net_device *dev); struct net_device *alloc_candev_mqs(int sizeof_priv, unsigned int echo_skb_max, @@ -154,6 +130,32 @@ void can_bus_off(struct net_device *dev); const char *can_get_state_str(const enum can_state state); const char *can_get_ctrlmode_str(u32 ctrlmode); +/* drop skb if it does not contain a valid CAN frame for sending */ +static inline bool can_dev_dropped_skb(struct net_device *dev, struct sk_buff *skb) +{ + struct can_priv *priv = netdev_priv(dev); + u32 silent_mode = priv->ctrlmode & (CAN_CTRLMODE_LISTENONLY | + CAN_CTRLMODE_RESTRICTED); + + if (silent_mode) { + netdev_info_once(dev, "interface in %s mode, dropping skb\n", + can_get_ctrlmode_str(silent_mode)); + goto invalid_skb; + } + + if (!(priv->ctrlmode & CAN_CTRLMODE_FD) && can_is_canfd_skb(skb)) { + netdev_info_once(dev, "CAN FD is disabled, dropping skb\n"); + goto invalid_skb; + } + + return can_dropped_invalid_skb(dev, skb); + +invalid_skb: + kfree_skb(skb); + dev->stats.tx_dropped++; + return true; +} + void can_state_get_by_berr_counter(const struct net_device *dev, const struct can_berr_counter *bec, enum can_state *tx_state, diff --git a/include/uapi/linux/can/netlink.h b/include/uapi/linux/can/netlink.h index ef62f56eaaef..fafd1cce4798 100644 --- a/include/uapi/linux/can/netlink.h +++ b/include/uapi/linux/can/netlink.h @@ -103,6 +103,7 @@ struct can_ctrlmode { #define CAN_CTRLMODE_CC_LEN8_DLC 0x100 /* Classic CAN DLC option */ #define CAN_CTRLMODE_TDC_AUTO 0x200 /* FD transceiver automatically calculates TDCV */ #define CAN_CTRLMODE_TDC_MANUAL 0x400 /* FD TDCV is manually set up by user */ +#define CAN_CTRLMODE_RESTRICTED 0x800 /* Restricted operation mode */ /* * CAN device statistics -- cgit v1.2.3 From e63281614747c73f25b708c75bc696c4e76f5588 Mon Sep 17 00:00:00 2001 From: Vincent Mailhol Date: Wed, 26 Nov 2025 11:16:06 +0100 Subject: can: netlink: add initial CAN XL support CAN XL uses bittiming parameters different from Classical CAN and CAN FD. Thus, all the data bittiming parameters, including TDC, need to be duplicated for CAN XL. Add the CAN XL netlink interface for all the features which are common with CAN FD. Any new CAN XL specific features are added later on. The first time CAN XL is activated, the MTU is set by default to CANXL_MAX_MTU. The user may then configure a custom MTU within the CANXL_MIN_MTU to CANXL_MAX_MTU range, in which case, the custom MTU value will be kept as long as CAN XL remains active. Signed-off-by: Vincent Mailhol Signed-off-by: Oliver Hartkopp Link: https://patch.msgid.link/20251126-canxl-v8-5-e7e3eb74f889@pengutronix.de Signed-off-by: Marc Kleine-Budde --- drivers/net/can/dev/dev.c | 14 +++++++- drivers/net/can/dev/netlink.c | 76 +++++++++++++++++++++++++++++++--------- include/linux/can/bittiming.h | 6 ++-- include/linux/can/dev.h | 7 +++- include/uapi/linux/can/netlink.h | 7 ++++ 5 files changed, 90 insertions(+), 20 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/net/can/dev/dev.c b/drivers/net/can/dev/dev.c index b6980d32e5b4..bdec2c52c8ec 100644 --- a/drivers/net/can/dev/dev.c +++ b/drivers/net/can/dev/dev.c @@ -117,6 +117,12 @@ const char *can_get_ctrlmode_str(u32 ctrlmode) return "TDC-MANUAL"; case CAN_CTRLMODE_RESTRICTED: return "RESTRICTED"; + case CAN_CTRLMODE_XL: + return "XL"; + case CAN_CTRLMODE_XL_TDC_AUTO: + return "XL-TDC-AUTO"; + case CAN_CTRLMODE_XL_TDC_MANUAL: + return "XL-TDC-MANUAL"; default: return ""; } @@ -350,7 +356,13 @@ void can_set_default_mtu(struct net_device *dev) { struct can_priv *priv = netdev_priv(dev); - if (priv->ctrlmode & CAN_CTRLMODE_FD) { + if (priv->ctrlmode & CAN_CTRLMODE_XL) { + if (can_is_canxl_dev_mtu(dev->mtu)) + return; + dev->mtu = CANXL_MTU; + dev->min_mtu = CANXL_MIN_MTU; + dev->max_mtu = CANXL_MAX_MTU; + } else if (priv->ctrlmode & CAN_CTRLMODE_FD) { dev->mtu = CANFD_MTU; dev->min_mtu = CANFD_MTU; dev->max_mtu = CANFD_MTU; diff --git a/drivers/net/can/dev/netlink.c b/drivers/net/can/dev/netlink.c index 87e731527dd7..fdd1fa7cf93a 100644 --- a/drivers/net/can/dev/netlink.c +++ b/drivers/net/can/dev/netlink.c @@ -2,7 +2,7 @@ /* Copyright (C) 2005 Marc Kleine-Budde, Pengutronix * Copyright (C) 2006 Andrey Volkov, Varma Electronics * Copyright (C) 2008-2009 Wolfgang Grandegger - * Copyright (C) 2021 Vincent Mailhol + * Copyright (C) 2021-2025 Vincent Mailhol */ #include @@ -22,6 +22,9 @@ static const struct nla_policy can_policy[IFLA_CAN_MAX + 1] = { [IFLA_CAN_TERMINATION] = { .type = NLA_U16 }, [IFLA_CAN_TDC] = { .type = NLA_NESTED }, [IFLA_CAN_CTRLMODE_EXT] = { .type = NLA_NESTED }, + [IFLA_CAN_XL_DATA_BITTIMING] = { .len = sizeof(struct can_bittiming) }, + [IFLA_CAN_XL_DATA_BITTIMING_CONST] = { .len = sizeof(struct can_bittiming_const) }, + [IFLA_CAN_XL_TDC] = { .type = NLA_NESTED }, }; static const struct nla_policy can_tdc_policy[IFLA_CAN_TDC_MAX + 1] = { @@ -70,7 +73,7 @@ static int can_validate_tdc(struct nlattr *data_tdc, return -EOPNOTSUPP; } - /* If one of the CAN_CTRLMODE_TDC_* flag is set then TDC + /* If one of the CAN_CTRLMODE_{,XL}_TDC_* flags is set then TDC * must be set and vice-versa */ if ((tdc_auto || tdc_manual) && !data_tdc) { @@ -82,8 +85,8 @@ static int can_validate_tdc(struct nlattr *data_tdc, return -EOPNOTSUPP; } - /* If providing TDC parameters, at least TDCO is needed. TDCV - * is needed if and only if CAN_CTRLMODE_TDC_MANUAL is set + /* If providing TDC parameters, at least TDCO is needed. TDCV is + * needed if and only if CAN_CTRLMODE_{,XL}_TDC_MANUAL is set */ if (data_tdc) { struct nlattr *tb_tdc[IFLA_CAN_TDC_MAX + 1]; @@ -126,10 +129,10 @@ static int can_validate_databittiming(struct nlattr *data[], bool is_on; int err; - /* Make sure that valid CAN FD configurations always consist of + /* Make sure that valid CAN FD/XL configurations always consist of * - nominal/arbitration bittiming * - data bittiming - * - control mode with CAN_CTRLMODE_FD set + * - control mode with CAN_CTRLMODE_{FD,XL} set * - TDC parameters are coherent (details in can_validate_tdc()) */ @@ -139,7 +142,10 @@ static int can_validate_databittiming(struct nlattr *data[], is_on = flags & CAN_CTRLMODE_FD; type = "FD"; } else { - return -EOPNOTSUPP; /* Place holder for CAN XL */ + data_tdc = data[IFLA_CAN_XL_TDC]; + tdc_flags = flags & CAN_CTRLMODE_XL_TDC_MASK; + is_on = flags & CAN_CTRLMODE_XL; + type = "XL"; } if (is_on) { @@ -206,6 +212,11 @@ static int can_validate(struct nlattr *tb[], struct nlattr *data[], if (err) return err; + err = can_validate_databittiming(data, extack, + IFLA_CAN_XL_DATA_BITTIMING, flags); + if (err) + return err; + return 0; } @@ -251,18 +262,26 @@ static int can_ctrlmode_changelink(struct net_device *dev, /* If a top dependency flag is provided, reset all its dependencies */ if (cm->mask & CAN_CTRLMODE_FD) priv->ctrlmode &= ~CAN_CTRLMODE_FD_TDC_MASK; + if (cm->mask & CAN_CTRLMODE_XL) + priv->ctrlmode &= ~(CAN_CTRLMODE_XL_TDC_MASK); /* clear bits to be modified and copy the flag values */ priv->ctrlmode &= ~cm->mask; priv->ctrlmode |= maskedflags; - /* Wipe potential leftovers from previous CAN FD config */ + /* Wipe potential leftovers from previous CAN FD/XL config */ if (!(priv->ctrlmode & CAN_CTRLMODE_FD)) { memset(&priv->fd.data_bittiming, 0, sizeof(priv->fd.data_bittiming)); priv->ctrlmode &= ~CAN_CTRLMODE_FD_TDC_MASK; memset(&priv->fd.tdc, 0, sizeof(priv->fd.tdc)); } + if (!(priv->ctrlmode & CAN_CTRLMODE_XL)) { + memset(&priv->xl.data_bittiming, 0, + sizeof(priv->fd.data_bittiming)); + priv->ctrlmode &= ~CAN_CTRLMODE_XL_TDC_MASK; + memset(&priv->xl.tdc, 0, sizeof(priv->xl.tdc)); + } can_set_default_mtu(dev); @@ -337,7 +356,10 @@ static int can_dbt_changelink(struct net_device *dev, struct nlattr *data[], dbt_params = &priv->fd; tdc_mask = CAN_CTRLMODE_FD_TDC_MASK; } else { - return -EOPNOTSUPP; /* Place holder for CAN XL */ + data_bittiming = data[IFLA_CAN_XL_DATA_BITTIMING]; + data_tdc = data[IFLA_CAN_XL_TDC]; + dbt_params = &priv->xl; + tdc_mask = CAN_CTRLMODE_XL_TDC_MASK; } if (!data_bittiming) @@ -388,7 +410,7 @@ static int can_dbt_changelink(struct net_device *dev, struct nlattr *data[], */ can_calc_tdco(&dbt_params->tdc, dbt_params->tdc_const, &dbt, tdc_mask, &priv->ctrlmode, priv->ctrlmode_supported); - } /* else: both CAN_CTRLMODE_TDC_{AUTO,MANUAL} are explicitly + } /* else: both CAN_CTRLMODE_{,XL}_TDC_{AUTO,MANUAL} are explicitly * turned off. TDC is disabled: do nothing */ @@ -493,6 +515,11 @@ static int can_changelink(struct net_device *dev, struct nlattr *tb[], if (err) return err; + /* CAN XL */ + err = can_dbt_changelink(dev, data, false, extack); + if (err) + return err; + if (data[IFLA_CAN_TERMINATION]) { const u16 termval = nla_get_u16(data[IFLA_CAN_TERMINATION]); const unsigned int num_term = priv->termination_const_cnt; @@ -560,14 +587,14 @@ static size_t can_data_bittiming_get_size(struct data_bittiming_params *dbt_para { size_t size = 0; - if (dbt_params->data_bittiming.bitrate) /* IFLA_CAN_DATA_BITTIMING */ + if (dbt_params->data_bittiming.bitrate) /* IFLA_CAN_{,XL}_DATA_BITTIMING */ size += nla_total_size(sizeof(dbt_params->data_bittiming)); - if (dbt_params->data_bittiming_const) /* IFLA_CAN_DATA_BITTIMING_CONST */ + if (dbt_params->data_bittiming_const) /* IFLA_CAN_{,XL}_DATA_BITTIMING_CONST */ size += nla_total_size(sizeof(*dbt_params->data_bittiming_const)); - if (dbt_params->data_bitrate_const) /* IFLA_CAN_DATA_BITRATE_CONST */ + if (dbt_params->data_bitrate_const) /* IFLA_CAN_{,XL}_DATA_BITRATE_CONST */ size += nla_total_size(sizeof(*dbt_params->data_bitrate_const) * dbt_params->data_bitrate_const_cnt); - size += can_tdc_get_size(dbt_params, tdc_flags);/* IFLA_CAN_TDC */ + size += can_tdc_get_size(dbt_params, tdc_flags);/* IFLA_CAN_{,XL}_TDC */ return size; } @@ -607,6 +634,9 @@ static size_t can_get_size(const struct net_device *dev) size += can_data_bittiming_get_size(&priv->fd, priv->ctrlmode & CAN_CTRLMODE_FD_TDC_MASK); + size += can_data_bittiming_get_size(&priv->xl, + priv->ctrlmode & CAN_CTRLMODE_XL_TDC_MASK); + return size; } @@ -651,7 +681,9 @@ static int can_tdc_fill_info(struct sk_buff *skb, const struct net_device *dev, tdc_is_enabled = can_fd_tdc_is_enabled(priv); tdc_manual = priv->ctrlmode & CAN_CTRLMODE_TDC_MANUAL; } else { - return -EOPNOTSUPP; /* Place holder for CAN XL */ + dbt_params = &priv->xl; + tdc_is_enabled = can_xl_tdc_is_enabled(priv); + tdc_manual = priv->ctrlmode & CAN_CTRLMODE_XL_TDC_MANUAL; } tdc_const = dbt_params->tdc_const; tdc = &dbt_params->tdc; @@ -773,7 +805,19 @@ static int can_fill_info(struct sk_buff *skb, const struct net_device *dev) can_tdc_fill_info(skb, dev, IFLA_CAN_TDC) || - can_ctrlmode_ext_fill_info(skb, priv) + can_ctrlmode_ext_fill_info(skb, priv) || + + can_bittiming_fill_info(skb, IFLA_CAN_XL_DATA_BITTIMING, + &priv->xl.data_bittiming) || + + can_bittiming_const_fill_info(skb, IFLA_CAN_XL_DATA_BITTIMING_CONST, + priv->xl.data_bittiming_const) || + + can_bitrate_const_fill_info(skb, IFLA_CAN_XL_DATA_BITRATE_CONST, + priv->xl.data_bitrate_const, + priv->xl.data_bitrate_const_cnt) || + + can_tdc_fill_info(skb, dev, IFLA_CAN_XL_TDC) ) return -EMSGSIZE; diff --git a/include/linux/can/bittiming.h b/include/linux/can/bittiming.h index 3926c78b2222..b6cd2476ffd7 100644 --- a/include/linux/can/bittiming.h +++ b/include/linux/can/bittiming.h @@ -16,10 +16,12 @@ #define CAN_CTRLMODE_FD_TDC_MASK \ (CAN_CTRLMODE_TDC_AUTO | CAN_CTRLMODE_TDC_MANUAL) +#define CAN_CTRLMODE_XL_TDC_MASK \ + (CAN_CTRLMODE_XL_TDC_AUTO | CAN_CTRLMODE_XL_TDC_MANUAL) #define CAN_CTRLMODE_TDC_AUTO_MASK \ - (CAN_CTRLMODE_TDC_AUTO) + (CAN_CTRLMODE_TDC_AUTO | CAN_CTRLMODE_XL_TDC_AUTO) #define CAN_CTRLMODE_TDC_MANUAL_MASK \ - (CAN_CTRLMODE_TDC_MANUAL) + (CAN_CTRLMODE_TDC_MANUAL | CAN_CTRLMODE_XL_TDC_MANUAL) /* * struct can_tdc - CAN FD Transmission Delay Compensation parameters diff --git a/include/linux/can/dev.h b/include/linux/can/dev.h index ab11c0e9111b..f15879bd818d 100644 --- a/include/linux/can/dev.h +++ b/include/linux/can/dev.h @@ -47,7 +47,7 @@ struct can_priv { const struct can_bittiming_const *bittiming_const; struct can_bittiming bittiming; - struct data_bittiming_params fd; + struct data_bittiming_params fd, xl; unsigned int bitrate_const_cnt; const u32 *bitrate_const; u32 bitrate_max; @@ -85,6 +85,11 @@ static inline bool can_fd_tdc_is_enabled(const struct can_priv *priv) return !!(priv->ctrlmode & CAN_CTRLMODE_FD_TDC_MASK); } +static inline bool can_xl_tdc_is_enabled(const struct can_priv *priv) +{ + return !!(priv->ctrlmode & CAN_CTRLMODE_XL_TDC_MASK); +} + static inline u32 can_get_static_ctrlmode(struct can_priv *priv) { return priv->ctrlmode & ~priv->ctrlmode_supported; diff --git a/include/uapi/linux/can/netlink.h b/include/uapi/linux/can/netlink.h index fafd1cce4798..c2c96c5978a8 100644 --- a/include/uapi/linux/can/netlink.h +++ b/include/uapi/linux/can/netlink.h @@ -104,6 +104,9 @@ struct can_ctrlmode { #define CAN_CTRLMODE_TDC_AUTO 0x200 /* FD transceiver automatically calculates TDCV */ #define CAN_CTRLMODE_TDC_MANUAL 0x400 /* FD TDCV is manually set up by user */ #define CAN_CTRLMODE_RESTRICTED 0x800 /* Restricted operation mode */ +#define CAN_CTRLMODE_XL 0x1000 /* CAN XL mode */ +#define CAN_CTRLMODE_XL_TDC_AUTO 0x2000 /* XL transceiver automatically calculates TDCV */ +#define CAN_CTRLMODE_XL_TDC_MANUAL 0x4000 /* XL TDCV is manually set up by user */ /* * CAN device statistics @@ -139,6 +142,10 @@ enum { IFLA_CAN_BITRATE_MAX, IFLA_CAN_TDC, /* FD */ IFLA_CAN_CTRLMODE_EXT, + IFLA_CAN_XL_DATA_BITTIMING, + IFLA_CAN_XL_DATA_BITTIMING_CONST, + IFLA_CAN_XL_DATA_BITRATE_CONST, + IFLA_CAN_XL_TDC, /* add new constants above here */ __IFLA_CAN_MAX, -- cgit v1.2.3 From 233134af208689c2d5d40896f5740473a74e3cb2 Mon Sep 17 00:00:00 2001 From: Vincent Mailhol Date: Wed, 26 Nov 2025 11:16:07 +0100 Subject: can: netlink: add CAN_CTRLMODE_XL_TMS flag The Transceiver Mode Switching (TMS) indicates whether the CAN XL controller shall use the PWM or NRZ encoding during the data phase. The term "transceiver mode switching" is used in both ISO 11898-1 and CiA 612-2 (although only the latter one uses the abbreviation TMS). We adopt the same naming convention here for consistency. Add the CAN_CTRLMODE_XL_TMS flag to the list of the CAN control modes. Add can_validate_xl_flags() to check the coherency of the TMS flag. That function will be reused in upcoming changes to validate the other CAN XL flags. Signed-off-by: Vincent Mailhol Signed-off-by: Oliver Hartkopp Link: https://patch.msgid.link/20251126-canxl-v8-6-e7e3eb74f889@pengutronix.de Signed-off-by: Marc Kleine-Budde --- drivers/net/can/dev/dev.c | 2 ++ drivers/net/can/dev/netlink.c | 48 +++++++++++++++++++++++++++++++++++++--- include/uapi/linux/can/netlink.h | 1 + 3 files changed, 48 insertions(+), 3 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/net/can/dev/dev.c b/drivers/net/can/dev/dev.c index bdec2c52c8ec..091f30e94c61 100644 --- a/drivers/net/can/dev/dev.c +++ b/drivers/net/can/dev/dev.c @@ -123,6 +123,8 @@ const char *can_get_ctrlmode_str(u32 ctrlmode) return "XL-TDC-AUTO"; case CAN_CTRLMODE_XL_TDC_MANUAL: return "XL-TDC-MANUAL"; + case CAN_CTRLMODE_XL_TMS: + return "TMS"; default: return ""; } diff --git a/drivers/net/can/dev/netlink.c b/drivers/net/can/dev/netlink.c index fdd1fa7cf93a..b2c24439abba 100644 --- a/drivers/net/can/dev/netlink.c +++ b/drivers/net/can/dev/netlink.c @@ -181,6 +181,32 @@ static int can_validate_databittiming(struct nlattr *data[], return 0; } +static int can_validate_xl_flags(struct netlink_ext_ack *extack, + u32 masked_flags, u32 mask) +{ + if (masked_flags & CAN_CTRLMODE_XL) { + if (masked_flags & CAN_CTRLMODE_XL_TMS) { + const u32 tms_conflicts_mask = CAN_CTRLMODE_FD | + CAN_CTRLMODE_XL_TDC_MASK; + u32 tms_conflicts = masked_flags & tms_conflicts_mask; + + if (tms_conflicts) { + NL_SET_ERR_MSG_FMT(extack, + "TMS and %s are mutually exclusive", + can_get_ctrlmode_str(tms_conflicts)); + return -EOPNOTSUPP; + } + } + } else { + if (mask & CAN_CTRLMODE_XL_TMS) { + NL_SET_ERR_MSG(extack, "TMS requires CAN XL"); + return -EOPNOTSUPP; + } + } + + return 0; +} + static int can_validate(struct nlattr *tb[], struct nlattr *data[], struct netlink_ext_ack *extack) { @@ -201,6 +227,10 @@ static int can_validate(struct nlattr *tb[], struct nlattr *data[], "LISTEN-ONLY and RESTRICTED modes are mutually exclusive"); return -EOPNOTSUPP; } + + err = can_validate_xl_flags(extack, flags, cm->mask); + if (err) + return err; } err = can_validate_bittiming(data, extack, IFLA_CAN_BITTIMING); @@ -226,7 +256,7 @@ static int can_ctrlmode_changelink(struct net_device *dev, { struct can_priv *priv = netdev_priv(dev); struct can_ctrlmode *cm; - u32 ctrlstatic, maskedflags, notsupp, ctrlstatic_missing; + u32 ctrlstatic, maskedflags, deactivated, notsupp, ctrlstatic_missing; if (!data[IFLA_CAN_CTRLMODE]) return 0; @@ -238,6 +268,7 @@ static int can_ctrlmode_changelink(struct net_device *dev, cm = nla_data(data[IFLA_CAN_CTRLMODE]); ctrlstatic = can_get_static_ctrlmode(priv); maskedflags = cm->flags & cm->mask; + deactivated = ~cm->flags & cm->mask; notsupp = maskedflags & ~(priv->ctrlmode_supported | ctrlstatic); ctrlstatic_missing = (maskedflags & ctrlstatic) ^ ctrlstatic; @@ -259,11 +290,21 @@ static int can_ctrlmode_changelink(struct net_device *dev, return -EOPNOTSUPP; } + /* If FD was active and is not turned off, check for XL conflicts */ + if (priv->ctrlmode & CAN_CTRLMODE_FD & ~deactivated) { + if (maskedflags & CAN_CTRLMODE_XL_TMS) { + NL_SET_ERR_MSG(extack, + "TMS can not be activated while CAN FD is on"); + return -EOPNOTSUPP; + } + } + /* If a top dependency flag is provided, reset all its dependencies */ if (cm->mask & CAN_CTRLMODE_FD) priv->ctrlmode &= ~CAN_CTRLMODE_FD_TDC_MASK; if (cm->mask & CAN_CTRLMODE_XL) - priv->ctrlmode &= ~(CAN_CTRLMODE_XL_TDC_MASK); + priv->ctrlmode &= ~(CAN_CTRLMODE_XL_TDC_MASK | + CAN_CTRLMODE_XL_TMS); /* clear bits to be modified and copy the flag values */ priv->ctrlmode &= ~cm->mask; @@ -395,7 +436,8 @@ static int can_dbt_changelink(struct net_device *dev, struct nlattr *data[], if (data[IFLA_CAN_CTRLMODE]) { struct can_ctrlmode *cm = nla_data(data[IFLA_CAN_CTRLMODE]); - need_tdc_calc = !(cm->mask & tdc_mask); + if (fd || !(priv->ctrlmode & CAN_CTRLMODE_XL_TMS)) + need_tdc_calc = !(cm->mask & tdc_mask); } if (data_tdc) { /* TDC parameters are provided: use them */ diff --git a/include/uapi/linux/can/netlink.h b/include/uapi/linux/can/netlink.h index c2c96c5978a8..ebafb091d80f 100644 --- a/include/uapi/linux/can/netlink.h +++ b/include/uapi/linux/can/netlink.h @@ -107,6 +107,7 @@ struct can_ctrlmode { #define CAN_CTRLMODE_XL 0x1000 /* CAN XL mode */ #define CAN_CTRLMODE_XL_TDC_AUTO 0x2000 /* XL transceiver automatically calculates TDCV */ #define CAN_CTRLMODE_XL_TDC_MANUAL 0x4000 /* XL TDCV is manually set up by user */ +#define CAN_CTRLMODE_XL_TMS 0x8000 /* Transceiver Mode Switching */ /* * CAN device statistics -- cgit v1.2.3 From 46552323fa6779beb1ea558254dfd56021174c93 Mon Sep 17 00:00:00 2001 From: Vincent Mailhol Date: Wed, 26 Nov 2025 11:16:12 +0100 Subject: can: netlink: add PWM netlink interface When the TMS is switched on, the node uses PWM (Pulse Width Modulation) during the data phase instead of the classic NRZ (Non Return to Zero) encoding. PWM is configured by three parameters: - PWMS: Pulse Width Modulation Short phase - PWML: Pulse Width Modulation Long phase - PWMO: Pulse Width Modulation Offset time For each of these parameters, define three IFLA symbols: - IFLA_CAN_PWM_PWM*_MIN: the minimum allowed value. - IFLA_CAN_PWM_PWM*_MAX: the maximum allowed value. - IFLA_CAN_PWM_PWM*: the runtime value. This results in a total of nine IFLA symbols which are all nested in a parent IFLA_CAN_XL_PWM symbol. IFLA_CAN_PWM_PWM*_MIN and IFLA_CAN_PWM_PWM*_MAX define the range of allowed values and will match the value statically configured by the device in struct can_pwm_const. IFLA_CAN_PWM_PWM* match the runtime values stored in struct can_pwm. Those parameters may only be configured when the tms mode is on. If the PWMS, PWML and PWMO parameters are provided, check that all the needed parameters are present using can_validate_pwm(), then check their value using can_validate_pwm_bittiming(). PWMO defaults to zero if omitted. Otherwise, if CAN_CTRLMODE_XL_TMS is true but none of the PWM parameters are provided, calculate them using can_calc_pwm(). Signed-off-by: Vincent Mailhol Signed-off-by: Oliver Hartkopp Link: https://patch.msgid.link/20251126-canxl-v8-11-e7e3eb74f889@pengutronix.de Signed-off-by: Marc Kleine-Budde --- drivers/net/can/dev/netlink.c | 192 ++++++++++++++++++++++++++++++++++++++- include/uapi/linux/can/netlink.h | 25 +++++ 2 files changed, 215 insertions(+), 2 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/net/can/dev/netlink.c b/drivers/net/can/dev/netlink.c index b2c24439abba..d6b0e686fb11 100644 --- a/drivers/net/can/dev/netlink.c +++ b/drivers/net/can/dev/netlink.c @@ -25,6 +25,7 @@ static const struct nla_policy can_policy[IFLA_CAN_MAX + 1] = { [IFLA_CAN_XL_DATA_BITTIMING] = { .len = sizeof(struct can_bittiming) }, [IFLA_CAN_XL_DATA_BITTIMING_CONST] = { .len = sizeof(struct can_bittiming_const) }, [IFLA_CAN_XL_TDC] = { .type = NLA_NESTED }, + [IFLA_CAN_XL_PWM] = { .type = NLA_NESTED }, }; static const struct nla_policy can_tdc_policy[IFLA_CAN_TDC_MAX + 1] = { @@ -39,6 +40,18 @@ static const struct nla_policy can_tdc_policy[IFLA_CAN_TDC_MAX + 1] = { [IFLA_CAN_TDC_TDCF] = { .type = NLA_U32 }, }; +static const struct nla_policy can_pwm_policy[IFLA_CAN_PWM_MAX + 1] = { + [IFLA_CAN_PWM_PWMS_MIN] = { .type = NLA_U32 }, + [IFLA_CAN_PWM_PWMS_MAX] = { .type = NLA_U32 }, + [IFLA_CAN_PWM_PWML_MIN] = { .type = NLA_U32 }, + [IFLA_CAN_PWM_PWML_MAX] = { .type = NLA_U32 }, + [IFLA_CAN_PWM_PWMO_MIN] = { .type = NLA_U32 }, + [IFLA_CAN_PWM_PWMO_MAX] = { .type = NLA_U32 }, + [IFLA_CAN_PWM_PWMS] = { .type = NLA_U32 }, + [IFLA_CAN_PWM_PWML] = { .type = NLA_U32 }, + [IFLA_CAN_PWM_PWMO] = { .type = NLA_U32 }, +}; + static int can_validate_bittiming(struct nlattr *data[], struct netlink_ext_ack *extack, int ifla_can_bittiming) @@ -119,6 +132,40 @@ static int can_validate_tdc(struct nlattr *data_tdc, return 0; } +static int can_validate_pwm(struct nlattr *data[], + struct netlink_ext_ack *extack, u32 flags) +{ + struct nlattr *tb_pwm[IFLA_CAN_PWM_MAX + 1]; + int err; + + if (!data[IFLA_CAN_XL_PWM]) + return 0; + + if (!(flags & CAN_CTRLMODE_XL_TMS)) { + NL_SET_ERR_MSG(extack, "PWM requires TMS"); + return -EOPNOTSUPP; + } + + err = nla_parse_nested(tb_pwm, IFLA_CAN_PWM_MAX, data[IFLA_CAN_XL_PWM], + can_pwm_policy, extack); + if (err) + return err; + + if (!tb_pwm[IFLA_CAN_PWM_PWMS] != !tb_pwm[IFLA_CAN_PWM_PWML]) { + NL_SET_ERR_MSG(extack, + "Provide either both PWMS and PWML, or none for automatic calculation"); + return -EOPNOTSUPP; + } + + if (tb_pwm[IFLA_CAN_PWM_PWMO] && + (!tb_pwm[IFLA_CAN_PWM_PWMS] || !tb_pwm[IFLA_CAN_PWM_PWML])) { + NL_SET_ERR_MSG(extack, "PWMO requires both PWMS and PWML"); + return -EOPNOTSUPP; + } + + return 0; +} + static int can_validate_databittiming(struct nlattr *data[], struct netlink_ext_ack *extack, int ifla_can_data_bittiming, u32 flags) @@ -247,6 +294,10 @@ static int can_validate(struct nlattr *tb[], struct nlattr *data[], if (err) return err; + err = can_validate_pwm(data, extack, flags); + if (err) + return err; + return 0; } @@ -322,6 +373,7 @@ static int can_ctrlmode_changelink(struct net_device *dev, sizeof(priv->fd.data_bittiming)); priv->ctrlmode &= ~CAN_CTRLMODE_XL_TDC_MASK; memset(&priv->xl.tdc, 0, sizeof(priv->xl.tdc)); + memset(&priv->xl.pwm, 0, sizeof(priv->xl.pwm)); } can_set_default_mtu(dev); @@ -468,6 +520,76 @@ static int can_dbt_changelink(struct net_device *dev, struct nlattr *data[], return 0; } +static int can_pwm_changelink(struct net_device *dev, + const struct nlattr *pwm_nla, + struct netlink_ext_ack *extack) +{ + struct can_priv *priv = netdev_priv(dev); + const struct can_pwm_const *pwm_const = priv->xl.pwm_const; + struct nlattr *tb_pwm[IFLA_CAN_PWM_MAX + 1]; + struct can_pwm pwm = { 0 }; + int err; + + if (!(priv->ctrlmode & CAN_CTRLMODE_XL_TMS)) + return 0; + + if (!pwm_const) { + NL_SET_ERR_MSG(extack, "The device does not support PWM"); + return -EOPNOTSUPP; + } + + if (!pwm_nla) + return can_calc_pwm(dev, extack); + + err = nla_parse_nested(tb_pwm, IFLA_CAN_PWM_MAX, pwm_nla, + can_pwm_policy, extack); + if (err) + return err; + + if (tb_pwm[IFLA_CAN_PWM_PWMS]) { + pwm.pwms = nla_get_u32(tb_pwm[IFLA_CAN_PWM_PWMS]); + if (pwm.pwms < pwm_const->pwms_min || + pwm.pwms > pwm_const->pwms_max) { + NL_SET_ERR_MSG_FMT(extack, + "PWMS: %u tqmin is out of range: %u...%u", + pwm.pwms, pwm_const->pwms_min, + pwm_const->pwms_max); + return -EINVAL; + } + } + + if (tb_pwm[IFLA_CAN_PWM_PWML]) { + pwm.pwml = nla_get_u32(tb_pwm[IFLA_CAN_PWM_PWML]); + if (pwm.pwml < pwm_const->pwml_min || + pwm.pwml > pwm_const->pwml_max) { + NL_SET_ERR_MSG_FMT(extack, + "PWML: %u tqmin is out of range: %u...%u", + pwm.pwml, pwm_const->pwml_min, + pwm_const->pwml_max); + return -EINVAL; + } + } + + if (tb_pwm[IFLA_CAN_PWM_PWMO]) { + pwm.pwmo = nla_get_u32(tb_pwm[IFLA_CAN_PWM_PWMO]); + if (pwm.pwmo < pwm_const->pwmo_min || + pwm.pwmo > pwm_const->pwmo_max) { + NL_SET_ERR_MSG_FMT(extack, + "PWMO: %u tqmin is out of range: %u...%u", + pwm.pwmo, pwm_const->pwmo_min, + pwm_const->pwmo_max); + return -EINVAL; + } + } + + err = can_validate_pwm_bittiming(dev, &pwm, extack); + if (err) + return err; + + priv->xl.pwm = pwm; + return 0; +} + static int can_changelink(struct net_device *dev, struct nlattr *tb[], struct nlattr *data[], struct netlink_ext_ack *extack) @@ -559,6 +681,9 @@ static int can_changelink(struct net_device *dev, struct nlattr *tb[], /* CAN XL */ err = can_dbt_changelink(dev, data, false, extack); + if (err) + return err; + err = can_pwm_changelink(dev, data[IFLA_CAN_XL_PWM], extack); if (err) return err; @@ -647,6 +772,30 @@ static size_t can_ctrlmode_ext_get_size(void) nla_total_size(sizeof(u32)); /* IFLA_CAN_CTRLMODE_SUPPORTED */ } +static size_t can_pwm_get_size(const struct can_pwm_const *pwm_const, + bool pwm_on) +{ + size_t size; + + if (!pwm_const || !pwm_on) + return 0; + + size = nla_total_size(0); /* nest IFLA_CAN_PWM */ + + size += nla_total_size(sizeof(u32)); /* IFLA_CAN_PWM_PWMS_MIN */ + size += nla_total_size(sizeof(u32)); /* IFLA_CAN_PWM_PWMS_MAX */ + size += nla_total_size(sizeof(u32)); /* IFLA_CAN_PWM_PWML_MIN */ + size += nla_total_size(sizeof(u32)); /* IFLA_CAN_PWM_PWML_MAX */ + size += nla_total_size(sizeof(u32)); /* IFLA_CAN_PWM_PWMO_MIN */ + size += nla_total_size(sizeof(u32)); /* IFLA_CAN_PWM_PWMO_MAX */ + + size += nla_total_size(sizeof(u32)); /* IFLA_CAN_PWM_PWMS */ + size += nla_total_size(sizeof(u32)); /* IFLA_CAN_PWM_PWML */ + size += nla_total_size(sizeof(u32)); /* IFLA_CAN_PWM_PWMO */ + + return size; +} + static size_t can_get_size(const struct net_device *dev) { struct can_priv *priv = netdev_priv(dev); @@ -678,6 +827,8 @@ static size_t can_get_size(const struct net_device *dev) size += can_data_bittiming_get_size(&priv->xl, priv->ctrlmode & CAN_CTRLMODE_XL_TDC_MASK); + size += can_pwm_get_size(priv->xl.pwm_const, /* IFLA_CAN_XL_PWM */ + priv->ctrlmode & CAN_CTRLMODE_XL_TMS); return size; } @@ -776,6 +927,42 @@ err_cancel: return -EMSGSIZE; } +static int can_pwm_fill_info(struct sk_buff *skb, const struct can_priv *priv) +{ + const struct can_pwm_const *pwm_const = priv->xl.pwm_const; + const struct can_pwm *pwm = &priv->xl.pwm; + struct nlattr *nest; + + if (!pwm_const) + return 0; + + nest = nla_nest_start(skb, IFLA_CAN_XL_PWM); + if (!nest) + return -EMSGSIZE; + + if (nla_put_u32(skb, IFLA_CAN_PWM_PWMS_MIN, pwm_const->pwms_min) || + nla_put_u32(skb, IFLA_CAN_PWM_PWMS_MAX, pwm_const->pwms_max) || + nla_put_u32(skb, IFLA_CAN_PWM_PWML_MIN, pwm_const->pwml_min) || + nla_put_u32(skb, IFLA_CAN_PWM_PWML_MAX, pwm_const->pwml_max) || + nla_put_u32(skb, IFLA_CAN_PWM_PWMO_MIN, pwm_const->pwmo_min) || + nla_put_u32(skb, IFLA_CAN_PWM_PWMO_MAX, pwm_const->pwmo_max)) + goto err_cancel; + + if (priv->ctrlmode & CAN_CTRLMODE_XL_TMS) { + if (nla_put_u32(skb, IFLA_CAN_PWM_PWMS, pwm->pwms) || + nla_put_u32(skb, IFLA_CAN_PWM_PWML, pwm->pwml) || + nla_put_u32(skb, IFLA_CAN_PWM_PWMO, pwm->pwmo)) + goto err_cancel; + } + + nla_nest_end(skb, nest); + return 0; + +err_cancel: + nla_nest_cancel(skb, nest); + return -EMSGSIZE; +} + static int can_ctrlmode_ext_fill_info(struct sk_buff *skb, const struct can_priv *priv) { @@ -859,9 +1046,10 @@ static int can_fill_info(struct sk_buff *skb, const struct net_device *dev) priv->xl.data_bitrate_const, priv->xl.data_bitrate_const_cnt) || - can_tdc_fill_info(skb, dev, IFLA_CAN_XL_TDC) - ) + can_tdc_fill_info(skb, dev, IFLA_CAN_XL_TDC) || + can_pwm_fill_info(skb, priv) + ) return -EMSGSIZE; return 0; diff --git a/include/uapi/linux/can/netlink.h b/include/uapi/linux/can/netlink.h index ebafb091d80f..c30d16746159 100644 --- a/include/uapi/linux/can/netlink.h +++ b/include/uapi/linux/can/netlink.h @@ -5,6 +5,7 @@ * Definitions for the CAN netlink interface * * Copyright (c) 2009 Wolfgang Grandegger + * Copyright (c) 2021-2025 Vincent Mailhol * * This program is free software; you can redistribute it and/or modify * it under the terms of the version 2 of the GNU General Public License @@ -147,6 +148,7 @@ enum { IFLA_CAN_XL_DATA_BITTIMING_CONST, IFLA_CAN_XL_DATA_BITRATE_CONST, IFLA_CAN_XL_TDC, + IFLA_CAN_XL_PWM, /* add new constants above here */ __IFLA_CAN_MAX, @@ -188,6 +190,29 @@ enum { IFLA_CAN_CTRLMODE_MAX = __IFLA_CAN_CTRLMODE - 1 }; +/* + * CAN FD/XL Pulse-Width Modulation (PWM) + * + * Please refer to struct can_pwm_const and can_pwm in + * include/linux/can/bittiming.h for further details. + */ +enum { + IFLA_CAN_PWM_UNSPEC, + IFLA_CAN_PWM_PWMS_MIN, /* u32 */ + IFLA_CAN_PWM_PWMS_MAX, /* u32 */ + IFLA_CAN_PWM_PWML_MIN, /* u32 */ + IFLA_CAN_PWM_PWML_MAX, /* u32 */ + IFLA_CAN_PWM_PWMO_MIN, /* u32 */ + IFLA_CAN_PWM_PWMO_MAX, /* u32 */ + IFLA_CAN_PWM_PWMS, /* u32 */ + IFLA_CAN_PWM_PWML, /* u32 */ + IFLA_CAN_PWM_PWMO, /* u32 */ + + /* add new constants above here */ + __IFLA_CAN_PWM, + IFLA_CAN_PWM_MAX = __IFLA_CAN_PWM - 1 +}; + /* u16 termination range: 1..65535 Ohms */ #define CAN_TERMINATION_DISABLED 0 -- cgit v1.2.3 From f0fdaa4ad55b7c6e46a5ccb9102bc9a96cad360f Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Mon, 27 Oct 2025 21:04:09 -0700 Subject: virt: acrn: split acrn_mmio_dev_res out of acrn_mmiodev Add struct acrn_mmio_dev_res before struct acrn_mmio_dev. The former is used in the latter and breaking them up provides better kernel-doc documentation for the struct members. Suggested-by: Fei Li Signed-off-by: Randy Dunlap Acked-by: Fei Li Link: https://patch.msgid.link/20251028040409.868254-1-rdunlap@infradead.org Signed-off-by: Greg Kroah-Hartman --- include/uapi/linux/acrn.h | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/acrn.h b/include/uapi/linux/acrn.h index 7b714c1902eb..79e7855a8c42 100644 --- a/include/uapi/linux/acrn.h +++ b/include/uapi/linux/acrn.h @@ -418,26 +418,32 @@ struct acrn_pcidev { }; /** - * struct acrn_mmiodev - Info for assigning or de-assigning a MMIO device - * @name: Name of the MMIO device. - * @res[].user_vm_pa: Physical address of User VM of the MMIO region - * for the MMIO device. - * @res[].service_vm_pa: Physical address of Service VM of the MMIO - * region for the MMIO device. - * @res[].size: Size of the MMIO region for the MMIO device. - * @res[].mem_type: Memory type of the MMIO region for the MMIO - * device. + * struct acrn_mmio_dev_res - MMIO device resource description + * @user_vm_pa: Physical address of User VM of the MMIO region + * for the MMIO device. + * @service_vm_pa: Physical address of Service VM of the MMIO + * region for the MMIO device. + * @size: Size of the MMIO region for the MMIO device. + * @mem_type: Memory type of the MMIO region for the MMIO + * device. + */ +struct acrn_mmio_dev_res { + __u64 user_vm_pa; + __u64 service_vm_pa; + __u64 size; + __u64 mem_type; +}; + +/** + * struct acrn_mmiodev - Info for assigning or de-assigning an MMIO device + * @name: Name of the MMIO device. + * @res: Array of MMIO device descriptions * * This structure will be passed to hypervisor directly. */ struct acrn_mmiodev { __u8 name[8]; - struct { - __u64 user_vm_pa; - __u64 service_vm_pa; - __u64 size; - __u64 mem_type; - } res[ACRN_MMIODEV_RES_NUM]; + struct acrn_mmio_dev_res res[ACRN_MMIODEV_RES_NUM]; }; /** -- cgit v1.2.3 From 81c45c62dc3eefd83af8eb8df10e45705e8e3a47 Mon Sep 17 00:00:00 2001 From: Nicolin Chen Date: Mon, 3 Nov 2025 09:27:55 -0800 Subject: iommu/arm-smmu-v3-iommufd: Allow attaching nested domain for GBPA cases A vDEVICE has been a hard requirement for attaching a nested domain to the device. This makes sense when installing a guest STE, since a vSID must be present and given to the kernel during the vDEVICE allocation. But, when CR0.SMMUEN is disabled, VM doesn't really need a vSID to program the vSMMU behavior as GBPA will take effect, in which case the vSTE in the nested domain could have carried the bypass or abort configuration in GBPA register. Thus, having such a hard requirement doesn't work well for GBPA. Skip vmaster allocation in arm_smmu_attach_prepare_vmaster() for an abort or bypass vSTE. Note that device on this attachment won't report vevents. Update the uAPI doc accordingly. Link: https://patch.msgid.link/r/20251103172755.2026145-1-nicolinc@nvidia.com Tested-by: Shameer Kolothum Signed-off-by: Nicolin Chen Reviewed-by: Jason Gunthorpe Reviewed-by: Pranjal Shrivastava Tested-by: Shuai Xue Signed-off-by: Jason Gunthorpe --- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c | 13 ++++++++++++- include/uapi/linux/iommufd.h | 10 ++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c index 8cd8929bbfdf..e5fbbdbdea24 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c @@ -99,6 +99,8 @@ static void arm_smmu_make_nested_domain_ste( int arm_smmu_attach_prepare_vmaster(struct arm_smmu_attach_state *state, struct arm_smmu_nested_domain *nested_domain) { + unsigned int cfg = + FIELD_GET(STRTAB_STE_0_CFG, le64_to_cpu(nested_domain->ste[0])); struct arm_smmu_vmaster *vmaster; unsigned long vsid; int ret; @@ -107,8 +109,17 @@ int arm_smmu_attach_prepare_vmaster(struct arm_smmu_attach_state *state, ret = iommufd_viommu_get_vdev_id(&nested_domain->vsmmu->core, state->master->dev, &vsid); - if (ret) + /* + * Attaching to a translate nested domain must allocate a vDEVICE prior, + * as CD/ATS invalidations and vevents require a vSID to work properly. + * A abort/bypass domain is allowed to attach w/o vmaster for GBPA case. + */ + if (ret) { + if (cfg == STRTAB_STE_0_CFG_ABORT || + cfg == STRTAB_STE_0_CFG_BYPASS) + return 0; return ret; + } vmaster = kzalloc(sizeof(*vmaster), GFP_KERNEL); if (!vmaster) diff --git a/include/uapi/linux/iommufd.h b/include/uapi/linux/iommufd.h index c218c89e0e2e..2c41920b641d 100644 --- a/include/uapi/linux/iommufd.h +++ b/include/uapi/linux/iommufd.h @@ -450,6 +450,16 @@ struct iommu_hwpt_vtd_s1 { * nested domain will translate the same as the nesting parent. The S1 will * install a Context Descriptor Table pointing at userspace memory translated * by the nesting parent. + * + * It's suggested to allocate a vDEVICE object carrying vSID and then re-attach + * the nested domain, as soon as the vSID is available in the VMM level: + * + * - when Cfg=translate, a vDEVICE must be allocated prior to attaching to the + * allocated nested domain, as CD/ATS invalidations and vevents need a vSID. + * - when Cfg=bypass/abort, a vDEVICE is not enforced during the nested domain + * attachment, to support a GBPA case where VM sets CR0.SMMUEN=0. However, if + * VM sets CR0.SMMUEN=1 while missing a vDEVICE object, kernel would fail to + * report events to the VM. E.g. F_TRANSLATION when guest STE.Cfg=abort. */ struct iommu_hwpt_arm_smmuv3 { __aligned_le64 ste[2]; -- cgit v1.2.3 From 5d24321e4c159088604512d7a5c5cf634d23e01a Mon Sep 17 00:00:00 2001 From: Gabriel Krisman Bertazi Date: Tue, 25 Nov 2025 16:18:01 -0500 Subject: io_uring: Introduce getsockname io_uring cmd Introduce a socket-specific io_uring_cmd to support getsockname/getpeername via io_uring. I made this an io_uring_cmd instead of a new operation to avoid polluting the command namespace with what is exclusively a socket operation. In addition, since we don't need to conform to existing interfaces, this merges the getsockname/getpeername in a single operation, since the implementation is pretty much the same. This has been frequently requested, for instance at [1] and more recently in the project Discord channel. The main use-case is to support fixed socket file descriptors. [1] https://github.com/axboe/liburing/issues/1356 Signed-off-by: Gabriel Krisman Bertazi Signed-off-by: Jens Axboe --- include/uapi/linux/io_uring.h | 1 + io_uring/cmd_net.c | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/io_uring.h b/include/uapi/linux/io_uring.h index deb772222b6d..b5b23c0d5283 100644 --- a/include/uapi/linux/io_uring.h +++ b/include/uapi/linux/io_uring.h @@ -1009,6 +1009,7 @@ enum io_uring_socket_op { SOCKET_URING_OP_GETSOCKOPT, SOCKET_URING_OP_SETSOCKOPT, SOCKET_URING_OP_TX_TIMESTAMP, + SOCKET_URING_OP_GETSOCKNAME, }; /* diff --git a/io_uring/cmd_net.c b/io_uring/cmd_net.c index 27a09aa4c9d0..5d11caf5509c 100644 --- a/io_uring/cmd_net.c +++ b/io_uring/cmd_net.c @@ -132,6 +132,26 @@ static int io_uring_cmd_timestamp(struct socket *sock, return -EAGAIN; } +static int io_uring_cmd_getsockname(struct socket *sock, + struct io_uring_cmd *cmd, + unsigned int issue_flags) +{ + const struct io_uring_sqe *sqe = cmd->sqe; + struct sockaddr __user *uaddr; + unsigned int peer; + int __user *ulen; + + if (sqe->ioprio || sqe->__pad1 || sqe->len || sqe->rw_flags) + return -EINVAL; + + uaddr = u64_to_user_ptr(READ_ONCE(sqe->addr)); + ulen = u64_to_user_ptr(sqe->addr3); + peer = READ_ONCE(sqe->optlen); + if (peer > 1) + return -EINVAL; + return do_getsockname(sock, peer, uaddr, ulen); +} + int io_uring_cmd_sock(struct io_uring_cmd *cmd, unsigned int issue_flags) { struct socket *sock = cmd->file->private_data; @@ -159,6 +179,8 @@ int io_uring_cmd_sock(struct io_uring_cmd *cmd, unsigned int issue_flags) return io_uring_cmd_setsockopt(sock, cmd, issue_flags); case SOCKET_URING_OP_TX_TIMESTAMP: return io_uring_cmd_timestamp(sock, cmd, issue_flags); + case SOCKET_URING_OP_GETSOCKNAME: + return io_uring_cmd_getsockname(sock, cmd, issue_flags); default: return -EOPNOTSUPP; } -- cgit v1.2.3 From e6c43c95009035a63091cd49736886f883127510 Mon Sep 17 00:00:00 2001 From: Alexander Duyck Date: Fri, 21 Nov 2025 08:39:55 -0800 Subject: net: phy: Add MDIO_PMA_CTRL1_SPEED for 2.5G and 5G to reflect PMA values The 2.5G and 5G values are not consistent between the PCS CTRL1 and PMA CTRL1 values. In order to avoid confusion between the two I am updating the values to include "PMA" in the name similar to values used in similar places. To avoid breaking UAPI I have retained the original macros and just defined them as the new PMA based defines. Signed-off-by: Alexander Duyck Link: https://patch.msgid.link/176374319569.959489.6610469879021800710.stgit@ahduyck-xeon-server.home.arpa Signed-off-by: Paolo Abeni --- drivers/net/phy/phy-c45.c | 8 ++++---- include/uapi/linux/mdio.h | 12 ++++++++++-- 2 files changed, 14 insertions(+), 6 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/net/phy/phy-c45.c b/drivers/net/phy/phy-c45.c index e8e5be4684ab..f5e23b53994f 100644 --- a/drivers/net/phy/phy-c45.c +++ b/drivers/net/phy/phy-c45.c @@ -148,12 +148,12 @@ int genphy_c45_pma_setup_forced(struct phy_device *phydev) ctrl2 |= MDIO_PMA_CTRL2_1000BT; break; case SPEED_2500: - ctrl1 |= MDIO_CTRL1_SPEED2_5G; + ctrl1 |= MDIO_PMA_CTRL1_SPEED2_5G; /* Assume 2.5Gbase-T */ ctrl2 |= MDIO_PMA_CTRL2_2_5GBT; break; case SPEED_5000: - ctrl1 |= MDIO_CTRL1_SPEED5G; + ctrl1 |= MDIO_PMA_CTRL1_SPEED5G; /* Assume 5Gbase-T */ ctrl2 |= MDIO_PMA_CTRL2_5GBT; break; @@ -618,10 +618,10 @@ int genphy_c45_read_pma(struct phy_device *phydev) case MDIO_PMA_CTRL1_SPEED1000: phydev->speed = SPEED_1000; break; - case MDIO_CTRL1_SPEED2_5G: + case MDIO_PMA_CTRL1_SPEED2_5G: phydev->speed = SPEED_2500; break; - case MDIO_CTRL1_SPEED5G: + case MDIO_PMA_CTRL1_SPEED5G: phydev->speed = SPEED_5000; break; case MDIO_CTRL1_SPEED10G: diff --git a/include/uapi/linux/mdio.h b/include/uapi/linux/mdio.h index 6975f182b22c..9ee6eeae64b8 100644 --- a/include/uapi/linux/mdio.h +++ b/include/uapi/linux/mdio.h @@ -116,10 +116,18 @@ #define MDIO_CTRL1_SPEED10G (MDIO_CTRL1_SPEEDSELEXT | 0x00) /* 10PASS-TS/2BASE-TL */ #define MDIO_CTRL1_SPEED10P2B (MDIO_CTRL1_SPEEDSELEXT | 0x04) +/* Note: the MDIO_CTRL1_SPEED_XXX values for everything past 10PASS-TS/2BASE-TL + * do not match between the PCS and PMA values. Any additions past this point + * should be PMA or PCS specific. The following 2 defines are workarounds for + * values added before this was caught. They should be considered deprecated. + */ +#define MDIO_CTRL1_SPEED2_5G MDIO_PMA_CTRL1_SPEED2_5G +#define MDIO_CTRL1_SPEED5G MDIO_PMA_CTRL1_SPEED5G /* 2.5 Gb/s */ -#define MDIO_CTRL1_SPEED2_5G (MDIO_CTRL1_SPEEDSELEXT | 0x18) +#define MDIO_PMA_CTRL1_SPEED2_5G (MDIO_CTRL1_SPEEDSELEXT | 0x18) /* 5 Gb/s */ -#define MDIO_CTRL1_SPEED5G (MDIO_CTRL1_SPEEDSELEXT | 0x1c) +#define MDIO_PMA_CTRL1_SPEED5G (MDIO_CTRL1_SPEEDSELEXT | 0x1c) + /* Status register 1. */ #define MDIO_STAT1_LPOWERABLE 0x0002 /* Low-power ability */ -- cgit v1.2.3 From 7622d55276932bfeb947b7b6cbf7ea0aa41feeb8 Mon Sep 17 00:00:00 2001 From: Alexander Duyck Date: Fri, 21 Nov 2025 08:40:02 -0800 Subject: net: pcs: xpcs: Add support for 25G, 50G, and 100G interfaces With this change we are adding support for 25G, 50G, and 100G interface types to the XPCS driver. This had supposedly been enabled with the addition of XLGMII but I don't see any capability for configuration there so I suspect it may need to be refactored in the future. With this change we can enable the XPCS driver with the selected interface and it should be able to detect link, speed, and report the link status to the phylink interface. Signed-off-by: Alexander Duyck Link: https://patch.msgid.link/176374320248.959489.11649590675011158859.stgit@ahduyck-xeon-server.home.arpa Signed-off-by: Paolo Abeni --- drivers/net/pcs/pcs-xpcs.c | 105 +++++++++++++++++++++++++++++++++++++++++++-- include/uapi/linux/mdio.h | 6 +++ 2 files changed, 107 insertions(+), 4 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c index 3d1bd5aac093..9fb9d4fd2a5b 100644 --- a/drivers/net/pcs/pcs-xpcs.c +++ b/drivers/net/pcs/pcs-xpcs.c @@ -37,6 +37,16 @@ static const int xpcs_10gkr_features[] = { __ETHTOOL_LINK_MODE_MASK_NBITS, }; +static const int xpcs_25gbaser_features[] = { + ETHTOOL_LINK_MODE_MII_BIT, + ETHTOOL_LINK_MODE_Pause_BIT, + ETHTOOL_LINK_MODE_Asym_Pause_BIT, + ETHTOOL_LINK_MODE_25000baseCR_Full_BIT, + ETHTOOL_LINK_MODE_25000baseKR_Full_BIT, + ETHTOOL_LINK_MODE_25000baseSR_Full_BIT, + __ETHTOOL_LINK_MODE_MASK_NBITS, +}; + static const int xpcs_xlgmii_features[] = { ETHTOOL_LINK_MODE_Pause_BIT, ETHTOOL_LINK_MODE_Asym_Pause_BIT, @@ -67,6 +77,40 @@ static const int xpcs_xlgmii_features[] = { __ETHTOOL_LINK_MODE_MASK_NBITS, }; +static const int xpcs_50gbaser_features[] = { + ETHTOOL_LINK_MODE_MII_BIT, + ETHTOOL_LINK_MODE_Pause_BIT, + ETHTOOL_LINK_MODE_Asym_Pause_BIT, + ETHTOOL_LINK_MODE_50000baseKR_Full_BIT, + ETHTOOL_LINK_MODE_50000baseSR_Full_BIT, + ETHTOOL_LINK_MODE_50000baseCR_Full_BIT, + ETHTOOL_LINK_MODE_50000baseLR_ER_FR_Full_BIT, + ETHTOOL_LINK_MODE_50000baseDR_Full_BIT, + __ETHTOOL_LINK_MODE_MASK_NBITS, +}; + +static const int xpcs_50gbaser2_features[] = { + ETHTOOL_LINK_MODE_MII_BIT, + ETHTOOL_LINK_MODE_Pause_BIT, + ETHTOOL_LINK_MODE_Asym_Pause_BIT, + ETHTOOL_LINK_MODE_50000baseCR2_Full_BIT, + ETHTOOL_LINK_MODE_50000baseKR2_Full_BIT, + ETHTOOL_LINK_MODE_50000baseSR2_Full_BIT, + __ETHTOOL_LINK_MODE_MASK_NBITS, +}; + +static const int xpcs_100gbasep_features[] = { + ETHTOOL_LINK_MODE_MII_BIT, + ETHTOOL_LINK_MODE_Pause_BIT, + ETHTOOL_LINK_MODE_Asym_Pause_BIT, + ETHTOOL_LINK_MODE_100000baseKR2_Full_BIT, + ETHTOOL_LINK_MODE_100000baseSR2_Full_BIT, + ETHTOOL_LINK_MODE_100000baseCR2_Full_BIT, + ETHTOOL_LINK_MODE_100000baseLR2_ER2_FR2_Full_BIT, + ETHTOOL_LINK_MODE_100000baseDR2_Full_BIT, + __ETHTOOL_LINK_MODE_MASK_NBITS, +}; + static const int xpcs_10gbaser_features[] = { ETHTOOL_LINK_MODE_Pause_BIT, ETHTOOL_LINK_MODE_Asym_Pause_BIT, @@ -523,9 +567,38 @@ static int xpcs_get_max_xlgmii_speed(struct dw_xpcs *xpcs, return speed; } -static void xpcs_resolve_pma(struct dw_xpcs *xpcs, - struct phylink_link_state *state) +static int xpcs_c45_read_pcs_speed(struct dw_xpcs *xpcs, + struct phylink_link_state *state) { + int pcs_ctrl1; + + pcs_ctrl1 = xpcs_read(xpcs, MDIO_MMD_PCS, MDIO_CTRL1); + if (pcs_ctrl1 < 0) + return pcs_ctrl1; + + switch (pcs_ctrl1 & MDIO_CTRL1_SPEEDSEL) { + case MDIO_PCS_CTRL1_SPEED25G: + state->speed = SPEED_25000; + break; + case MDIO_PCS_CTRL1_SPEED50G: + state->speed = SPEED_50000; + break; + case MDIO_PCS_CTRL1_SPEED100G: + state->speed = SPEED_100000; + break; + default: + state->speed = SPEED_UNKNOWN; + break; + } + + return 0; +} + +static int xpcs_resolve_pma(struct dw_xpcs *xpcs, + struct phylink_link_state *state) +{ + int err = 0; + state->pause = MLO_PAUSE_TX | MLO_PAUSE_RX; state->duplex = DUPLEX_FULL; @@ -536,10 +609,18 @@ static void xpcs_resolve_pma(struct dw_xpcs *xpcs, case PHY_INTERFACE_MODE_XLGMII: state->speed = xpcs_get_max_xlgmii_speed(xpcs, state); break; + case PHY_INTERFACE_MODE_100GBASEP: + case PHY_INTERFACE_MODE_LAUI: + case PHY_INTERFACE_MODE_50GBASER: + case PHY_INTERFACE_MODE_25GBASER: + err = xpcs_c45_read_pcs_speed(xpcs, state); + break; default: state->speed = SPEED_UNKNOWN; break; } + + return err; } static int xpcs_validate(struct phylink_pcs *pcs, unsigned long *supported, @@ -945,10 +1026,10 @@ static int xpcs_get_state_c73(struct dw_xpcs *xpcs, phylink_resolve_c73(state); } else { - xpcs_resolve_pma(xpcs, state); + ret = xpcs_resolve_pma(xpcs, state); } - return 0; + return ret; } static int xpcs_get_state_c37_sgmii(struct dw_xpcs *xpcs, @@ -1312,10 +1393,26 @@ static const struct dw_xpcs_compat synopsys_xpcs_compat[] = { .interface = PHY_INTERFACE_MODE_10GKR, .supported = xpcs_10gkr_features, .an_mode = DW_AN_C73, + }, { + .interface = PHY_INTERFACE_MODE_25GBASER, + .supported = xpcs_25gbaser_features, + .an_mode = DW_AN_C73, }, { .interface = PHY_INTERFACE_MODE_XLGMII, .supported = xpcs_xlgmii_features, .an_mode = DW_AN_C73, + }, { + .interface = PHY_INTERFACE_MODE_50GBASER, + .supported = xpcs_50gbaser_features, + .an_mode = DW_AN_C73, + }, { + .interface = PHY_INTERFACE_MODE_LAUI, + .supported = xpcs_50gbaser2_features, + .an_mode = DW_AN_C73, + }, { + .interface = PHY_INTERFACE_MODE_100GBASEP, + .supported = xpcs_100gbasep_features, + .an_mode = DW_AN_C73, }, { .interface = PHY_INTERFACE_MODE_10GBASER, .supported = xpcs_10gbaser_features, diff --git a/include/uapi/linux/mdio.h b/include/uapi/linux/mdio.h index 9ee6eeae64b8..f23cab33e586 100644 --- a/include/uapi/linux/mdio.h +++ b/include/uapi/linux/mdio.h @@ -123,6 +123,12 @@ */ #define MDIO_CTRL1_SPEED2_5G MDIO_PMA_CTRL1_SPEED2_5G #define MDIO_CTRL1_SPEED5G MDIO_PMA_CTRL1_SPEED5G +/* 100 Gb/s */ +#define MDIO_PCS_CTRL1_SPEED100G (MDIO_CTRL1_SPEEDSELEXT | 0x10) +/* 25 Gb/s */ +#define MDIO_PCS_CTRL1_SPEED25G (MDIO_CTRL1_SPEEDSELEXT | 0x14) +/* 50 Gb/s */ +#define MDIO_PCS_CTRL1_SPEED50G (MDIO_CTRL1_SPEEDSELEXT | 0x18) /* 2.5 Gb/s */ #define MDIO_PMA_CTRL1_SPEED2_5G (MDIO_CTRL1_SPEEDSELEXT | 0x18) /* 5 Gb/s */ -- cgit v1.2.3 From 39e138173ae7641e952b456d2de7ad2ac03e8d88 Mon Sep 17 00:00:00 2001 From: Alexander Duyck Date: Fri, 21 Nov 2025 08:40:09 -0800 Subject: net: pcs: xpcs: Fix PMA identifier handling in XPCS The XPCS driver was mangling the PMA identifier as the original code appears to have been focused on just capturing the OUI. Rather than store a mangled ID it is better to work with the actual PMA ID and instead just mask out the values that don't apply rather than shifting them and reordering them as you still don't get the original OUI for the NIC without having to bitswap the values as per the definition of the layout in IEEE 802.3-2022 22.2.4.3.1. By laying it out as it was in the hardware it is also less likely for us to have an unintentional collision as the enum values will occupy the revision number area while the OUI occupies the upper 22 bits. Signed-off-by: Alexander Duyck Link: https://patch.msgid.link/176374320920.959489.17267159479370601070.stgit@ahduyck-xeon-server.home.arpa Signed-off-by: Paolo Abeni --- drivers/net/pcs/pcs-xpcs.c | 9 ++++----- include/linux/pcs/pcs-xpcs.h | 2 +- include/uapi/linux/mdio.h | 5 +++++ 3 files changed, 10 insertions(+), 6 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c index 9fb9d4fd2a5b..a94a7cb93664 100644 --- a/drivers/net/pcs/pcs-xpcs.c +++ b/drivers/net/pcs/pcs-xpcs.c @@ -1365,17 +1365,16 @@ static int xpcs_read_ids(struct dw_xpcs *xpcs) if (ret < 0) return ret; - id = ret; + id = ret << 16; ret = xpcs_read(xpcs, MDIO_MMD_PMAPMD, MDIO_DEVID2); if (ret < 0) return ret; - /* Note the inverted dword order and masked out Model/Revision numbers - * with respect to what is done with the PCS ID... + /* For now we only record the OUI for the PMAPMD, we may want to + * add the model number at some point in the future. */ - ret = (ret >> 10) & 0x3F; - id |= ret << 16; + id |= ret & MDIO_DEVID2_OUI; /* Set the PMA ID if it hasn't been pre-initialized */ if (xpcs->info.pma == DW_XPCS_PMA_ID_NATIVE) diff --git a/include/linux/pcs/pcs-xpcs.h b/include/linux/pcs/pcs-xpcs.h index e40f554ff717..4cf6bd611e5a 100644 --- a/include/linux/pcs/pcs-xpcs.h +++ b/include/linux/pcs/pcs-xpcs.h @@ -38,7 +38,7 @@ enum dw_xpcs_pma_id { DW_XPCS_PMA_GEN4_6G_ID, DW_XPCS_PMA_GEN5_10G_ID, DW_XPCS_PMA_GEN5_12G_ID, - WX_TXGBE_XPCS_PMA_10G_ID = 0x0018fc80, + WX_TXGBE_XPCS_PMA_10G_ID = 0xfc806000, }; struct dw_xpcs_info { diff --git a/include/uapi/linux/mdio.h b/include/uapi/linux/mdio.h index f23cab33e586..8d769f100de6 100644 --- a/include/uapi/linux/mdio.h +++ b/include/uapi/linux/mdio.h @@ -147,6 +147,11 @@ #define MDIO_AN_STAT1_PAGE 0x0040 /* Page received */ #define MDIO_AN_STAT1_XNP 0x0080 /* Extended next page status */ +/* Device Identifier 2 */ +#define MDIO_DEVID2_OUI 0xfc00 /* OUI Portion of PHY ID */ +#define MDIO_DEVID2_MODEL_NUM 0x03f0 /* Manufacturer's Model Number */ +#define MDIO_DEVID2_REV_NUM 0x000f /* Revision Number */ + /* Speed register. */ #define MDIO_SPEED_10G 0x0001 /* 10G capable */ #define MDIO_PMA_SPEED_2B 0x0002 /* 2BASE-TL capable */ -- cgit v1.2.3 From 9e2fd062fa1713a33380cc97ef324d086dd45ba5 Mon Sep 17 00:00:00 2001 From: Pasha Tatashin Date: Tue, 25 Nov 2025 11:58:31 -0500 Subject: liveupdate: luo_core: Live Update Orchestrator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Patch series "Live Update Orchestrator", v8. This series introduces the Live Update Orchestrator, a kernel subsystem designed to facilitate live kernel updates using a kexec-based reboot. This capability is critical for cloud environments, allowing hypervisors to be updated with minimal downtime for running virtual machines. LUO achieves this by preserving the state of selected resources, such as memory, devices and their dependencies, across the kernel transition. As a key feature, this series includes support for preserving memfd file descriptors, which allows critical in-memory data, such as guest RAM or any other large memory region, to be maintained in RAM across the kexec reboot. The other series that use LUO, are VFIO [1], IOMMU [2], and PCI [3] preservations. Github repo of this series [4]. The core of LUO is a framework for managing the lifecycle of preserved resources through a userspace-driven interface. Key features include: - Session Management Userspace agent (i.e. luod [5]) creates named sessions, each represented by a file descriptor (via centralized agent that controls /dev/liveupdate). The lifecycle of all preserved resources within a session is tied to this FD, ensuring automatic kernel cleanup if the controlling userspace agent crashes or exits unexpectedly. - File Preservation A handler-based framework allows specific file types (demonstrated here with memfd) to be preserved. Handlers manage the serialization, restoration, and lifecycle of their specific file types. - File-Lifecycle-Bound State A new mechanism for managing shared global state whose lifecycle is tied to the preservation of one or more files. This is crucial for subsystems like IOMMU or HugeTLB, where multiple file descriptors may depend on a single, shared underlying resource that must be preserved only once. - KHO Integration LUO drives the Kexec Handover framework programmatically to pass its serialized metadata to the next kernel. The LUO state is finalized and added to the kexec image just before the reboot is triggered. In the future this step will also be removed once stateless KHO is merged [6]. - Userspace Interface Control is provided via ioctl commands on /dev/liveupdate for creating and retrieving sessions, as well as on session file descriptors for managing individual files. - Testing The series includes a set of selftests, including userspace API validation, kexec-based lifecycle tests for various session and file scenarios, and a new in-kernel test module to validate the FLB logic. Introduce LUO, a mechanism intended to facilitate kernel updates while keeping designated devices operational across the transition (e.g., via kexec). The primary use case is updating hypervisors with minimal disruption to running virtual machines. For userspace side of hypervisor update we have copyless migration. LUO is for updating the kernel. This initial patch lays the groundwork for the LUO subsystem. Further functionality, including the implementation of state transition logic, integration with KHO, and hooks for subsystems and file descriptors, will be added in subsequent patches. Create a character device at /dev/liveupdate. A new uAPI header, , will define the necessary structures. The magic number for IOCTL is registered in Documentation/userspace-api/ioctl/ioctl-number.rst. Link: https://lkml.kernel.org/r/20251125165850.3389713-1-pasha.tatashin@soleen.com Link: https://lkml.kernel.org/r/20251125165850.3389713-2-pasha.tatashin@soleen.com Link: https://lore.kernel.org/all/20251018000713.677779-1-vipinsh@google.com/ [1] Link: https://lore.kernel.org/linux-iommu/20250928190624.3735830-1-skhawaja@google.com [2] Link: https://lore.kernel.org/linux-pci/20250916-luo-pci-v2-0-c494053c3c08@kernel.org [3] Link: https://github.com/googleprodkernel/linux-liveupdate/tree/luo/v8 [4] Link: https://tinyurl.com/luoddesign [5] Link: https://lore.kernel.org/all/20251020100306.2709352-1-jasonmiu@google.com [6] Link: https://lore.kernel.org/all/20251115233409.768044-1-pasha.tatashin@soleen.com [7] Link: https://github.com/soleen/linux/blob/luo/v8b03/diff.v7.v8 [8] Signed-off-by: Pasha Tatashin Reviewed-by: Pratyush Yadav Reviewed-by: Mike Rapoport (Microsoft) Tested-by: David Matlack Cc: Aleksander Lobakin Cc: Alexander Graf Cc: Alice Ryhl Cc: Andriy Shevchenko Cc: anish kumar Cc: Anna Schumaker Cc: Bartosz Golaszewski Cc: Bjorn Helgaas Cc: Borislav Betkov Cc: Chanwoo Choi Cc: Chen Ridong Cc: Chris Li Cc: Christian Brauner Cc: Daniel Wagner Cc: Danilo Krummrich Cc: Dan Williams Cc: David Hildenbrand Cc: David Jeffery Cc: David Rientjes Cc: Greg Kroah-Hartman Cc: Guixin Liu Cc: "H. Peter Anvin" Cc: Hugh Dickins Cc: Ilpo Järvinen Cc: Ingo Molnar Cc: Ira Weiny Cc: Jann Horn Cc: Jason Gunthorpe Cc: Jens Axboe Cc: Joanthan Cameron Cc: Joel Granados Cc: Johannes Weiner Cc: Jonathan Corbet Cc: Lennart Poettering Cc: Leon Romanovsky Cc: Leon Romanovsky Cc: Lukas Wunner Cc: Marc Rutland Cc: Masahiro Yamada Cc: Matthew Maurer Cc: Miguel Ojeda Cc: Myugnjoo Ham Cc: Parav Pandit Cc: Randy Dunlap Cc: Roman Gushchin Cc: Saeed Mahameed Cc: Samiullah Khawaja Cc: Song Liu Cc: Steven Rostedt Cc: Stuart Hayes Cc: Tejun Heo Cc: Thomas Gleinxer Cc: Thomas Weißschuh Cc: Vincent Guittot Cc: William Tu Cc: Yoann Congal Cc: Zijun Hu Cc: Pratyush Yadav Cc: Zhu Yanjun Signed-off-by: Andrew Morton --- Documentation/userspace-api/ioctl/ioctl-number.rst | 2 + include/linux/liveupdate.h | 35 +++++++ include/uapi/linux/liveupdate.h | 46 +++++++++ kernel/liveupdate/Kconfig | 21 ++++ kernel/liveupdate/Makefile | 5 + kernel/liveupdate/luo_core.c | 111 +++++++++++++++++++++ 6 files changed, 220 insertions(+) create mode 100644 include/linux/liveupdate.h create mode 100644 include/uapi/linux/liveupdate.h create mode 100644 kernel/liveupdate/luo_core.c (limited to 'include/uapi/linux') diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst index 7c527a01d1cf..7232b3544cec 100644 --- a/Documentation/userspace-api/ioctl/ioctl-number.rst +++ b/Documentation/userspace-api/ioctl/ioctl-number.rst @@ -385,6 +385,8 @@ Code Seq# Include File Comments 0xB8 01-02 uapi/misc/mrvl_cn10k_dpi.h Marvell CN10K DPI driver 0xB8 all uapi/linux/mshv.h Microsoft Hyper-V /dev/mshv driver +0xBA 00-0F uapi/linux/liveupdate.h Pasha Tatashin + 0xC0 00-0F linux/usb/iowarrior.h 0xCA 00-0F uapi/misc/cxl.h Dead since 6.15 0xCA 10-2F uapi/misc/ocxl.h diff --git a/include/linux/liveupdate.h b/include/linux/liveupdate.h new file mode 100644 index 000000000000..c6a1d6bd90cb --- /dev/null +++ b/include/linux/liveupdate.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* + * Copyright (c) 2025, Google LLC. + * Pasha Tatashin + */ +#ifndef _LINUX_LIVEUPDATE_H +#define _LINUX_LIVEUPDATE_H + +#include +#include +#include + +#ifdef CONFIG_LIVEUPDATE + +/* Return true if live update orchestrator is enabled */ +bool liveupdate_enabled(void); + +/* Called during kexec to tell LUO that entered into reboot */ +int liveupdate_reboot(void); + +#else /* CONFIG_LIVEUPDATE */ + +static inline bool liveupdate_enabled(void) +{ + return false; +} + +static inline int liveupdate_reboot(void) +{ + return 0; +} + +#endif /* CONFIG_LIVEUPDATE */ +#endif /* _LINUX_LIVEUPDATE_H */ diff --git a/include/uapi/linux/liveupdate.h b/include/uapi/linux/liveupdate.h new file mode 100644 index 000000000000..df34c1642c4d --- /dev/null +++ b/include/uapi/linux/liveupdate.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ + +/* + * Userspace interface for /dev/liveupdate + * Live Update Orchestrator + * + * Copyright (c) 2025, Google LLC. + * Pasha Tatashin + */ + +#ifndef _UAPI_LIVEUPDATE_H +#define _UAPI_LIVEUPDATE_H + +#include +#include + +/** + * DOC: General ioctl format + * + * The ioctl interface follows a general format to allow for extensibility. Each + * ioctl is passed in a structure pointer as the argument providing the size of + * the structure in the first u32. The kernel checks that any structure space + * beyond what it understands is 0. This allows userspace to use the backward + * compatible portion while consistently using the newer, larger, structures. + * + * ioctls use a standard meaning for common errnos: + * + * - ENOTTY: The IOCTL number itself is not supported at all + * - E2BIG: The IOCTL number is supported, but the provided structure has + * non-zero in a part the kernel does not understand. + * - EOPNOTSUPP: The IOCTL number is supported, and the structure is + * understood, however a known field has a value the kernel does not + * understand or support. + * - EINVAL: Everything about the IOCTL was understood, but a field is not + * correct. + * - ENOENT: A provided token does not exist. + * - ENOMEM: Out of memory. + * - EOVERFLOW: Mathematics overflowed. + * + * As well as additional errnos, within specific ioctls. + */ + +/* The ioctl type, documented in ioctl-number.rst */ +#define LIVEUPDATE_IOCTL_TYPE 0xBA + +#endif /* _UAPI_LIVEUPDATE_H */ diff --git a/kernel/liveupdate/Kconfig b/kernel/liveupdate/Kconfig index a973a54447de..9b2515f31afb 100644 --- a/kernel/liveupdate/Kconfig +++ b/kernel/liveupdate/Kconfig @@ -51,4 +51,25 @@ config KEXEC_HANDOVER_ENABLE_DEFAULT The default behavior can still be overridden at boot time by passing 'kho=off'. +config LIVEUPDATE + bool "Live Update Orchestrator" + depends on KEXEC_HANDOVER + help + Enable the Live Update Orchestrator. Live Update is a mechanism, + typically based on kexec, that allows the kernel to be updated + while keeping selected devices operational across the transition. + These devices are intended to be reclaimed by the new kernel and + re-attached to their original workload without requiring a device + reset. + + Ability to handover a device from current to the next kernel depends + on specific support within device drivers and related kernel + subsystems. + + This feature primarily targets virtual machine hosts to quickly update + the kernel hypervisor with minimal disruption to the running virtual + machines. + + If unsure, say N. + endmenu diff --git a/kernel/liveupdate/Makefile b/kernel/liveupdate/Makefile index f52ce1ebcf86..08954c1770c4 100644 --- a/kernel/liveupdate/Makefile +++ b/kernel/liveupdate/Makefile @@ -1,5 +1,10 @@ # SPDX-License-Identifier: GPL-2.0 +luo-y := \ + luo_core.o + obj-$(CONFIG_KEXEC_HANDOVER) += kexec_handover.o obj-$(CONFIG_KEXEC_HANDOVER_DEBUG) += kexec_handover_debug.o obj-$(CONFIG_KEXEC_HANDOVER_DEBUGFS) += kexec_handover_debugfs.o + +obj-$(CONFIG_LIVEUPDATE) += luo.o diff --git a/kernel/liveupdate/luo_core.c b/kernel/liveupdate/luo_core.c new file mode 100644 index 000000000000..30ad8836360b --- /dev/null +++ b/kernel/liveupdate/luo_core.c @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * Copyright (c) 2025, Google LLC. + * Pasha Tatashin + */ + +/** + * DOC: Live Update Orchestrator (LUO) + * + * Live Update is a specialized, kexec-based reboot process that allows a + * running kernel to be updated from one version to another while preserving + * the state of selected resources and keeping designated hardware devices + * operational. For these devices, DMA activity may continue throughout the + * kernel transition. + * + * While the primary use case driving this work is supporting live updates of + * the Linux kernel when it is used as a hypervisor in cloud environments, the + * LUO framework itself is designed to be workload-agnostic. Live Update + * facilitates a full kernel version upgrade for any type of system. + * + * For example, a non-hypervisor system running an in-memory cache like + * memcached with many gigabytes of data can use LUO. The userspace service + * can place its cache into a memfd, have its state preserved by LUO, and + * restore it immediately after the kernel kexec. + * + * Whether the system is running virtual machines, containers, a + * high-performance database, or networking services, LUO's primary goal is to + * enable a full kernel update by preserving critical userspace state and + * keeping essential devices operational. + * + * The core of LUO is a mechanism that tracks the progress of a live update, + * along with a callback API that allows other kernel subsystems to participate + * in the process. Example subsystems that can hook into LUO include: kvm, + * iommu, interrupts, vfio, participating filesystems, and memory management. + * + * LUO uses Kexec Handover to transfer memory state from the current kernel to + * the next kernel. For more details see + * Documentation/core-api/kho/concepts.rst. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include + +static struct { + bool enabled; +} luo_global; + +static int __init early_liveupdate_param(char *buf) +{ + return kstrtobool(buf, &luo_global.enabled); +} +early_param("liveupdate", early_liveupdate_param); + +/* Public Functions */ + +/** + * liveupdate_reboot() - Kernel reboot notifier for live update final + * serialization. + * + * This function is invoked directly from the reboot() syscall pathway + * if kexec is in progress. + * + * If any callback fails, this function aborts KHO, undoes the freeze() + * callbacks, and returns an error. + */ +int liveupdate_reboot(void) +{ + return 0; +} + +/** + * liveupdate_enabled - Check if the live update feature is enabled. + * + * This function returns the state of the live update feature flag, which + * can be controlled via the ``liveupdate`` kernel command-line parameter. + * + * @return true if live update is enabled, false otherwise. + */ +bool liveupdate_enabled(void) +{ + return luo_global.enabled; +} + +struct luo_device_state { + struct miscdevice miscdev; +}; + +static const struct file_operations luo_fops = { + .owner = THIS_MODULE, +}; + +static struct luo_device_state luo_dev = { + .miscdev = { + .minor = MISC_DYNAMIC_MINOR, + .name = "liveupdate", + .fops = &luo_fops, + }, +}; + +static int __init liveupdate_ioctl_init(void) +{ + if (!liveupdate_enabled()) + return 0; + + return misc_register(&luo_dev.miscdev); +} +late_initcall(liveupdate_ioctl_init); -- cgit v1.2.3 From 0153094d03df5a2e834a19c59b255649a258ae46 Mon Sep 17 00:00:00 2001 From: Pasha Tatashin Date: Tue, 25 Nov 2025 11:58:34 -0500 Subject: liveupdate: luo_session: add sessions support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce concept of "Live Update Sessions" within the LUO framework. LUO sessions provide a mechanism to group and manage `struct file *` instances (representing file descriptors) that need to be preserved across a kexec-based live update. Each session is identified by a unique name and acts as a container for file objects whose state is critical to a userspace workload, such as a virtual machine or a high-performance database, aiming to maintain their functionality across a kernel transition. This groundwork establishes the framework for preserving file-backed state across kernel updates, with the actual file data preservation mechanisms to be implemented in subsequent patches. [dan.carpenter@linaro.org: fix use after free in luo_session_deserialize()] Link: https://lkml.kernel.org/r/c5dd637d7eed3a3be48c5e9fedb881596a3b1f5a.1764163896.git.dan.carpenter@linaro.org Link: https://lkml.kernel.org/r/20251125165850.3389713-5-pasha.tatashin@soleen.com Signed-off-by: Pasha Tatashin Signed-off-by: Dan Carpenter Reviewed-by: Mike Rapoport (Microsoft) Reviewed-by: Pratyush Yadav Tested-by: David Matlack Cc: Aleksander Lobakin Cc: Alexander Graf Cc: Alice Ryhl Cc: Andriy Shevchenko Cc: anish kumar Cc: Anna Schumaker Cc: Bartosz Golaszewski Cc: Bjorn Helgaas Cc: Borislav Betkov Cc: Chanwoo Choi Cc: Chen Ridong Cc: Chris Li Cc: Christian Brauner Cc: Daniel Wagner Cc: Danilo Krummrich Cc: Dan Williams Cc: David Hildenbrand Cc: David Jeffery Cc: David Rientjes Cc: Greg Kroah-Hartman Cc: Guixin Liu Cc: "H. Peter Anvin" Cc: Hugh Dickins Cc: Ilpo Järvinen Cc: Ingo Molnar Cc: Ira Weiny Cc: Jann Horn Cc: Jason Gunthorpe Cc: Jens Axboe Cc: Joanthan Cameron Cc: Joel Granados Cc: Johannes Weiner Cc: Jonathan Corbet Cc: Lennart Poettering Cc: Leon Romanovsky Cc: Leon Romanovsky Cc: Lukas Wunner Cc: Marc Rutland Cc: Masahiro Yamada Cc: Matthew Maurer Cc: Miguel Ojeda Cc: Myugnjoo Ham Cc: Parav Pandit Cc: Pratyush Yadav Cc: Randy Dunlap Cc: Roman Gushchin Cc: Saeed Mahameed Cc: Samiullah Khawaja Cc: Song Liu Cc: Steven Rostedt Cc: Stuart Hayes Cc: Tejun Heo Cc: Thomas Gleinxer Cc: Thomas Weißschuh Cc: Vincent Guittot Cc: William Tu Cc: Yoann Congal Cc: Zhu Yanjun Cc: Zijun Hu Signed-off-by: Andrew Morton --- include/linux/kho/abi/luo.h | 71 ++++++ include/uapi/linux/liveupdate.h | 3 + kernel/liveupdate/Makefile | 3 +- kernel/liveupdate/luo_core.c | 9 + kernel/liveupdate/luo_internal.h | 29 +++ kernel/liveupdate/luo_session.c | 463 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 577 insertions(+), 1 deletion(-) create mode 100644 kernel/liveupdate/luo_session.c (limited to 'include/uapi/linux') diff --git a/include/linux/kho/abi/luo.h b/include/linux/kho/abi/luo.h index 2099b51929e5..bf1ab2910959 100644 --- a/include/linux/kho/abi/luo.h +++ b/include/linux/kho/abi/luo.h @@ -32,6 +32,11 @@ * / { * compatible = "luo-v1"; * liveupdate-number = <...>; + * + * luo-session { + * compatible = "luo-session-v1"; + * luo-session-header = ; + * }; * }; * * Main LUO Node (/): @@ -40,11 +45,37 @@ * Identifies the overall LUO ABI version. * - liveupdate-number: u64 * A counter tracking the number of successful live updates performed. + * + * Session Node (luo-session): + * This node describes all preserved user-space sessions. + * + * - compatible: "luo-session-v1" + * Identifies the session ABI version. + * - luo-session-header: u64 + * The physical address of a `struct luo_session_header_ser`. This structure + * is the header for a contiguous block of memory containing an array of + * `struct luo_session_ser`, one for each preserved session. + * + * Serialization Structures: + * The FDT properties point to memory regions containing arrays of simple, + * `__packed` structures. These structures contain the actual preserved state. + * + * - struct luo_session_header_ser: + * Header for the session array. Contains the total page count of the + * preserved memory block and the number of `struct luo_session_ser` + * entries that follow. + * + * - struct luo_session_ser: + * Metadata for a single session, including its name and a physical pointer + * to another preserved memory block containing an array of + * `struct luo_file_ser` for all files in that session. */ #ifndef _LINUX_KHO_ABI_LUO_H #define _LINUX_KHO_ABI_LUO_H +#include + /* * The LUO FDT hooks all LUO state for sessions, fds, etc. * In the root it also carries "liveupdate-number" 64-bit property that @@ -55,4 +86,44 @@ #define LUO_FDT_COMPATIBLE "luo-v1" #define LUO_FDT_LIVEUPDATE_NUM "liveupdate-number" +/* + * LUO FDT session node + * LUO_FDT_SESSION_HEADER: is a u64 physical address of struct + * luo_session_header_ser + */ +#define LUO_FDT_SESSION_NODE_NAME "luo-session" +#define LUO_FDT_SESSION_COMPATIBLE "luo-session-v1" +#define LUO_FDT_SESSION_HEADER "luo-session-header" + +/** + * struct luo_session_header_ser - Header for the serialized session data block. + * @count: The number of `struct luo_session_ser` entries that immediately + * follow this header in the memory block. + * + * This structure is located at the beginning of a contiguous block of + * physical memory preserved across the kexec. It provides the necessary + * metadata to interpret the array of session entries that follow. + * + * If this structure is modified, `LUO_FDT_SESSION_COMPATIBLE` must be updated. + */ +struct luo_session_header_ser { + u64 count; +} __packed; + +/** + * struct luo_session_ser - Represents the serialized metadata for a LUO session. + * @name: The unique name of the session, provided by the userspace at + * the time of session creation. + * + * This structure is used to package session-specific metadata for transfer + * between kernels via Kexec Handover. An array of these structures (one per + * session) is created and passed to the new kernel, allowing it to reconstruct + * the session context. + * + * If this structure is modified, `LUO_FDT_SESSION_COMPATIBLE` must be updated. + */ +struct luo_session_ser { + char name[LIVEUPDATE_SESSION_NAME_LENGTH]; +} __packed; + #endif /* _LINUX_KHO_ABI_LUO_H */ diff --git a/include/uapi/linux/liveupdate.h b/include/uapi/linux/liveupdate.h index df34c1642c4d..40578ae19668 100644 --- a/include/uapi/linux/liveupdate.h +++ b/include/uapi/linux/liveupdate.h @@ -43,4 +43,7 @@ /* The ioctl type, documented in ioctl-number.rst */ #define LIVEUPDATE_IOCTL_TYPE 0xBA +/* The maximum length of session name including null termination */ +#define LIVEUPDATE_SESSION_NAME_LENGTH 64 + #endif /* _UAPI_LIVEUPDATE_H */ diff --git a/kernel/liveupdate/Makefile b/kernel/liveupdate/Makefile index 08954c1770c4..6af93caa58cf 100644 --- a/kernel/liveupdate/Makefile +++ b/kernel/liveupdate/Makefile @@ -1,7 +1,8 @@ # SPDX-License-Identifier: GPL-2.0 luo-y := \ - luo_core.o + luo_core.o \ + luo_session.o obj-$(CONFIG_KEXEC_HANDOVER) += kexec_handover.o obj-$(CONFIG_KEXEC_HANDOVER_DEBUG) += kexec_handover_debug.o diff --git a/kernel/liveupdate/luo_core.c b/kernel/liveupdate/luo_core.c index 9f9fe9a81b29..a0f7788cd003 100644 --- a/kernel/liveupdate/luo_core.c +++ b/kernel/liveupdate/luo_core.c @@ -118,6 +118,10 @@ static int __init luo_early_startup(void) pr_info("Retrieved live update data, liveupdate number: %lld\n", luo_global.liveupdate_num); + err = luo_session_setup_incoming(luo_global.fdt_in); + if (err) + return err; + return 0; } @@ -154,6 +158,7 @@ static int __init luo_fdt_setup(void) err |= fdt_begin_node(fdt_out, ""); err |= fdt_property_string(fdt_out, "compatible", LUO_FDT_COMPATIBLE); err |= fdt_property(fdt_out, LUO_FDT_LIVEUPDATE_NUM, &ln, sizeof(ln)); + err |= luo_session_setup_outgoing(fdt_out); err |= fdt_end_node(fdt_out); err |= fdt_finish(fdt_out); if (err) @@ -211,6 +216,10 @@ int liveupdate_reboot(void) if (!liveupdate_enabled()) return 0; + err = luo_session_serialize(); + if (err) + return err; + err = kho_finalize(); if (err) { pr_err("kho_finalize failed %d\n", err); diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h index 8612687b2000..05ae91695ec6 100644 --- a/kernel/liveupdate/luo_internal.h +++ b/kernel/liveupdate/luo_internal.h @@ -19,4 +19,33 @@ */ #define luo_restore_fail(__fmt, ...) panic(__fmt, ##__VA_ARGS__) +/** + * struct luo_session - Represents an active or incoming Live Update session. + * @name: A unique name for this session, used for identification and + * retrieval. + * @ser: Pointer to the serialized data for this session. + * @list: A list_head member used to link this session into a global list + * of either outgoing (to be preserved) or incoming (restored from + * previous kernel) sessions. + * @retrieved: A boolean flag indicating whether this session has been + * retrieved by a consumer in the new kernel. + * @mutex: protects fields in the luo_session. + */ +struct luo_session { + char name[LIVEUPDATE_SESSION_NAME_LENGTH]; + struct luo_session_ser *ser; + struct list_head list; + bool retrieved; + struct mutex mutex; +}; + +int luo_session_create(const char *name, struct file **filep); +int luo_session_retrieve(const char *name, struct file **filep); +int __init luo_session_setup_outgoing(void *fdt); +int __init luo_session_setup_incoming(void *fdt); +int luo_session_serialize(void); +int luo_session_deserialize(void); +bool luo_session_quiesce(void); +void luo_session_resume(void); + #endif /* _LINUX_LUO_INTERNAL_H */ diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c new file mode 100644 index 000000000000..3a031446d3a4 --- /dev/null +++ b/kernel/liveupdate/luo_session.c @@ -0,0 +1,463 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * Copyright (c) 2025, Google LLC. + * Pasha Tatashin + */ + +/** + * DOC: LUO Sessions + * + * LUO Sessions provide the core mechanism for grouping and managing `struct + * file *` instances that need to be preserved across a kexec-based live + * update. Each session acts as a named container for a set of file objects, + * allowing a userspace agent to manage the lifecycle of resources critical to a + * workload. + * + * Core Concepts: + * + * - Named Containers: Sessions are identified by a unique, user-provided name, + * which is used for both creation in the current kernel and retrieval in the + * next kernel. + * + * - Userspace Interface: Session management is driven from userspace via + * ioctls on /dev/liveupdate. + * + * - Serialization: Session metadata is preserved using the KHO framework. When + * a live update is triggered via kexec, an array of `struct luo_session_ser` + * is populated and placed in a preserved memory region. An FDT node is also + * created, containing the count of sessions and the physical address of this + * array. + * + * Session Lifecycle: + * + * 1. Creation: A userspace agent calls `luo_session_create()` to create a + * new, empty session and receives a file descriptor for it. + * + * 2. Serialization: When the `reboot(LINUX_REBOOT_CMD_KEXEC)` syscall is + * made, `luo_session_serialize()` is called. It iterates through all + * active sessions and writes their metadata into a memory area preserved + * by KHO. + * + * 3. Deserialization (in new kernel): After kexec, `luo_session_deserialize()` + * runs, reading the serialized data and creating a list of `struct + * luo_session` objects representing the preserved sessions. + * + * 4. Retrieval: A userspace agent in the new kernel can then call + * `luo_session_retrieve()` with a session name to get a new file + * descriptor and access the preserved state. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "luo_internal.h" + +/* 16 4K pages, give space for 744 sessions */ +#define LUO_SESSION_PGCNT 16ul +#define LUO_SESSION_MAX (((LUO_SESSION_PGCNT << PAGE_SHIFT) - \ + sizeof(struct luo_session_header_ser)) / \ + sizeof(struct luo_session_ser)) + +/** + * struct luo_session_header - Header struct for managing LUO sessions. + * @count: The number of sessions currently tracked in the @list. + * @list: The head of the linked list of `struct luo_session` instances. + * @rwsem: A read-write semaphore providing synchronized access to the + * session list and other fields in this structure. + * @header_ser: The header data of serialization array. + * @ser: The serialized session data (an array of + * `struct luo_session_ser`). + * @active: Set to true when first initialized. If previous kernel did not + * send session data, active stays false for incoming. + */ +struct luo_session_header { + long count; + struct list_head list; + struct rw_semaphore rwsem; + struct luo_session_header_ser *header_ser; + struct luo_session_ser *ser; + bool active; +}; + +/** + * struct luo_session_global - Global container for managing LUO sessions. + * @incoming: The sessions passed from the previous kernel. + * @outgoing: The sessions that are going to be passed to the next kernel. + */ +struct luo_session_global { + struct luo_session_header incoming; + struct luo_session_header outgoing; +}; + +static struct luo_session_global luo_session_global = { + .incoming = { + .list = LIST_HEAD_INIT(luo_session_global.incoming.list), + .rwsem = __RWSEM_INITIALIZER(luo_session_global.incoming.rwsem), + }, + .outgoing = { + .list = LIST_HEAD_INIT(luo_session_global.outgoing.list), + .rwsem = __RWSEM_INITIALIZER(luo_session_global.outgoing.rwsem), + }, +}; + +static struct luo_session *luo_session_alloc(const char *name) +{ + struct luo_session *session = kzalloc(sizeof(*session), GFP_KERNEL); + + if (!session) + return ERR_PTR(-ENOMEM); + + strscpy(session->name, name, sizeof(session->name)); + INIT_LIST_HEAD(&session->list); + mutex_init(&session->mutex); + + return session; +} + +static void luo_session_free(struct luo_session *session) +{ + mutex_destroy(&session->mutex); + kfree(session); +} + +static int luo_session_insert(struct luo_session_header *sh, + struct luo_session *session) +{ + struct luo_session *it; + + guard(rwsem_write)(&sh->rwsem); + + /* + * For outgoing we should make sure there is room in serialization array + * for new session. + */ + if (sh == &luo_session_global.outgoing) { + if (sh->count == LUO_SESSION_MAX) + return -ENOMEM; + } + + /* + * For small number of sessions this loop won't hurt performance + * but if we ever start using a lot of sessions, this might + * become a bottle neck during deserialization time, as it would + * cause O(n*n) complexity. + */ + list_for_each_entry(it, &sh->list, list) { + if (!strncmp(it->name, session->name, sizeof(it->name))) + return -EEXIST; + } + list_add_tail(&session->list, &sh->list); + sh->count++; + + return 0; +} + +static void luo_session_remove(struct luo_session_header *sh, + struct luo_session *session) +{ + guard(rwsem_write)(&sh->rwsem); + list_del(&session->list); + sh->count--; +} + +static int luo_session_release(struct inode *inodep, struct file *filep) +{ + struct luo_session *session = filep->private_data; + struct luo_session_header *sh; + + /* If retrieved is set, it means this session is from incoming list */ + if (session->retrieved) + sh = &luo_session_global.incoming; + else + sh = &luo_session_global.outgoing; + + luo_session_remove(sh, session); + luo_session_free(session); + + return 0; +} + +static const struct file_operations luo_session_fops = { + .owner = THIS_MODULE, + .release = luo_session_release, +}; + +/* Create a "struct file" for session */ +static int luo_session_getfile(struct luo_session *session, struct file **filep) +{ + char name_buf[128]; + struct file *file; + + lockdep_assert_held(&session->mutex); + snprintf(name_buf, sizeof(name_buf), "[luo_session] %s", session->name); + file = anon_inode_getfile(name_buf, &luo_session_fops, session, O_RDWR); + if (IS_ERR(file)) + return PTR_ERR(file); + + *filep = file; + + return 0; +} + +int luo_session_create(const char *name, struct file **filep) +{ + struct luo_session *session; + int err; + + session = luo_session_alloc(name); + if (IS_ERR(session)) + return PTR_ERR(session); + + err = luo_session_insert(&luo_session_global.outgoing, session); + if (err) + goto err_free; + + scoped_guard(mutex, &session->mutex) + err = luo_session_getfile(session, filep); + if (err) + goto err_remove; + + return 0; + +err_remove: + luo_session_remove(&luo_session_global.outgoing, session); +err_free: + luo_session_free(session); + + return err; +} + +int luo_session_retrieve(const char *name, struct file **filep) +{ + struct luo_session_header *sh = &luo_session_global.incoming; + struct luo_session *session = NULL; + struct luo_session *it; + int err; + + scoped_guard(rwsem_read, &sh->rwsem) { + list_for_each_entry(it, &sh->list, list) { + if (!strncmp(it->name, name, sizeof(it->name))) { + session = it; + break; + } + } + } + + if (!session) + return -ENOENT; + + guard(mutex)(&session->mutex); + if (session->retrieved) + return -EINVAL; + + err = luo_session_getfile(session, filep); + if (!err) + session->retrieved = true; + + return err; +} + +int __init luo_session_setup_outgoing(void *fdt_out) +{ + struct luo_session_header_ser *header_ser; + u64 header_ser_pa; + int err; + + header_ser = kho_alloc_preserve(LUO_SESSION_PGCNT << PAGE_SHIFT); + if (IS_ERR(header_ser)) + return PTR_ERR(header_ser); + header_ser_pa = virt_to_phys(header_ser); + + err = fdt_begin_node(fdt_out, LUO_FDT_SESSION_NODE_NAME); + err |= fdt_property_string(fdt_out, "compatible", + LUO_FDT_SESSION_COMPATIBLE); + err |= fdt_property(fdt_out, LUO_FDT_SESSION_HEADER, &header_ser_pa, + sizeof(header_ser_pa)); + err |= fdt_end_node(fdt_out); + + if (err) + goto err_unpreserve; + + luo_session_global.outgoing.header_ser = header_ser; + luo_session_global.outgoing.ser = (void *)(header_ser + 1); + luo_session_global.outgoing.active = true; + + return 0; + +err_unpreserve: + kho_unpreserve_free(header_ser); + return err; +} + +int __init luo_session_setup_incoming(void *fdt_in) +{ + struct luo_session_header_ser *header_ser; + int err, header_size, offset; + u64 header_ser_pa; + const void *ptr; + + offset = fdt_subnode_offset(fdt_in, 0, LUO_FDT_SESSION_NODE_NAME); + if (offset < 0) { + pr_err("Unable to get session node: [%s]\n", + LUO_FDT_SESSION_NODE_NAME); + return -EINVAL; + } + + err = fdt_node_check_compatible(fdt_in, offset, + LUO_FDT_SESSION_COMPATIBLE); + if (err) { + pr_err("Session node incompatible [%s]\n", + LUO_FDT_SESSION_COMPATIBLE); + return -EINVAL; + } + + header_size = 0; + ptr = fdt_getprop(fdt_in, offset, LUO_FDT_SESSION_HEADER, &header_size); + if (!ptr || header_size != sizeof(u64)) { + pr_err("Unable to get session header '%s' [%d]\n", + LUO_FDT_SESSION_HEADER, header_size); + return -EINVAL; + } + + header_ser_pa = get_unaligned((u64 *)ptr); + header_ser = phys_to_virt(header_ser_pa); + + luo_session_global.incoming.header_ser = header_ser; + luo_session_global.incoming.ser = (void *)(header_ser + 1); + luo_session_global.incoming.active = true; + + return 0; +} + +int luo_session_deserialize(void) +{ + struct luo_session_header *sh = &luo_session_global.incoming; + static bool is_deserialized; + static int err; + + /* If has been deserialized, always return the same error code */ + if (is_deserialized) + return err; + + is_deserialized = true; + if (!sh->active) + return 0; + + /* + * Note on error handling: + * + * If deserialization fails (e.g., allocation failure or corrupt data), + * we intentionally skip cleanup of sessions that were already restored. + * + * A partial failure leaves the preserved state inconsistent. + * Implementing a safe "undo" to unwind complex dependencies (sessions, + * files, hardware state) is error-prone and provides little value, as + * the system is effectively in a broken state. + * + * We treat these resources as leaked. The expected recovery path is for + * userspace to detect the failure and trigger a reboot, which will + * reliably reset devices and reclaim memory. + */ + for (int i = 0; i < sh->header_ser->count; i++) { + struct luo_session *session; + + session = luo_session_alloc(sh->ser[i].name); + if (IS_ERR(session)) { + pr_warn("Failed to allocate session [%s] during deserialization %pe\n", + sh->ser[i].name, session); + return PTR_ERR(session); + } + + err = luo_session_insert(sh, session); + if (err) { + pr_warn("Failed to insert session [%s] %pe\n", + session->name, ERR_PTR(err)); + luo_session_free(session); + return err; + } + } + + kho_restore_free(sh->header_ser); + sh->header_ser = NULL; + sh->ser = NULL; + + return 0; +} + +int luo_session_serialize(void) +{ + struct luo_session_header *sh = &luo_session_global.outgoing; + struct luo_session *session; + int i = 0; + + guard(rwsem_write)(&sh->rwsem); + list_for_each_entry(session, &sh->list, list) { + strscpy(sh->ser[i].name, session->name, + sizeof(sh->ser[i].name)); + i++; + } + sh->header_ser->count = sh->count; + + return 0; +} + +/** + * luo_session_quiesce - Ensure no active sessions exist and lock session lists. + * + * Acquires exclusive write locks on both incoming and outgoing session lists. + * It then validates no sessions exist in either list. + * + * This mechanism is used during file handler un/registration to ensure that no + * sessions are currently using the handler, and no new sessions can be created + * while un/registration is in progress. + * + * This prevents registering new handlers while sessions are active or + * while deserialization is in progress. + * + * Return: + * true - System is quiescent (0 sessions) and locked. + * false - Active sessions exist. The locks are released internally. + */ +bool luo_session_quiesce(void) +{ + down_write(&luo_session_global.incoming.rwsem); + down_write(&luo_session_global.outgoing.rwsem); + + if (luo_session_global.incoming.count || + luo_session_global.outgoing.count) { + up_write(&luo_session_global.outgoing.rwsem); + up_write(&luo_session_global.incoming.rwsem); + return false; + } + + return true; +} + +/** + * luo_session_resume - Unlock session lists and resume normal activity. + * + * Releases the exclusive locks acquired by a successful call to + * luo_session_quiesce(). + */ +void luo_session_resume(void) +{ + up_write(&luo_session_global.outgoing.rwsem); + up_write(&luo_session_global.incoming.rwsem); +} -- cgit v1.2.3 From 81cd25d263a182b3dcdc8af3b92e4b8e4db336de Mon Sep 17 00:00:00 2001 From: Pasha Tatashin Date: Tue, 25 Nov 2025 11:58:35 -0500 Subject: liveupdate: luo_core: add user interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce the user-space interface for the Live Update Orchestrator via ioctl commands, enabling external control over the live update process and management of preserved resources. The idea is that there is going to be a single userspace agent driving the live update, therefore, only a single process can ever hold this device opened at a time. The following ioctl commands are introduced: LIVEUPDATE_IOCTL_CREATE_SESSION Provides a way for userspace to create a named session for grouping file descriptors that need to be preserved. It returns a new file descriptor representing the session. LIVEUPDATE_IOCTL_RETRIEVE_SESSION Allows the userspace agent in the new kernel to reclaim a preserved session by its name, receiving a new file descriptor to manage the restored resources. Link: https://lkml.kernel.org/r/20251125165850.3389713-6-pasha.tatashin@soleen.com Signed-off-by: Pasha Tatashin Reviewed-by: Mike Rapoport (Microsoft) Reviewed-by: Pratyush Yadav Tested-by: David Matlack Cc: Aleksander Lobakin Cc: Alexander Graf Cc: Alice Ryhl Cc: Andriy Shevchenko Cc: anish kumar Cc: Anna Schumaker Cc: Bartosz Golaszewski Cc: Bjorn Helgaas Cc: Borislav Betkov Cc: Chanwoo Choi Cc: Chen Ridong Cc: Chris Li Cc: Christian Brauner Cc: Daniel Wagner Cc: Danilo Krummrich Cc: Dan Williams Cc: David Hildenbrand Cc: David Jeffery Cc: David Rientjes Cc: Greg Kroah-Hartman Cc: Guixin Liu Cc: "H. Peter Anvin" Cc: Hugh Dickins Cc: Ilpo Järvinen Cc: Ingo Molnar Cc: Ira Weiny Cc: Jann Horn Cc: Jason Gunthorpe Cc: Jens Axboe Cc: Joanthan Cameron Cc: Joel Granados Cc: Johannes Weiner Cc: Jonathan Corbet Cc: Lennart Poettering Cc: Leon Romanovsky Cc: Leon Romanovsky Cc: Lukas Wunner Cc: Marc Rutland Cc: Masahiro Yamada Cc: Matthew Maurer Cc: Miguel Ojeda Cc: Myugnjoo Ham Cc: Parav Pandit Cc: Pratyush Yadav Cc: Randy Dunlap Cc: Roman Gushchin Cc: Saeed Mahameed Cc: Samiullah Khawaja Cc: Song Liu Cc: Steven Rostedt Cc: Stuart Hayes Cc: Tejun Heo Cc: Thomas Gleinxer Cc: Thomas Weißschuh Cc: Vincent Guittot Cc: William Tu Cc: Yoann Congal Cc: Zhu Yanjun Cc: Zijun Hu Signed-off-by: Andrew Morton --- include/uapi/linux/liveupdate.h | 64 ++++++++++++++ kernel/liveupdate/luo_core.c | 178 +++++++++++++++++++++++++++++++++++++++ kernel/liveupdate/luo_internal.h | 21 +++++ 3 files changed, 263 insertions(+) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/liveupdate.h b/include/uapi/linux/liveupdate.h index 40578ae19668..1183cf984b5f 100644 --- a/include/uapi/linux/liveupdate.h +++ b/include/uapi/linux/liveupdate.h @@ -46,4 +46,68 @@ /* The maximum length of session name including null termination */ #define LIVEUPDATE_SESSION_NAME_LENGTH 64 +/* The /dev/liveupdate ioctl commands */ +enum { + LIVEUPDATE_CMD_BASE = 0x00, + LIVEUPDATE_CMD_CREATE_SESSION = LIVEUPDATE_CMD_BASE, + LIVEUPDATE_CMD_RETRIEVE_SESSION = 0x01, +}; + +/** + * struct liveupdate_ioctl_create_session - ioctl(LIVEUPDATE_IOCTL_CREATE_SESSION) + * @size: Input; sizeof(struct liveupdate_ioctl_create_session) + * @fd: Output; The new file descriptor for the created session. + * @name: Input; A null-terminated string for the session name, max + * length %LIVEUPDATE_SESSION_NAME_LENGTH including termination + * character. + * + * Creates a new live update session for managing preserved resources. + * This ioctl can only be called on the main /dev/liveupdate device. + * + * Return: 0 on success, negative error code on failure. + */ +struct liveupdate_ioctl_create_session { + __u32 size; + __s32 fd; + __u8 name[LIVEUPDATE_SESSION_NAME_LENGTH]; +}; + +#define LIVEUPDATE_IOCTL_CREATE_SESSION \ + _IO(LIVEUPDATE_IOCTL_TYPE, LIVEUPDATE_CMD_CREATE_SESSION) + +/** + * struct liveupdate_ioctl_retrieve_session - ioctl(LIVEUPDATE_IOCTL_RETRIEVE_SESSION) + * @size: Input; sizeof(struct liveupdate_ioctl_retrieve_session) + * @fd: Output; The new file descriptor for the retrieved session. + * @name: Input; A null-terminated string identifying the session to retrieve. + * The name must exactly match the name used when the session was + * created in the previous kernel. + * + * Retrieves a handle (a new file descriptor) for a preserved session by its + * name. This is the primary mechanism for a userspace agent to regain control + * of its preserved resources after a live update. + * + * The userspace application provides the null-terminated `name` of a session + * it created before the live update. If a preserved session with a matching + * name is found, the kernel instantiates it and returns a new file descriptor + * in the `fd` field. This new session FD can then be used for all file-specific + * operations, such as restoring individual file descriptors with + * LIVEUPDATE_SESSION_RETRIEVE_FD. + * + * It is the responsibility of the userspace application to know the names of + * the sessions it needs to retrieve. If no session with the given name is + * found, the ioctl will fail with -ENOENT. + * + * This ioctl can only be called on the main /dev/liveupdate device when the + * system is in the LIVEUPDATE_STATE_UPDATED state. + */ +struct liveupdate_ioctl_retrieve_session { + __u32 size; + __s32 fd; + __u8 name[LIVEUPDATE_SESSION_NAME_LENGTH]; +}; + +#define LIVEUPDATE_IOCTL_RETRIEVE_SESSION \ + _IO(LIVEUPDATE_IOCTL_TYPE, LIVEUPDATE_CMD_RETRIEVE_SESSION) + #endif /* _UAPI_LIVEUPDATE_H */ diff --git a/kernel/liveupdate/luo_core.c b/kernel/liveupdate/luo_core.c index a0f7788cd003..f7ecaf7740d1 100644 --- a/kernel/liveupdate/luo_core.c +++ b/kernel/liveupdate/luo_core.c @@ -41,7 +41,13 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include +#include +#include +#include +#include #include +#include #include #include #include @@ -246,12 +252,183 @@ bool liveupdate_enabled(void) return luo_global.enabled; } +/** + * DOC: LUO ioctl Interface + * + * The IOCTL user-space control interface for the LUO subsystem. + * It registers a character device, typically found at ``/dev/liveupdate``, + * which allows a userspace agent to manage the LUO state machine and its + * associated resources, such as preservable file descriptors. + * + * To ensure that the state machine is controlled by a single entity, access + * to this device is exclusive: only one process is permitted to have + * ``/dev/liveupdate`` open at any given time. Subsequent open attempts will + * fail with -EBUSY until the first process closes its file descriptor. + * This singleton model simplifies state management by preventing conflicting + * commands from multiple userspace agents. + */ + struct luo_device_state { struct miscdevice miscdev; + atomic_t in_use; }; +static int luo_ioctl_create_session(struct luo_ucmd *ucmd) +{ + struct liveupdate_ioctl_create_session *argp = ucmd->cmd; + struct file *file; + int err; + + argp->fd = get_unused_fd_flags(O_CLOEXEC); + if (argp->fd < 0) + return argp->fd; + + err = luo_session_create(argp->name, &file); + if (err) + goto err_put_fd; + + err = luo_ucmd_respond(ucmd, sizeof(*argp)); + if (err) + goto err_put_file; + + fd_install(argp->fd, file); + + return 0; + +err_put_file: + fput(file); +err_put_fd: + put_unused_fd(argp->fd); + + return err; +} + +static int luo_ioctl_retrieve_session(struct luo_ucmd *ucmd) +{ + struct liveupdate_ioctl_retrieve_session *argp = ucmd->cmd; + struct file *file; + int err; + + argp->fd = get_unused_fd_flags(O_CLOEXEC); + if (argp->fd < 0) + return argp->fd; + + err = luo_session_retrieve(argp->name, &file); + if (err < 0) + goto err_put_fd; + + err = luo_ucmd_respond(ucmd, sizeof(*argp)); + if (err) + goto err_put_file; + + fd_install(argp->fd, file); + + return 0; + +err_put_file: + fput(file); +err_put_fd: + put_unused_fd(argp->fd); + + return err; +} + +static int luo_open(struct inode *inodep, struct file *filep) +{ + struct luo_device_state *ldev = container_of(filep->private_data, + struct luo_device_state, + miscdev); + + if (atomic_cmpxchg(&ldev->in_use, 0, 1)) + return -EBUSY; + + /* Always return -EIO to user if deserialization fail */ + if (luo_session_deserialize()) { + atomic_set(&ldev->in_use, 0); + return -EIO; + } + + return 0; +} + +static int luo_release(struct inode *inodep, struct file *filep) +{ + struct luo_device_state *ldev = container_of(filep->private_data, + struct luo_device_state, + miscdev); + atomic_set(&ldev->in_use, 0); + + return 0; +} + +union ucmd_buffer { + struct liveupdate_ioctl_create_session create; + struct liveupdate_ioctl_retrieve_session retrieve; +}; + +struct luo_ioctl_op { + unsigned int size; + unsigned int min_size; + unsigned int ioctl_num; + int (*execute)(struct luo_ucmd *ucmd); +}; + +#define IOCTL_OP(_ioctl, _fn, _struct, _last) \ + [_IOC_NR(_ioctl) - LIVEUPDATE_CMD_BASE] = { \ + .size = sizeof(_struct) + \ + BUILD_BUG_ON_ZERO(sizeof(union ucmd_buffer) < \ + sizeof(_struct)), \ + .min_size = offsetofend(_struct, _last), \ + .ioctl_num = _ioctl, \ + .execute = _fn, \ + } + +static const struct luo_ioctl_op luo_ioctl_ops[] = { + IOCTL_OP(LIVEUPDATE_IOCTL_CREATE_SESSION, luo_ioctl_create_session, + struct liveupdate_ioctl_create_session, name), + IOCTL_OP(LIVEUPDATE_IOCTL_RETRIEVE_SESSION, luo_ioctl_retrieve_session, + struct liveupdate_ioctl_retrieve_session, name), +}; + +static long luo_ioctl(struct file *filep, unsigned int cmd, unsigned long arg) +{ + const struct luo_ioctl_op *op; + struct luo_ucmd ucmd = {}; + union ucmd_buffer buf; + unsigned int nr; + int err; + + nr = _IOC_NR(cmd); + if (nr < LIVEUPDATE_CMD_BASE || + (nr - LIVEUPDATE_CMD_BASE) >= ARRAY_SIZE(luo_ioctl_ops)) { + return -EINVAL; + } + + ucmd.ubuffer = (void __user *)arg; + err = get_user(ucmd.user_size, (u32 __user *)ucmd.ubuffer); + if (err) + return err; + + op = &luo_ioctl_ops[nr - LIVEUPDATE_CMD_BASE]; + if (op->ioctl_num != cmd) + return -ENOIOCTLCMD; + if (ucmd.user_size < op->min_size) + return -EINVAL; + + ucmd.cmd = &buf; + err = copy_struct_from_user(ucmd.cmd, op->size, ucmd.ubuffer, + ucmd.user_size); + if (err) + return err; + + return op->execute(&ucmd); +} + static const struct file_operations luo_fops = { .owner = THIS_MODULE, + .open = luo_open, + .release = luo_release, + .unlocked_ioctl = luo_ioctl, }; static struct luo_device_state luo_dev = { @@ -260,6 +437,7 @@ static struct luo_device_state luo_dev = { .name = "liveupdate", .fops = &luo_fops, }, + .in_use = ATOMIC_INIT(0), }; static int __init liveupdate_ioctl_init(void) diff --git a/kernel/liveupdate/luo_internal.h b/kernel/liveupdate/luo_internal.h index 05ae91695ec6..1292ac47eef8 100644 --- a/kernel/liveupdate/luo_internal.h +++ b/kernel/liveupdate/luo_internal.h @@ -9,6 +9,27 @@ #define _LINUX_LUO_INTERNAL_H #include +#include + +struct luo_ucmd { + void __user *ubuffer; + u32 user_size; + void *cmd; +}; + +static inline int luo_ucmd_respond(struct luo_ucmd *ucmd, + size_t kernel_cmd_size) +{ + /* + * Copy the minimum of what the user provided and what we actually + * have. + */ + if (copy_to_user(ucmd->ubuffer, ucmd->cmd, + min_t(size_t, ucmd->user_size, kernel_cmd_size))) { + return -EFAULT; + } + return 0; +} /* * Handles a deserialization failure: devices and memory is in unpredictable -- cgit v1.2.3 From 16cec0d265219f14a7fcebcc43aeb69205adba56 Mon Sep 17 00:00:00 2001 From: Pasha Tatashin Date: Tue, 25 Nov 2025 11:58:37 -0500 Subject: liveupdate: luo_session: add ioctls for file preservation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introducing the userspace interface and internal logic required to manage the lifecycle of file descriptors within a session. Previously, a session was merely a container; this change makes it a functional management unit. The following capabilities are added: A new set of ioctl commands are added, which operate on the file descriptor returned by CREATE_SESSION. This allows userspace to: - LIVEUPDATE_SESSION_PRESERVE_FD: Add a file descriptor to a session to be preserved across the live update. - LIVEUPDATE_SESSION_RETRIEVE_FD: Retrieve a preserved file in the new kernel using its unique token. - LIVEUPDATE_SESSION_FINISH: finish session The session's .release handler is enhanced to be state-aware. When a session's file descriptor is closed, it correctly unpreserves the session based on its current state before freeing all associated file resources. Link: https://lkml.kernel.org/r/20251125165850.3389713-8-pasha.tatashin@soleen.com Signed-off-by: Pasha Tatashin Reviewed-by: Pratyush Yadav Reviewed-by: Mike Rapoport (Microsoft) Tested-by: David Matlack Cc: Aleksander Lobakin Cc: Alexander Graf Cc: Alice Ryhl Cc: Andriy Shevchenko Cc: anish kumar Cc: Anna Schumaker Cc: Bartosz Golaszewski Cc: Bjorn Helgaas Cc: Borislav Betkov Cc: Chanwoo Choi Cc: Chen Ridong Cc: Chris Li Cc: Christian Brauner Cc: Daniel Wagner Cc: Danilo Krummrich Cc: Dan Williams Cc: David Hildenbrand Cc: David Jeffery Cc: David Rientjes Cc: Greg Kroah-Hartman Cc: Guixin Liu Cc: "H. Peter Anvin" Cc: Hugh Dickins Cc: Ilpo Järvinen Cc: Ingo Molnar Cc: Ira Weiny Cc: Jann Horn Cc: Jason Gunthorpe Cc: Jens Axboe Cc: Joanthan Cameron Cc: Joel Granados Cc: Johannes Weiner Cc: Jonathan Corbet Cc: Lennart Poettering Cc: Leon Romanovsky Cc: Leon Romanovsky Cc: Lukas Wunner Cc: Marc Rutland Cc: Masahiro Yamada Cc: Matthew Maurer Cc: Miguel Ojeda Cc: Myugnjoo Ham Cc: Parav Pandit Cc: Pratyush Yadav Cc: Randy Dunlap Cc: Roman Gushchin Cc: Saeed Mahameed Cc: Samiullah Khawaja Cc: Song Liu Cc: Steven Rostedt Cc: Stuart Hayes Cc: Tejun Heo Cc: Thomas Gleinxer Cc: Thomas Weißschuh Cc: Vincent Guittot Cc: William Tu Cc: Yoann Congal Cc: Zhu Yanjun Cc: Zijun Hu Signed-off-by: Andrew Morton --- include/uapi/linux/liveupdate.h | 103 ++++++++++++++++++++++ kernel/liveupdate/luo_session.c | 187 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 288 insertions(+), 2 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/liveupdate.h b/include/uapi/linux/liveupdate.h index 1183cf984b5f..30bc66ee9436 100644 --- a/include/uapi/linux/liveupdate.h +++ b/include/uapi/linux/liveupdate.h @@ -53,6 +53,14 @@ enum { LIVEUPDATE_CMD_RETRIEVE_SESSION = 0x01, }; +/* ioctl commands for session file descriptors */ +enum { + LIVEUPDATE_CMD_SESSION_BASE = 0x40, + LIVEUPDATE_CMD_SESSION_PRESERVE_FD = LIVEUPDATE_CMD_SESSION_BASE, + LIVEUPDATE_CMD_SESSION_RETRIEVE_FD = 0x41, + LIVEUPDATE_CMD_SESSION_FINISH = 0x42, +}; + /** * struct liveupdate_ioctl_create_session - ioctl(LIVEUPDATE_IOCTL_CREATE_SESSION) * @size: Input; sizeof(struct liveupdate_ioctl_create_session) @@ -110,4 +118,99 @@ struct liveupdate_ioctl_retrieve_session { #define LIVEUPDATE_IOCTL_RETRIEVE_SESSION \ _IO(LIVEUPDATE_IOCTL_TYPE, LIVEUPDATE_CMD_RETRIEVE_SESSION) +/* Session specific IOCTLs */ + +/** + * struct liveupdate_session_preserve_fd - ioctl(LIVEUPDATE_SESSION_PRESERVE_FD) + * @size: Input; sizeof(struct liveupdate_session_preserve_fd) + * @fd: Input; The user-space file descriptor to be preserved. + * @token: Input; An opaque, unique token for preserved resource. + * + * Holds parameters for preserving a file descriptor. + * + * User sets the @fd field identifying the file descriptor to preserve + * (e.g., memfd, kvm, iommufd, VFIO). The kernel validates if this FD type + * and its dependencies are supported for preservation. If validation passes, + * the kernel marks the FD internally and *initiates the process* of preparing + * its state for saving. The actual snapshotting of the state typically occurs + * during the subsequent %LIVEUPDATE_IOCTL_PREPARE execution phase, though + * some finalization might occur during freeze. + * On successful validation and initiation, the kernel uses the @token + * field with an opaque identifier representing the resource being preserved. + * This token confirms the FD is targeted for preservation and is required for + * the subsequent %LIVEUPDATE_SESSION_RETRIEVE_FD call after the live update. + * + * Return: 0 on success (validation passed, preservation initiated), negative + * error code on failure (e.g., unsupported FD type, dependency issue, + * validation failed). + */ +struct liveupdate_session_preserve_fd { + __u32 size; + __s32 fd; + __aligned_u64 token; +}; + +#define LIVEUPDATE_SESSION_PRESERVE_FD \ + _IO(LIVEUPDATE_IOCTL_TYPE, LIVEUPDATE_CMD_SESSION_PRESERVE_FD) + +/** + * struct liveupdate_session_retrieve_fd - ioctl(LIVEUPDATE_SESSION_RETRIEVE_FD) + * @size: Input; sizeof(struct liveupdate_session_retrieve_fd) + * @fd: Output; The new file descriptor representing the fully restored + * kernel resource. + * @token: Input; An opaque, token that was used to preserve the resource. + * + * Retrieve a previously preserved file descriptor. + * + * User sets the @token field to the value obtained from a successful + * %LIVEUPDATE_IOCTL_FD_PRESERVE call before the live update. On success, + * the kernel restores the state (saved during the PREPARE/FREEZE phases) + * associated with the token and populates the @fd field with a new file + * descriptor referencing the restored resource in the current (new) kernel. + * This operation must be performed *before* signaling completion via + * %LIVEUPDATE_IOCTL_FINISH. + * + * Return: 0 on success, negative error code on failure (e.g., invalid token). + */ +struct liveupdate_session_retrieve_fd { + __u32 size; + __s32 fd; + __aligned_u64 token; +}; + +#define LIVEUPDATE_SESSION_RETRIEVE_FD \ + _IO(LIVEUPDATE_IOCTL_TYPE, LIVEUPDATE_CMD_SESSION_RETRIEVE_FD) + +/** + * struct liveupdate_session_finish - ioctl(LIVEUPDATE_SESSION_FINISH) + * @size: Input; sizeof(struct liveupdate_session_finish) + * @reserved: Input; Must be zero. Reserved for future use. + * + * Signals the completion of the restoration process for a retrieved session. + * This is the final operation that should be performed on a session file + * descriptor after a live update. + * + * This ioctl must be called once all required file descriptors for the session + * have been successfully retrieved (using %LIVEUPDATE_SESSION_RETRIEVE_FD) and + * are fully restored from the userspace and kernel perspective. + * + * Upon success, the kernel releases its ownership of the preserved resources + * associated with this session. This allows internal resources to be freed, + * typically by decrementing reference counts on the underlying preserved + * objects. + * + * If this operation fails, the resources remain preserved in memory. Userspace + * may attempt to call finish again. The resources will otherwise be reset + * during the next live update cycle. + * + * Return: 0 on success, negative error code on failure. + */ +struct liveupdate_session_finish { + __u32 size; + __u32 reserved; +}; + +#define LIVEUPDATE_SESSION_FINISH \ + _IO(LIVEUPDATE_IOCTL_TYPE, LIVEUPDATE_CMD_SESSION_FINISH) + #endif /* _UAPI_LIVEUPDATE_H */ diff --git a/kernel/liveupdate/luo_session.c b/kernel/liveupdate/luo_session.c index 3a031446d3a4..dbdbc3bd7929 100644 --- a/kernel/liveupdate/luo_session.c +++ b/kernel/liveupdate/luo_session.c @@ -125,6 +125,8 @@ static struct luo_session *luo_session_alloc(const char *name) return ERR_PTR(-ENOMEM); strscpy(session->name, name, sizeof(session->name)); + INIT_LIST_HEAD(&session->file_set.files_list); + luo_file_set_init(&session->file_set); INIT_LIST_HEAD(&session->list); mutex_init(&session->mutex); @@ -133,6 +135,7 @@ static struct luo_session *luo_session_alloc(const char *name) static void luo_session_free(struct luo_session *session) { + luo_file_set_destroy(&session->file_set); mutex_destroy(&session->mutex); kfree(session); } @@ -177,16 +180,46 @@ static void luo_session_remove(struct luo_session_header *sh, sh->count--; } +static int luo_session_finish_one(struct luo_session *session) +{ + guard(mutex)(&session->mutex); + return luo_file_finish(&session->file_set); +} + +static void luo_session_unfreeze_one(struct luo_session *session, + struct luo_session_ser *ser) +{ + guard(mutex)(&session->mutex); + luo_file_unfreeze(&session->file_set, &ser->file_set_ser); +} + +static int luo_session_freeze_one(struct luo_session *session, + struct luo_session_ser *ser) +{ + guard(mutex)(&session->mutex); + return luo_file_freeze(&session->file_set, &ser->file_set_ser); +} + static int luo_session_release(struct inode *inodep, struct file *filep) { struct luo_session *session = filep->private_data; struct luo_session_header *sh; /* If retrieved is set, it means this session is from incoming list */ - if (session->retrieved) + if (session->retrieved) { + int err = luo_session_finish_one(session); + + if (err) { + pr_warn("Unable to finish session [%s] on release\n", + session->name); + return err; + } sh = &luo_session_global.incoming; - else + } else { + scoped_guard(mutex, &session->mutex) + luo_file_unpreserve_files(&session->file_set); sh = &luo_session_global.outgoing; + } luo_session_remove(sh, session); luo_session_free(session); @@ -194,9 +227,140 @@ static int luo_session_release(struct inode *inodep, struct file *filep) return 0; } +static int luo_session_preserve_fd(struct luo_session *session, + struct luo_ucmd *ucmd) +{ + struct liveupdate_session_preserve_fd *argp = ucmd->cmd; + int err; + + guard(mutex)(&session->mutex); + err = luo_preserve_file(&session->file_set, argp->token, argp->fd); + if (err) + return err; + + err = luo_ucmd_respond(ucmd, sizeof(*argp)); + if (err) + pr_warn("The file was successfully preserved, but response to user failed\n"); + + return err; +} + +static int luo_session_retrieve_fd(struct luo_session *session, + struct luo_ucmd *ucmd) +{ + struct liveupdate_session_retrieve_fd *argp = ucmd->cmd; + struct file *file; + int err; + + argp->fd = get_unused_fd_flags(O_CLOEXEC); + if (argp->fd < 0) + return argp->fd; + + guard(mutex)(&session->mutex); + err = luo_retrieve_file(&session->file_set, argp->token, &file); + if (err < 0) + goto err_put_fd; + + err = luo_ucmd_respond(ucmd, sizeof(*argp)); + if (err) + goto err_put_file; + + fd_install(argp->fd, file); + + return 0; + +err_put_file: + fput(file); +err_put_fd: + put_unused_fd(argp->fd); + + return err; +} + +static int luo_session_finish(struct luo_session *session, + struct luo_ucmd *ucmd) +{ + struct liveupdate_session_finish *argp = ucmd->cmd; + int err = luo_session_finish_one(session); + + if (err) + return err; + + return luo_ucmd_respond(ucmd, sizeof(*argp)); +} + +union ucmd_buffer { + struct liveupdate_session_finish finish; + struct liveupdate_session_preserve_fd preserve; + struct liveupdate_session_retrieve_fd retrieve; +}; + +struct luo_ioctl_op { + unsigned int size; + unsigned int min_size; + unsigned int ioctl_num; + int (*execute)(struct luo_session *session, struct luo_ucmd *ucmd); +}; + +#define IOCTL_OP(_ioctl, _fn, _struct, _last) \ + [_IOC_NR(_ioctl) - LIVEUPDATE_CMD_SESSION_BASE] = { \ + .size = sizeof(_struct) + \ + BUILD_BUG_ON_ZERO(sizeof(union ucmd_buffer) < \ + sizeof(_struct)), \ + .min_size = offsetofend(_struct, _last), \ + .ioctl_num = _ioctl, \ + .execute = _fn, \ + } + +static const struct luo_ioctl_op luo_session_ioctl_ops[] = { + IOCTL_OP(LIVEUPDATE_SESSION_FINISH, luo_session_finish, + struct liveupdate_session_finish, reserved), + IOCTL_OP(LIVEUPDATE_SESSION_PRESERVE_FD, luo_session_preserve_fd, + struct liveupdate_session_preserve_fd, token), + IOCTL_OP(LIVEUPDATE_SESSION_RETRIEVE_FD, luo_session_retrieve_fd, + struct liveupdate_session_retrieve_fd, token), +}; + +static long luo_session_ioctl(struct file *filep, unsigned int cmd, + unsigned long arg) +{ + struct luo_session *session = filep->private_data; + const struct luo_ioctl_op *op; + struct luo_ucmd ucmd = {}; + union ucmd_buffer buf; + unsigned int nr; + int ret; + + nr = _IOC_NR(cmd); + if (nr < LIVEUPDATE_CMD_SESSION_BASE || (nr - LIVEUPDATE_CMD_SESSION_BASE) >= + ARRAY_SIZE(luo_session_ioctl_ops)) { + return -EINVAL; + } + + ucmd.ubuffer = (void __user *)arg; + ret = get_user(ucmd.user_size, (u32 __user *)ucmd.ubuffer); + if (ret) + return ret; + + op = &luo_session_ioctl_ops[nr - LIVEUPDATE_CMD_SESSION_BASE]; + if (op->ioctl_num != cmd) + return -ENOIOCTLCMD; + if (ucmd.user_size < op->min_size) + return -EINVAL; + + ucmd.cmd = &buf; + ret = copy_struct_from_user(ucmd.cmd, op->size, ucmd.ubuffer, + ucmd.user_size); + if (ret) + return ret; + + return op->execute(session, &ucmd); +} + static const struct file_operations luo_session_fops = { .owner = THIS_MODULE, .release = luo_session_release, + .unlocked_ioctl = luo_session_ioctl, }; /* Create a "struct file" for session */ @@ -392,6 +556,11 @@ int luo_session_deserialize(void) luo_session_free(session); return err; } + + scoped_guard(mutex, &session->mutex) { + luo_file_deserialize(&session->file_set, + &sh->ser[i].file_set_ser); + } } kho_restore_free(sh->header_ser); @@ -406,9 +575,14 @@ int luo_session_serialize(void) struct luo_session_header *sh = &luo_session_global.outgoing; struct luo_session *session; int i = 0; + int err; guard(rwsem_write)(&sh->rwsem); list_for_each_entry(session, &sh->list, list) { + err = luo_session_freeze_one(session, &sh->ser[i]); + if (err) + goto err_undo; + strscpy(sh->ser[i].name, session->name, sizeof(sh->ser[i].name)); i++; @@ -416,6 +590,15 @@ int luo_session_serialize(void) sh->header_ser->count = sh->count; return 0; + +err_undo: + list_for_each_entry_continue_reverse(session, &sh->list, list) { + i--; + luo_session_unfreeze_one(session, &sh->ser[i]); + memset(sh->ser[i].name, 0, sizeof(sh->ser[i].name)); + } + + return err; } /** -- cgit v1.2.3 From 3fa805c37dd4d3e72ae5c58800f3f46ab3ca1f70 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Fri, 10 Oct 2025 03:36:50 -0700 Subject: vmcoreinfo: track and log recoverable hardware errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce a generic infrastructure for tracking recoverable hardware errors (HW errors that are visible to the OS but does not cause a panic) and record them for vmcore consumption. This aids post-mortem crash analysis tools by preserving a count and timestamp for the last occurrence of such errors. On the other side, correctable errors, which the OS typically remains unaware of because the underlying hardware handles them transparently, are less relevant for crash dump and therefore are NOT tracked in this infrastructure. Add centralized logging for sources of recoverable hardware errors based on the subsystem it has been notified. hwerror_data is write-only at kernel runtime, and it is meant to be read from vmcore using tools like crash/drgn. For example, this is how it looks like when opening the crashdump from drgn. >>> prog['hwerror_data'] (struct hwerror_info[1]){ { .count = (int)844, .timestamp = (time64_t)1752852018, }, ... This helps fleet operators quickly triage whether a crash may be influenced by hardware recoverable errors (which executes a uncommon code path in the kernel), especially when recoverable errors occurred shortly before a panic, such as the bug fixed by commit ee62ce7a1d90 ("page_pool: Track DMA-mapped pages and unmap them when destroying the pool") This is not intended to replace full hardware diagnostics but provides a fast way to correlate hardware events with kernel panics quickly. Rare machine check exceptions—like those indicated by mce_flags.p5 or mce_flags.winchip—are not accounted for in this method, as they fall outside the intended usage scope for this feature's user base. [leitao@debian.org: add hw-recoverable-errors to toctree] Link: https://lkml.kernel.org/r/20251127-vmcoreinfo_fix-v1-1-26f5b1c43da9@debian.org Link: https://lkml.kernel.org/r/20251010-vmcore_hw_error-v5-1-636ede3efe44@debian.org Signed-off-by: Breno Leitao Suggested-by: Tony Luck Suggested-by: Shuai Xue Reviewed-by: Shuai Xue Reviewed-by: Hanjun Guo [APEI] Cc: Bjorn Helgaas Cc: Bob Moore Cc: Borislav Betkov Cc: "H. Peter Anvin" Cc: Ingo Molnar Cc: James Morse Cc: Konrad Rzessutek Wilk Cc: Len Brown Cc: Mahesh Salgaonkar Cc: Mauro Carvalho Chehab Cc: "Oliver O'Halloran" Cc: Omar Sandoval Cc: Thomas Gleinxer Signed-off-by: Andrew Morton --- Documentation/driver-api/hw-recoverable-errors.rst | 60 ++++++++++++++++++++++ Documentation/driver-api/index.rst | 1 + arch/x86/kernel/cpu/mce/core.c | 4 ++ drivers/acpi/apei/ghes.c | 36 +++++++++++++ drivers/pci/pcie/aer.c | 2 + include/linux/vmcore_info.h | 8 +++ include/uapi/linux/vmcore.h | 9 ++++ kernel/vmcore_info.c | 17 ++++++ 8 files changed, 137 insertions(+) create mode 100644 Documentation/driver-api/hw-recoverable-errors.rst (limited to 'include/uapi/linux') diff --git a/Documentation/driver-api/hw-recoverable-errors.rst b/Documentation/driver-api/hw-recoverable-errors.rst new file mode 100644 index 000000000000..fc526c3454bd --- /dev/null +++ b/Documentation/driver-api/hw-recoverable-errors.rst @@ -0,0 +1,60 @@ +.. SPDX-License-Identifier: GPL-2.0 + +================================================= +Recoverable Hardware Error Tracking in vmcoreinfo +================================================= + +Overview +-------- + +This feature provides a generic infrastructure within the Linux kernel to track +and log recoverable hardware errors. These are hardware recoverable errors +visible that might not cause immediate panics but may influence health, mainly +because new code path will be executed in the kernel. + +By recording counts and timestamps of recoverable errors into the vmcoreinfo +crash dump notes, this infrastructure aids post-mortem crash analysis tools in +correlating hardware events with kernel failures. This enables faster triage +and better understanding of root causes, especially in large-scale cloud +environments where hardware issues are common. + +Benefits +-------- + +- Facilitates correlation of hardware recoverable errors with kernel panics or + unusual code paths that lead to system crashes. +- Provides operators and cloud providers quick insights, improving reliability + and reducing troubleshooting time. +- Complements existing full hardware diagnostics without replacing them. + +Data Exposure and Consumption +----------------------------- + +- The tracked error data consists of per-error-type counts and timestamps of + last occurrence. +- This data is stored in the `hwerror_data` array, categorized by error source + types like CPU, memory, PCI, CXL, and others. +- It is exposed via vmcoreinfo crash dump notes and can be read using tools + like `crash`, `drgn`, or other kernel crash analysis utilities. +- There is no other way to read these data other than from crash dumps. +- These errors are divided by area, which includes CPU, Memory, PCI, CXL and + others. + +Typical usage example (in drgn REPL): + +.. code-block:: python + + >>> prog['hwerror_data'] + (struct hwerror_info[HWERR_RECOV_MAX]){ + { + .count = (int)844, + .timestamp = (time64_t)1752852018, + }, + ... + } + +Enabling +-------- + +- This feature is enabled when CONFIG_VMCORE_INFO is set. + diff --git a/Documentation/driver-api/index.rst b/Documentation/driver-api/index.rst index 3e2a270bd828..a35705b44799 100644 --- a/Documentation/driver-api/index.rst +++ b/Documentation/driver-api/index.rst @@ -96,6 +96,7 @@ Subsystem-specific APIs gpio/index hsi hte/index + hw-recoverable-errors i2c iio/index infiniband diff --git a/arch/x86/kernel/cpu/mce/core.c b/arch/x86/kernel/cpu/mce/core.c index 460e90a1a0b1..08adbf4cd6ed 100644 --- a/arch/x86/kernel/cpu/mce/core.c +++ b/arch/x86/kernel/cpu/mce/core.c @@ -45,6 +45,7 @@ #include #include #include +#include #include #include @@ -1700,6 +1701,9 @@ noinstr void do_machine_check(struct pt_regs *regs) } out: + /* Given it didn't panic, mark it as recoverable */ + hwerr_log_error_type(HWERR_RECOV_OTHERS); + instrumentation_end(); clear: diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c index 97ee19f2cae0..92b0e3c391b2 100644 --- a/drivers/acpi/apei/ghes.c +++ b/drivers/acpi/apei/ghes.c @@ -43,6 +43,7 @@ #include #include #include +#include #include #include @@ -867,6 +868,40 @@ int cxl_cper_kfifo_get(struct cxl_cper_work_data *wd) } EXPORT_SYMBOL_NS_GPL(cxl_cper_kfifo_get, "CXL"); +static void ghes_log_hwerr(int sev, guid_t *sec_type) +{ + if (sev != CPER_SEV_RECOVERABLE) + return; + + if (guid_equal(sec_type, &CPER_SEC_PROC_ARM) || + guid_equal(sec_type, &CPER_SEC_PROC_GENERIC) || + guid_equal(sec_type, &CPER_SEC_PROC_IA)) { + hwerr_log_error_type(HWERR_RECOV_CPU); + return; + } + + if (guid_equal(sec_type, &CPER_SEC_CXL_PROT_ERR) || + guid_equal(sec_type, &CPER_SEC_CXL_GEN_MEDIA_GUID) || + guid_equal(sec_type, &CPER_SEC_CXL_DRAM_GUID) || + guid_equal(sec_type, &CPER_SEC_CXL_MEM_MODULE_GUID)) { + hwerr_log_error_type(HWERR_RECOV_CXL); + return; + } + + if (guid_equal(sec_type, &CPER_SEC_PCIE) || + guid_equal(sec_type, &CPER_SEC_PCI_X_BUS)) { + hwerr_log_error_type(HWERR_RECOV_PCI); + return; + } + + if (guid_equal(sec_type, &CPER_SEC_PLATFORM_MEM)) { + hwerr_log_error_type(HWERR_RECOV_MEMORY); + return; + } + + hwerr_log_error_type(HWERR_RECOV_OTHERS); +} + static void ghes_do_proc(struct ghes *ghes, const struct acpi_hest_generic_status *estatus) { @@ -888,6 +923,7 @@ static void ghes_do_proc(struct ghes *ghes, if (gdata->validation_bits & CPER_SEC_VALID_FRU_TEXT) fru_text = gdata->fru_text; + ghes_log_hwerr(sev, sec_type); if (guid_equal(sec_type, &CPER_SEC_PLATFORM_MEM)) { struct cper_sec_mem_err *mem_err = acpi_hest_get_payload(gdata); diff --git a/drivers/pci/pcie/aer.c b/drivers/pci/pcie/aer.c index 0b5ed4722ac3..e0bcaa896803 100644 --- a/drivers/pci/pcie/aer.c +++ b/drivers/pci/pcie/aer.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -765,6 +766,7 @@ static void pci_dev_aer_stats_incr(struct pci_dev *pdev, break; case AER_NONFATAL: aer_info->dev_total_nonfatal_errs++; + hwerr_log_error_type(HWERR_RECOV_PCI); counter = &aer_info->dev_nonfatal_errs[0]; max = AER_MAX_TYPEOF_UNCOR_ERRS; break; diff --git a/include/linux/vmcore_info.h b/include/linux/vmcore_info.h index 37e003ae5262..e71518caacdf 100644 --- a/include/linux/vmcore_info.h +++ b/include/linux/vmcore_info.h @@ -5,6 +5,7 @@ #include #include #include +#include #define CRASH_CORE_NOTE_HEAD_BYTES ALIGN(sizeof(struct elf_note), 4) #define CRASH_CORE_NOTE_NAME_BYTES ALIGN(sizeof(NN_PRSTATUS), 4) @@ -77,4 +78,11 @@ extern u32 *vmcoreinfo_note; Elf_Word *append_elf_note(Elf_Word *buf, char *name, unsigned int type, void *data, size_t data_len); void final_note(Elf_Word *buf); + +#ifdef CONFIG_VMCORE_INFO +void hwerr_log_error_type(enum hwerr_error_type src); +#else +static inline void hwerr_log_error_type(enum hwerr_error_type src) {}; +#endif + #endif /* LINUX_VMCORE_INFO_H */ diff --git a/include/uapi/linux/vmcore.h b/include/uapi/linux/vmcore.h index 3e9da91866ff..2ba89fafa518 100644 --- a/include/uapi/linux/vmcore.h +++ b/include/uapi/linux/vmcore.h @@ -15,4 +15,13 @@ struct vmcoredd_header { __u8 dump_name[VMCOREDD_MAX_NAME_BYTES]; /* Device dump's name */ }; +enum hwerr_error_type { + HWERR_RECOV_CPU, + HWERR_RECOV_MEMORY, + HWERR_RECOV_PCI, + HWERR_RECOV_CXL, + HWERR_RECOV_OTHERS, + HWERR_RECOV_MAX, +}; + #endif /* _UAPI_VMCORE_H */ diff --git a/kernel/vmcore_info.c b/kernel/vmcore_info.c index e066d31d08f8..fe9bf8db1922 100644 --- a/kernel/vmcore_info.c +++ b/kernel/vmcore_info.c @@ -31,6 +31,13 @@ u32 *vmcoreinfo_note; /* trusted vmcoreinfo, e.g. we can make a copy in the crash memory */ static unsigned char *vmcoreinfo_data_safecopy; +struct hwerr_info { + atomic_t count; + time64_t timestamp; +}; + +static struct hwerr_info hwerr_data[HWERR_RECOV_MAX]; + Elf_Word *append_elf_note(Elf_Word *buf, char *name, unsigned int type, void *data, size_t data_len) { @@ -118,6 +125,16 @@ phys_addr_t __weak paddr_vmcoreinfo_note(void) } EXPORT_SYMBOL(paddr_vmcoreinfo_note); +void hwerr_log_error_type(enum hwerr_error_type src) +{ + if (src < 0 || src >= HWERR_RECOV_MAX) + return; + + atomic_inc(&hwerr_data[src].count); + WRITE_ONCE(hwerr_data[src].timestamp, ktime_get_real_seconds()); +} +EXPORT_SYMBOL_GPL(hwerr_log_error_type); + static int __init crash_save_vmcoreinfo_init(void) { vmcoreinfo_data = (unsigned char *)get_zeroed_page(GFP_KERNEL); -- cgit v1.2.3 From c4f0ab06e1e0c1331e6febd03538a7f621f15134 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sat, 1 Nov 2025 12:20:50 -0700 Subject: netfilter: ip6t_srh: fix UAPI kernel-doc comments format Fix the kernel-doc format for struct members to be "@member" instead of "@ member" to avoid kernel-doc warnings. Warning: ip6t_srh.h:60 struct member 'next_hdr' not described in 'ip6t_srh' Warning: ip6t_srh.h:60 struct member 'hdr_len' not described in 'ip6t_srh' Warning: ip6t_srh.h:60 struct member 'segs_left' not described in 'ip6t_srh' Warning: ip6t_srh.h:60 struct member 'last_entry' not described in 'ip6t_srh' Warning: ip6t_srh.h:60 struct member 'tag' not described in 'ip6t_srh' Warning: ip6t_srh.h:60 struct member 'mt_flags' not described in 'ip6t_srh' Warning: ip6t_srh.h:60 struct member 'mt_invflags' not described in 'ip6t_srh' Warning: ip6t_srh.h:93 struct member 'next_hdr' not described in 'ip6t_srh1' Warning: ip6t_srh.h:93 struct member 'hdr_len' not described in 'ip6t_srh1' Warning: ip6t_srh.h:93 struct member 'segs_left' not described in 'ip6t_srh1' Warning: ip6t_srh.h:93 struct member 'last_entry' not described in 'ip6t_srh1' Warning: ip6t_srh.h:93 struct member 'tag' not described in 'ip6t_srh1' Warning: ip6t_srh.h:93 struct member 'psid_addr' not described in 'ip6t_srh1' Warning: ip6t_srh.h:93 struct member 'nsid_addr' not described in 'ip6t_srh1' Warning: ip6t_srh.h:93 struct member 'lsid_addr' not described in 'ip6t_srh1' Warning: ip6t_srh.h:93 struct member 'psid_msk' not described in 'ip6t_srh1' Warning: ip6t_srh.h:93 struct member 'nsid_msk' not described in 'ip6t_srh1' Warning: ip6t_srh.h:93 struct member 'lsid_msk' not described in 'ip6t_srh1' Warning: ip6t_srh.h:93 struct member 'mt_flags' not described in 'ip6t_srh1' Warning: ip6t_srh.h:93 struct member 'mt_invflags' not described in 'ip6t_srh1' Signed-off-by: Randy Dunlap Signed-off-by: Florian Westphal Signed-off-by: Pablo Neira Ayuso --- include/uapi/linux/netfilter_ipv6/ip6t_srh.h | 40 ++++++++++++++-------------- 1 file changed, 20 insertions(+), 20 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/netfilter_ipv6/ip6t_srh.h b/include/uapi/linux/netfilter_ipv6/ip6t_srh.h index 54ed83360dac..80c66c8ece82 100644 --- a/include/uapi/linux/netfilter_ipv6/ip6t_srh.h +++ b/include/uapi/linux/netfilter_ipv6/ip6t_srh.h @@ -41,13 +41,13 @@ /** * struct ip6t_srh - SRH match options - * @ next_hdr: Next header field of SRH - * @ hdr_len: Extension header length field of SRH - * @ segs_left: Segments left field of SRH - * @ last_entry: Last entry field of SRH - * @ tag: Tag field of SRH - * @ mt_flags: match options - * @ mt_invflags: Invert the sense of match options + * @next_hdr: Next header field of SRH + * @hdr_len: Extension header length field of SRH + * @segs_left: Segments left field of SRH + * @last_entry: Last entry field of SRH + * @tag: Tag field of SRH + * @mt_flags: match options + * @mt_invflags: Invert the sense of match options */ struct ip6t_srh { @@ -62,19 +62,19 @@ struct ip6t_srh { /** * struct ip6t_srh1 - SRH match options (revision 1) - * @ next_hdr: Next header field of SRH - * @ hdr_len: Extension header length field of SRH - * @ segs_left: Segments left field of SRH - * @ last_entry: Last entry field of SRH - * @ tag: Tag field of SRH - * @ psid_addr: Address of previous SID in SRH SID list - * @ nsid_addr: Address of NEXT SID in SRH SID list - * @ lsid_addr: Address of LAST SID in SRH SID list - * @ psid_msk: Mask of previous SID in SRH SID list - * @ nsid_msk: Mask of next SID in SRH SID list - * @ lsid_msk: MAsk of last SID in SRH SID list - * @ mt_flags: match options - * @ mt_invflags: Invert the sense of match options + * @next_hdr: Next header field of SRH + * @hdr_len: Extension header length field of SRH + * @segs_left: Segments left field of SRH + * @last_entry: Last entry field of SRH + * @tag: Tag field of SRH + * @psid_addr: Address of previous SID in SRH SID list + * @nsid_addr: Address of NEXT SID in SRH SID list + * @lsid_addr: Address of LAST SID in SRH SID list + * @psid_msk: Mask of previous SID in SRH SID list + * @nsid_msk: Mask of next SID in SRH SID list + * @lsid_msk: MAsk of last SID in SRH SID list + * @mt_flags: match options + * @mt_invflags: Invert the sense of match options */ struct ip6t_srh1 { -- cgit v1.2.3 From d3a439e55c193b930e0007967cf8d7a29890449b Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sat, 1 Nov 2025 12:20:38 -0700 Subject: netfilter: nf_tables: improve UAPI kernel-doc comments In include/uapi/linux/netfilter/nf_tables.h, correct the kernel-doc comments for mistyped enum names and enum values to avoid these kernel-doc warnings and improve the documentation: nf_tables.h:896: warning: Enum value 'NFT_EXTHDR_OP_TCPOPT' not described in enum 'nft_exthdr_op' nf_tables.h:896: warning: Excess enum value 'NFT_EXTHDR_OP_TCP' description in 'nft_exthdr_op' nf_tables.h:1210: warning: expecting prototype for enum nft_flow_attributes. Prototype was for enum nft_offload_attributes instead nf_tables.h:1428: warning: expecting prototype for enum nft_reject_code. Prototype was for enum nft_reject_inet_code instead (add beginning '@' to each enum value description:) nf_tables.h:1493: warning: Enum value 'NFTA_TPROXY_FAMILY' not described in enum 'nft_tproxy_attributes' nf_tables.h:1493: warning: Enum value 'NFTA_TPROXY_REG_ADDR' not described in enum 'nft_tproxy_attributes' nf_tables.h:1493: warning: Enum value 'NFTA_TPROXY_REG_PORT' not described in enum 'nft_tproxy_attributes' nf_tables.h:1796: warning: expecting prototype for enum nft_device_attributes. Prototype was for enum nft_devices_attributes instead Signed-off-by: Randy Dunlap Signed-off-by: Florian Westphal Signed-off-by: Pablo Neira Ayuso --- include/uapi/linux/netfilter/nf_tables.h | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/netfilter/nf_tables.h b/include/uapi/linux/netfilter/nf_tables.h index 7c0c915f0306..45c71f7d21c2 100644 --- a/include/uapi/linux/netfilter/nf_tables.h +++ b/include/uapi/linux/netfilter/nf_tables.h @@ -881,7 +881,7 @@ enum nft_exthdr_flags { * enum nft_exthdr_op - nf_tables match options * * @NFT_EXTHDR_OP_IPV6: match against ipv6 extension headers - * @NFT_EXTHDR_OP_TCP: match against tcp options + * @NFT_EXTHDR_OP_TCPOPT: match against tcp options * @NFT_EXTHDR_OP_IPV4: match against ipv4 options * @NFT_EXTHDR_OP_SCTP: match against sctp chunks * @NFT_EXTHDR_OP_DCCP: match against dccp otions @@ -1200,7 +1200,7 @@ enum nft_ct_attributes { #define NFTA_CT_MAX (__NFTA_CT_MAX - 1) /** - * enum nft_flow_attributes - ct offload expression attributes + * enum nft_offload_attributes - ct offload expression attributes * @NFTA_FLOW_TABLE_NAME: flow table name (NLA_STRING) */ enum nft_offload_attributes { @@ -1410,7 +1410,7 @@ enum nft_reject_types { }; /** - * enum nft_reject_code - Generic reject codes for IPv4/IPv6 + * enum nft_reject_inet_code - Generic reject codes for IPv4/IPv6 * * @NFT_REJECT_ICMPX_NO_ROUTE: no route to host / network unreachable * @NFT_REJECT_ICMPX_PORT_UNREACH: port unreachable @@ -1480,9 +1480,9 @@ enum nft_nat_attributes { /** * enum nft_tproxy_attributes - nf_tables tproxy expression netlink attributes * - * NFTA_TPROXY_FAMILY: Target address family (NLA_U32: nft_registers) - * NFTA_TPROXY_REG_ADDR: Target address register (NLA_U32: nft_registers) - * NFTA_TPROXY_REG_PORT: Target port register (NLA_U32: nft_registers) + * @NFTA_TPROXY_FAMILY: Target address family (NLA_U32: nft_registers) + * @NFTA_TPROXY_REG_ADDR: Target address register (NLA_U32: nft_registers) + * @NFTA_TPROXY_REG_PORT: Target port register (NLA_U32: nft_registers) */ enum nft_tproxy_attributes { NFTA_TPROXY_UNSPEC, @@ -1783,7 +1783,7 @@ enum nft_synproxy_attributes { #define NFTA_SYNPROXY_MAX (__NFTA_SYNPROXY_MAX - 1) /** - * enum nft_device_attributes - nf_tables device netlink attributes + * enum nft_devices_attributes - nf_tables device netlink attributes * * @NFTA_DEVICE_NAME: name of this device (NLA_STRING) * @NFTA_DEVICE_PREFIX: device name prefix, a simple wildcard (NLA_STRING) -- cgit v1.2.3 From 6557cae0a2a1952645e5df50e1d6eb7267ea2131 Mon Sep 17 00:00:00 2001 From: Peter Enderborg Date: Wed, 26 Nov 2025 14:54:06 +0100 Subject: if_ether.h: Clarify ethertype validity for gsw1xx dsa This 0x88C3 is registered to Infineon Technologies Corporate Research ST and are used by MaxLinear. Infineon made a spin off called Lantiq. Lantiq was acquired by Intel MaxLinear acquired Intels Connected Home division. The product FAQ from MaxLinear describes it's history from the F24S. The driver for the gsw1xx is based on Lantiq showing it's similarities. Ref https://standards-oui.ieee.org/ethertype/eth.txt Signed-off-by: Peter Enderborg Reviewed-by: Andrew Lunn Signed-off-by: Jakub Kicinski --- include/uapi/linux/if_ether.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/if_ether.h b/include/uapi/linux/if_ether.h index 2c93b7b731c8..df9d44a11540 100644 --- a/include/uapi/linux/if_ether.h +++ b/include/uapi/linux/if_ether.h @@ -92,7 +92,9 @@ #define ETH_P_ETHERCAT 0x88A4 /* EtherCAT */ #define ETH_P_8021AD 0x88A8 /* 802.1ad Service VLAN */ #define ETH_P_802_EX1 0x88B5 /* 802.1 Local Experimental 1. */ -#define ETH_P_MXLGSW 0x88C3 /* MaxLinear GSW DSA [ NOT AN OFFICIALLY REGISTERED ID ] */ +#define ETH_P_MXLGSW 0x88C3 /* Infineon Technologies Corporate Research ST + * Used by MaxLinear GSW DSA + */ #define ETH_P_PREAUTH 0x88C7 /* 802.11 Preauthentication */ #define ETH_P_TIPC 0x88CA /* TIPC */ #define ETH_P_LLDP 0x88CC /* Link Layer Discovery Protocol */ -- cgit v1.2.3 From 4be9e04ebf75a5c4478c1c6295e2122e5dc98f5f Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Fri, 28 Nov 2025 10:55:09 +0100 Subject: vfs: add needed headers for new struct delegation definition The definition of struct delegation uses stdint.h integer types. Add the necessary headers to ensure that always works. Fixes: 1602bad16d7d ("vfs: expose delegation support to userland") Signed-off-by: Jeff Layton Signed-off-by: Christian Brauner --- include/uapi/linux/fcntl.h | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/fcntl.h b/include/uapi/linux/fcntl.h index 008fac15e573..5e277fd955aa 100644 --- a/include/uapi/linux/fcntl.h +++ b/include/uapi/linux/fcntl.h @@ -4,6 +4,11 @@ #include #include +#ifdef __KERNEL__ +#include +#else +#include +#endif #define F_SETLEASE (F_LINUX_SPECIFIC_BASE + 0) #define F_GETLEASE (F_LINUX_SPECIFIC_BASE + 1) -- cgit v1.2.3 From 414690746d2da0dc9a931f8c02d83e5834141251 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Mon, 24 Nov 2025 18:28:08 -0800 Subject: i2c: i2c.h: fix a bad kernel-doc line Change an empty line into a blank kernel-doc line to prevent a kernel-doc warning: Warning: ../include/uapi/linux/i2c.h:38 bad line: Fixes: bfb3939c51d5 ("i2c: refactor documentation of struct i2c_msg") Signed-off-by: Randy Dunlap Signed-off-by: Wolfram Sang --- include/uapi/linux/i2c.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/i2c.h b/include/uapi/linux/i2c.h index a2db2a56c8b0..2a226657d9f8 100644 --- a/include/uapi/linux/i2c.h +++ b/include/uapi/linux/i2c.h @@ -36,7 +36,7 @@ * * Only if I2C_FUNC_NOSTART is set: * %I2C_M_NOSTART: skip repeated start sequence - + * * Only if I2C_FUNC_PROTOCOL_MANGLING is set: * %I2C_M_NO_RD_ACK: in a read message, master ACK/NACK bit is skipped * %I2C_M_IGNORE_NAK: treat NACK from client as ACK -- cgit v1.2.3 From 205dd7a5d6ad6f4c8e8fcd3c3b95a7c0e7067fee Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Tue, 18 Nov 2025 18:06:31 -0500 Subject: virtio_pci: drop kernel.h virtio UAPI headers really have no business pulling in kernel.h Replace it with const.h which seems to be what's needed for __KERNEL_DIV_ROUND_UP. Fixes: 7c1ae151e812 ("virtio_pci: Introduce device parts access commands") Cc: Yishai Hadas Cc: Alex Williamson Message-ID: <7a73b6c6af67e13b86633cd7bf11ad56b5d9809b.1763535341.git.mst@redhat.com> Signed-off-by: Michael S. Tsirkin --- include/uapi/linux/virtio_pci.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/virtio_pci.h b/include/uapi/linux/virtio_pci.h index c691ac210ce2..e732e3456e27 100644 --- a/include/uapi/linux/virtio_pci.h +++ b/include/uapi/linux/virtio_pci.h @@ -40,7 +40,7 @@ #define _LINUX_VIRTIO_PCI_H #include -#include +#include #ifndef VIRTIO_PCI_NO_LEGACY -- cgit v1.2.3 From 6b0f4ca079dbe6ae4aa57e529d67c7dc00d63577 Mon Sep 17 00:00:00 2001 From: Asbjørn Sloth Tønnesen Date: Wed, 26 Nov 2025 17:35:37 +0000 Subject: wireguard: netlink: add YNL specification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch adds a near[1] complete YNL specification for WireGuard, documenting the protocol in a machine-readable format, rather than comments in wireguard.h, and eases usage from C and non-C programming languages alike. The generated C library will be featured in a later patch, so in this patch I will use the in-kernel python client for examples. This makes the documentation in the UAPI header redundant, it is therefore removed. The in-line documentation in the spec is based on the existing comment in wireguard.h, and once released it will be available in the kernel documentation at: https://docs.kernel.org/netlink/specs/wireguard.html (until then run: make htmldocs) Generate wireguard.rst from this spec: $ make -C tools/net/ynl/generated/ wireguard.rst Query wireguard interface through pyynl: $ sudo ./tools/net/ynl/pyynl/cli.py --family wireguard \ --dump get-device \ --json '{"ifindex":3}' [{'fwmark': 0, 'ifindex': 3, 'ifname': 'wg-test', 'listen-port': 54318, 'peers': [{0: {'allowedips': [{0: {'cidr-mask': 0, 'family': 2, 'ipaddr': '0.0.0.0'}}, {0: {'cidr-mask': 0, 'family': 10, 'ipaddr': '::'}}], 'endpoint': b'[...]', 'last-handshake-time': {'nsec': 42, 'sec': 42}, 'persistent-keepalive-interval': 42, 'preshared-key': '[...]', 'protocol-version': 1, 'public-key': '[...]', 'rx-bytes': 42, 'tx-bytes': 42}}], 'private-key': '[...]', 'public-key': '[...]'}] Add another allowed IP prefix: $ sudo ./tools/net/ynl/pyynl/cli.py --family wireguard \ --do set-device --json '{"ifindex":3,"peers":[ {"public-key":"6a df b1 83 a4 ..","allowedips":[ {"cidr-mask":0,"family":10,"ipaddr":"::"}]}]}' [1] As can be seen above, the "endpoint" is only dumped as binary data, as it can't be fully described in YNL. It's either a struct sockaddr_in or struct sockaddr_in6 depending on the attribute length. Signed-off-by: Asbjørn Sloth Tønnesen Signed-off-by: Jason A. Donenfeld --- Documentation/netlink/specs/wireguard.yaml | 298 +++++++++++++++++++++++++++++ MAINTAINERS | 1 + include/uapi/linux/wireguard.h | 129 ------------- 3 files changed, 299 insertions(+), 129 deletions(-) create mode 100644 Documentation/netlink/specs/wireguard.yaml (limited to 'include/uapi/linux') diff --git a/Documentation/netlink/specs/wireguard.yaml b/Documentation/netlink/specs/wireguard.yaml new file mode 100644 index 000000000000..30479fc6bb69 --- /dev/null +++ b/Documentation/netlink/specs/wireguard.yaml @@ -0,0 +1,298 @@ +# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) +--- +name: wireguard +protocol: genetlink-legacy + +doc: | + **Netlink protocol to control WireGuard network devices.** + + The below enums and macros are for interfacing with WireGuard, using generic + netlink, with family ``WG_GENL_NAME`` and version ``WG_GENL_VERSION``. It + defines two commands: get and set. Note that while they share many common + attributes, these two commands actually accept a slightly different set of + inputs and outputs. These differences are noted under the individual + attributes. +c-family-name: wg-genl-name +c-version-name: wg-genl-version +max-by-define: true + +definitions: + - + name-prefix: wg- + name: key-len + type: const + value: 32 + - + name: --kernel-timespec + type: struct + header: linux/time_types.h + members: + - + name: sec + type: u64 + doc: Number of seconds, since UNIX epoch. + - + name: nsec + type: u64 + doc: Number of nanoseconds, after the second began. + - + name: wgdevice-flags + name-prefix: wgdevice-f- + enum-name: wgdevice-flag + type: flags + entries: + - replace-peers + - + name: wgpeer-flags + name-prefix: wgpeer-f- + enum-name: wgpeer-flag + type: flags + entries: + - remove-me + - replace-allowedips + - update-only + - + name: wgallowedip-flags + name-prefix: wgallowedip-f- + enum-name: wgallowedip-flag + type: flags + entries: + - remove-me + +attribute-sets: + - + name: wgdevice + enum-name: wgdevice-attribute + name-prefix: wgdevice-a- + attr-cnt-name: --wgdevice-a-last + attributes: + - + name: unspec + type: unused + value: 0 + - + name: ifindex + type: u32 + - + name: ifname + type: string + checks: + max-len: 15 + - + name: private-key + type: binary + doc: Set to all zeros to remove. + display-hint: hex + checks: + exact-len: wg-key-len + - + name: public-key + type: binary + display-hint: hex + checks: + exact-len: wg-key-len + - + name: flags + type: u32 + doc: | + ``0`` or ``WGDEVICE_F_REPLACE_PEERS`` if all current peers should be + removed prior to adding the list below. + enum: wgdevice-flags + - + name: listen-port + type: u16 + doc: Set as ``0`` to choose randomly. + - + name: fwmark + type: u32 + doc: Set as ``0`` to disable. + - + name: peers + type: indexed-array + sub-type: nest + nested-attributes: wgpeer + doc: | + The index/type parameter is unused on ``SET_DEVICE`` operations and is + zero on ``GET_DEVICE`` operations. + - + name: wgpeer + enum-name: wgpeer-attribute + name-prefix: wgpeer-a- + attr-cnt-name: --wgpeer-a-last + attributes: + - + name: unspec + type: unused + value: 0 + - + name: public-key + type: binary + display-hint: hex + checks: + exact-len: wg-key-len + - + name: preshared-key + type: binary + doc: Set as all zeros to remove. + display-hint: hex + checks: + exact-len: wg-key-len + - + name: flags + type: u32 + doc: | + ``0`` and/or ``WGPEER_F_REMOVE_ME`` if the specified peer should not + exist at the end of the operation, rather than added/updated and/or + ``WGPEER_F_REPLACE_ALLOWEDIPS`` if all current allowed IPs of this + peer should be removed prior to adding the list below and/or + ``WGPEER_F_UPDATE_ONLY`` if the peer should only be set if it already + exists. + enum: wgpeer-flags + - + name: endpoint + type: binary + doc: struct sockaddr_in or struct sockaddr_in6 + checks: + min-len: 16 + - + name: persistent-keepalive-interval + type: u16 + doc: Set as ``0`` to disable. + - + name: last-handshake-time + type: binary + struct: --kernel-timespec + checks: + exact-len: 16 + - + name: rx-bytes + type: u64 + - + name: tx-bytes + type: u64 + - + name: allowedips + type: indexed-array + sub-type: nest + nested-attributes: wgallowedip + doc: | + The index/type parameter is unused on ``SET_DEVICE`` operations and is + zero on ``GET_DEVICE`` operations. + - + name: protocol-version + type: u32 + doc: | + Should not be set or used at all by most users of this API, as the + most recent protocol will be used when this is unset. Otherwise, + must be set to ``1``. + - + name: wgallowedip + enum-name: wgallowedip-attribute + name-prefix: wgallowedip-a- + attr-cnt-name: --wgallowedip-a-last + attributes: + - + name: unspec + type: unused + value: 0 + - + name: family + type: u16 + doc: IP family, either ``AF_INET`` or ``AF_INET6``. + - + name: ipaddr + type: binary + doc: Either ``struct in_addr`` or ``struct in6_addr``. + display-hint: ipv4-or-v6 + checks: + min-len: 4 + - + name: cidr-mask + type: u8 + - + name: flags + type: u32 + doc: | + ``WGALLOWEDIP_F_REMOVE_ME`` if the specified IP should be removed; + otherwise, this IP will be added if it is not already present. + enum: wgallowedip-flags + +operations: + enum-name: wg-cmd + name-prefix: wg-cmd- + list: + - + name: get-device + value: 0 + doc: | + Retrieve WireGuard device + ~~~~~~~~~~~~~~~~~~~~~~~~~ + + The command should be called with one but not both of: + + - ``WGDEVICE_A_IFINDEX`` + - ``WGDEVICE_A_IFNAME`` + + The kernel will then return several messages (``NLM_F_MULTI``). It is + possible that all of the allowed IPs of a single peer will not fit + within a single netlink message. In that case, the same peer will be + written in the following message, except it will only contain + ``WGPEER_A_PUBLIC_KEY`` and ``WGPEER_A_ALLOWEDIPS``. This may occur + several times in a row for the same peer. It is then up to the receiver + to coalesce adjacent peers. Likewise, it is possible that all peers will + not fit within a single message. So, subsequent peers will be sent in + following messages, except those will only contain ``WGDEVICE_A_IFNAME`` + and ``WGDEVICE_A_PEERS``. It is then up to the receiver to coalesce + these messages to form the complete list of peers. + + Since this is an ``NLA_F_DUMP`` command, the final message will always + be ``NLMSG_DONE``, even if an error occurs. However, this ``NLMSG_DONE`` + message contains an integer error code. It is either zero or a negative + error code corresponding to the errno. + attribute-set: wgdevice + flags: [uns-admin-perm] + + dump: + pre: wg-get-device-start + post: wg-get-device-done + request: + attributes: + - ifindex + - ifname + reply: &all-attrs + attributes: + - ifindex + - ifname + - private-key + - public-key + - flags + - listen-port + - fwmark + - peers + - + name: set-device + value: 1 + doc: | + Set WireGuard device + ~~~~~~~~~~~~~~~~~~~~ + + This command should be called with a wgdevice set, containing one but + not both of ``WGDEVICE_A_IFINDEX`` and ``WGDEVICE_A_IFNAME``. + + It is possible that the amount of configuration data exceeds that of the + maximum message length accepted by the kernel. In that case, several + messages should be sent one after another, with each successive one + filling in information not contained in the prior. Note that if + ``WGDEVICE_F_REPLACE_PEERS`` is specified in the first message, it + probably should not be specified in fragments that come after, so that + the list of peers is only cleared the first time but appended after. + Likewise for peers, if ``WGPEER_F_REPLACE_ALLOWEDIPS`` is specified in + the first message of a peer, it likely should not be specified in + subsequent fragments. + + If an error occurs, ``NLMSG_ERROR`` will reply containing an errno. + attribute-set: wgdevice + flags: [uns-admin-perm] + + do: + request: *all-attrs diff --git a/MAINTAINERS b/MAINTAINERS index 09932ab7e0e8..8b44a380642c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -27673,6 +27673,7 @@ M: Jason A. Donenfeld L: wireguard@lists.zx2c4.com L: netdev@vger.kernel.org S: Maintained +F: Documentation/netlink/specs/wireguard.yaml F: drivers/net/wireguard/ F: tools/testing/selftests/wireguard/ diff --git a/include/uapi/linux/wireguard.h b/include/uapi/linux/wireguard.h index 8c26391196d5..dee4401e0b5d 100644 --- a/include/uapi/linux/wireguard.h +++ b/include/uapi/linux/wireguard.h @@ -1,135 +1,6 @@ /* SPDX-License-Identifier: (GPL-2.0 WITH Linux-syscall-note) OR MIT */ /* * Copyright (C) 2015-2019 Jason A. Donenfeld . All Rights Reserved. - * - * Documentation - * ============= - * - * The below enums and macros are for interfacing with WireGuard, using generic - * netlink, with family WG_GENL_NAME and version WG_GENL_VERSION. It defines two - * methods: get and set. Note that while they share many common attributes, - * these two functions actually accept a slightly different set of inputs and - * outputs. - * - * WG_CMD_GET_DEVICE - * ----------------- - * - * May only be called via NLM_F_REQUEST | NLM_F_DUMP. The command should contain - * one but not both of: - * - * WGDEVICE_A_IFINDEX: NLA_U32 - * WGDEVICE_A_IFNAME: NLA_NUL_STRING, maxlen IFNAMSIZ - 1 - * - * The kernel will then return several messages (NLM_F_MULTI) containing the - * following tree of nested items: - * - * WGDEVICE_A_IFINDEX: NLA_U32 - * WGDEVICE_A_IFNAME: NLA_NUL_STRING, maxlen IFNAMSIZ - 1 - * WGDEVICE_A_PRIVATE_KEY: NLA_EXACT_LEN, len WG_KEY_LEN - * WGDEVICE_A_PUBLIC_KEY: NLA_EXACT_LEN, len WG_KEY_LEN - * WGDEVICE_A_LISTEN_PORT: NLA_U16 - * WGDEVICE_A_FWMARK: NLA_U32 - * WGDEVICE_A_PEERS: NLA_NESTED - * 0: NLA_NESTED - * WGPEER_A_PUBLIC_KEY: NLA_EXACT_LEN, len WG_KEY_LEN - * WGPEER_A_PRESHARED_KEY: NLA_EXACT_LEN, len WG_KEY_LEN - * WGPEER_A_ENDPOINT: NLA_MIN_LEN(struct sockaddr), struct sockaddr_in or struct sockaddr_in6 - * WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL: NLA_U16 - * WGPEER_A_LAST_HANDSHAKE_TIME: NLA_EXACT_LEN, struct __kernel_timespec - * WGPEER_A_RX_BYTES: NLA_U64 - * WGPEER_A_TX_BYTES: NLA_U64 - * WGPEER_A_ALLOWEDIPS: NLA_NESTED - * 0: NLA_NESTED - * WGALLOWEDIP_A_FAMILY: NLA_U16 - * WGALLOWEDIP_A_IPADDR: NLA_MIN_LEN(struct in_addr), struct in_addr or struct in6_addr - * WGALLOWEDIP_A_CIDR_MASK: NLA_U8 - * 0: NLA_NESTED - * ... - * 0: NLA_NESTED - * ... - * ... - * WGPEER_A_PROTOCOL_VERSION: NLA_U32 - * 0: NLA_NESTED - * ... - * ... - * - * It is possible that all of the allowed IPs of a single peer will not - * fit within a single netlink message. In that case, the same peer will - * be written in the following message, except it will only contain - * WGPEER_A_PUBLIC_KEY and WGPEER_A_ALLOWEDIPS. This may occur several - * times in a row for the same peer. It is then up to the receiver to - * coalesce adjacent peers. Likewise, it is possible that all peers will - * not fit within a single message. So, subsequent peers will be sent - * in following messages, except those will only contain WGDEVICE_A_IFNAME - * and WGDEVICE_A_PEERS. It is then up to the receiver to coalesce these - * messages to form the complete list of peers. - * - * Since this is an NLA_F_DUMP command, the final message will always be - * NLMSG_DONE, even if an error occurs. However, this NLMSG_DONE message - * contains an integer error code. It is either zero or a negative error - * code corresponding to the errno. - * - * WG_CMD_SET_DEVICE - * ----------------- - * - * May only be called via NLM_F_REQUEST. The command should contain the - * following tree of nested items, containing one but not both of - * WGDEVICE_A_IFINDEX and WGDEVICE_A_IFNAME: - * - * WGDEVICE_A_IFINDEX: NLA_U32 - * WGDEVICE_A_IFNAME: NLA_NUL_STRING, maxlen IFNAMSIZ - 1 - * WGDEVICE_A_FLAGS: NLA_U32, 0 or WGDEVICE_F_REPLACE_PEERS if all current - * peers should be removed prior to adding the list below. - * WGDEVICE_A_PRIVATE_KEY: len WG_KEY_LEN, all zeros to remove - * WGDEVICE_A_LISTEN_PORT: NLA_U16, 0 to choose randomly - * WGDEVICE_A_FWMARK: NLA_U32, 0 to disable - * WGDEVICE_A_PEERS: NLA_NESTED - * 0: NLA_NESTED - * WGPEER_A_PUBLIC_KEY: len WG_KEY_LEN - * WGPEER_A_FLAGS: NLA_U32, 0 and/or WGPEER_F_REMOVE_ME if the - * specified peer should not exist at the end of the - * operation, rather than added/updated and/or - * WGPEER_F_REPLACE_ALLOWEDIPS if all current allowed - * IPs of this peer should be removed prior to adding - * the list below and/or WGPEER_F_UPDATE_ONLY if the - * peer should only be set if it already exists. - * WGPEER_A_PRESHARED_KEY: len WG_KEY_LEN, all zeros to remove - * WGPEER_A_ENDPOINT: struct sockaddr_in or struct sockaddr_in6 - * WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL: NLA_U16, 0 to disable - * WGPEER_A_ALLOWEDIPS: NLA_NESTED - * 0: NLA_NESTED - * WGALLOWEDIP_A_FAMILY: NLA_U16 - * WGALLOWEDIP_A_IPADDR: struct in_addr or struct in6_addr - * WGALLOWEDIP_A_CIDR_MASK: NLA_U8 - * WGALLOWEDIP_A_FLAGS: NLA_U32, WGALLOWEDIP_F_REMOVE_ME if - * the specified IP should be removed; - * otherwise, this IP will be added if - * it is not already present. - * 0: NLA_NESTED - * ... - * 0: NLA_NESTED - * ... - * ... - * WGPEER_A_PROTOCOL_VERSION: NLA_U32, should not be set or used at - * all by most users of this API, as the - * most recent protocol will be used when - * this is unset. Otherwise, must be set - * to 1. - * 0: NLA_NESTED - * ... - * ... - * - * It is possible that the amount of configuration data exceeds that of - * the maximum message length accepted by the kernel. In that case, several - * messages should be sent one after another, with each successive one - * filling in information not contained in the prior. Note that if - * WGDEVICE_F_REPLACE_PEERS is specified in the first message, it probably - * should not be specified in fragments that come after, so that the list - * of peers is only cleared the first time but appended after. Likewise for - * peers, if WGPEER_F_REPLACE_ALLOWEDIPS is specified in the first message - * of a peer, it likely should not be specified in subsequent fragments. - * - * If an error occurs, NLMSG_ERROR will reply containing an errno. */ #ifndef _WG_UAPI_WIREGUARD_H -- cgit v1.2.3 From b5c5a82bf5cb96e14a6627ef21be962052a0c6d8 Mon Sep 17 00:00:00 2001 From: Asbjørn Sloth Tønnesen Date: Wed, 26 Nov 2025 17:35:38 +0000 Subject: wireguard: uapi: move enum wg_cmd MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch moves enum wg_cmd to the end of the file, where ynl-gen would generate it. This is an incremental step towards adopting an UAPI header generated by ynl-gen. This is split out to keep the patches readable. This is a trivial patch with no behavioural changes intended. Signed-off-by: Asbjørn Sloth Tønnesen Signed-off-by: Jason A. Donenfeld --- include/uapi/linux/wireguard.h | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/wireguard.h b/include/uapi/linux/wireguard.h index dee4401e0b5d..3ebfffd61269 100644 --- a/include/uapi/linux/wireguard.h +++ b/include/uapi/linux/wireguard.h @@ -11,13 +11,6 @@ #define WG_KEY_LEN 32 -enum wg_cmd { - WG_CMD_GET_DEVICE, - WG_CMD_SET_DEVICE, - __WG_CMD_MAX -}; -#define WG_CMD_MAX (__WG_CMD_MAX - 1) - enum wgdevice_flag { WGDEVICE_F_REPLACE_PEERS = 1U << 0, __WGDEVICE_F_ALL = WGDEVICE_F_REPLACE_PEERS @@ -73,4 +66,12 @@ enum wgallowedip_attribute { }; #define WGALLOWEDIP_A_MAX (__WGALLOWEDIP_A_LAST - 1) +enum wg_cmd { + WG_CMD_GET_DEVICE, + WG_CMD_SET_DEVICE, + + __WG_CMD_MAX +}; +#define WG_CMD_MAX (__WG_CMD_MAX - 1) + #endif /* _WG_UAPI_WIREGUARD_H */ -- cgit v1.2.3 From 8d974872ab29eeb93a5b0b698007257d8be07968 Mon Sep 17 00:00:00 2001 From: Asbjørn Sloth Tønnesen Date: Wed, 26 Nov 2025 17:35:39 +0000 Subject: wireguard: uapi: move flag enums MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the wg*_flag enums, so they are defined above the attribute set enums, where ynl-gen would place them. This is an incremental step towards adopting an UAPI header generated by ynl-gen. This is split out to keep the patches readable. This is a trivial patch with no behavioural changes intended. Signed-off-by: Asbjørn Sloth Tønnesen Signed-off-by: Jason A. Donenfeld --- include/uapi/linux/wireguard.h | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/wireguard.h b/include/uapi/linux/wireguard.h index 3ebfffd61269..a2815f4f2910 100644 --- a/include/uapi/linux/wireguard.h +++ b/include/uapi/linux/wireguard.h @@ -15,6 +15,20 @@ enum wgdevice_flag { WGDEVICE_F_REPLACE_PEERS = 1U << 0, __WGDEVICE_F_ALL = WGDEVICE_F_REPLACE_PEERS }; + +enum wgpeer_flag { + WGPEER_F_REMOVE_ME = 1U << 0, + WGPEER_F_REPLACE_ALLOWEDIPS = 1U << 1, + WGPEER_F_UPDATE_ONLY = 1U << 2, + __WGPEER_F_ALL = WGPEER_F_REMOVE_ME | WGPEER_F_REPLACE_ALLOWEDIPS | + WGPEER_F_UPDATE_ONLY +}; + +enum wgallowedip_flag { + WGALLOWEDIP_F_REMOVE_ME = 1U << 0, + __WGALLOWEDIP_F_ALL = WGALLOWEDIP_F_REMOVE_ME +}; + enum wgdevice_attribute { WGDEVICE_A_UNSPEC, WGDEVICE_A_IFINDEX, @@ -29,13 +43,6 @@ enum wgdevice_attribute { }; #define WGDEVICE_A_MAX (__WGDEVICE_A_LAST - 1) -enum wgpeer_flag { - WGPEER_F_REMOVE_ME = 1U << 0, - WGPEER_F_REPLACE_ALLOWEDIPS = 1U << 1, - WGPEER_F_UPDATE_ONLY = 1U << 2, - __WGPEER_F_ALL = WGPEER_F_REMOVE_ME | WGPEER_F_REPLACE_ALLOWEDIPS | - WGPEER_F_UPDATE_ONLY -}; enum wgpeer_attribute { WGPEER_A_UNSPEC, WGPEER_A_PUBLIC_KEY, @@ -52,10 +59,6 @@ enum wgpeer_attribute { }; #define WGPEER_A_MAX (__WGPEER_A_LAST - 1) -enum wgallowedip_flag { - WGALLOWEDIP_F_REMOVE_ME = 1U << 0, - __WGALLOWEDIP_F_ALL = WGALLOWEDIP_F_REMOVE_ME -}; enum wgallowedip_attribute { WGALLOWEDIP_A_UNSPEC, WGALLOWEDIP_A_FAMILY, -- cgit v1.2.3 From 88cedad45ba14097e06d2c9f6578688097a94691 Mon Sep 17 00:00:00 2001 From: Asbjørn Sloth Tønnesen Date: Wed, 26 Nov 2025 17:35:40 +0000 Subject: wireguard: uapi: generate header with ynl-gen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use ynl-gen to generate the UAPI header for WireGuard. The cosmetic changes in this patch confirms that the spec is aligned with the implementation. By using the generated version, it ensures that they stay in sync. Changes in the generated header: * Trivial header guard rename. * Trivial white space changes. * Trivial comment changes. * Precompute bitflags in ynl-gen (see [1]). * Drop __*_F_ALL constants (see [1]). [1] https://lore.kernel.org/r/20251014123201.6ecfd146@kernel.org/ No behavioural changes intended. Signed-off-by: Asbjørn Sloth Tønnesen Signed-off-by: Jason A. Donenfeld --- drivers/net/wireguard/netlink.c | 6 +++--- include/uapi/linux/wireguard.h | 38 +++++++++++++++++++------------------- 2 files changed, 22 insertions(+), 22 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/net/wireguard/netlink.c b/drivers/net/wireguard/netlink.c index c2d0576e96f5..0ce0bda8c1ce 100644 --- a/drivers/net/wireguard/netlink.c +++ b/drivers/net/wireguard/netlink.c @@ -26,7 +26,7 @@ static const struct nla_policy device_policy[WGDEVICE_A_MAX + 1] = { [WGDEVICE_A_IFNAME] = { .type = NLA_NUL_STRING, .len = IFNAMSIZ - 1 }, [WGDEVICE_A_PRIVATE_KEY] = NLA_POLICY_EXACT_LEN(WG_KEY_LEN), [WGDEVICE_A_PUBLIC_KEY] = NLA_POLICY_EXACT_LEN(WG_KEY_LEN), - [WGDEVICE_A_FLAGS] = NLA_POLICY_MASK(NLA_U32, __WGDEVICE_F_ALL), + [WGDEVICE_A_FLAGS] = NLA_POLICY_MASK(NLA_U32, 0x1), [WGDEVICE_A_LISTEN_PORT] = { .type = NLA_U16 }, [WGDEVICE_A_FWMARK] = { .type = NLA_U32 }, [WGDEVICE_A_PEERS] = NLA_POLICY_NESTED_ARRAY(peer_policy), @@ -35,7 +35,7 @@ static const struct nla_policy device_policy[WGDEVICE_A_MAX + 1] = { static const struct nla_policy peer_policy[WGPEER_A_MAX + 1] = { [WGPEER_A_PUBLIC_KEY] = NLA_POLICY_EXACT_LEN(WG_KEY_LEN), [WGPEER_A_PRESHARED_KEY] = NLA_POLICY_EXACT_LEN(WG_KEY_LEN), - [WGPEER_A_FLAGS] = NLA_POLICY_MASK(NLA_U32, __WGPEER_F_ALL), + [WGPEER_A_FLAGS] = NLA_POLICY_MASK(NLA_U32, 0x7), [WGPEER_A_ENDPOINT] = NLA_POLICY_MIN_LEN(sizeof(struct sockaddr)), [WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL] = { .type = NLA_U16 }, [WGPEER_A_LAST_HANDSHAKE_TIME] = NLA_POLICY_EXACT_LEN(sizeof(struct __kernel_timespec)), @@ -49,7 +49,7 @@ static const struct nla_policy allowedip_policy[WGALLOWEDIP_A_MAX + 1] = { [WGALLOWEDIP_A_FAMILY] = { .type = NLA_U16 }, [WGALLOWEDIP_A_IPADDR] = NLA_POLICY_MIN_LEN(sizeof(struct in_addr)), [WGALLOWEDIP_A_CIDR_MASK] = { .type = NLA_U8 }, - [WGALLOWEDIP_A_FLAGS] = NLA_POLICY_MASK(NLA_U32, __WGALLOWEDIP_F_ALL), + [WGALLOWEDIP_A_FLAGS] = NLA_POLICY_MASK(NLA_U32, 0x1), }; static struct wg_device *lookup_interface(struct nlattr **attrs, diff --git a/include/uapi/linux/wireguard.h b/include/uapi/linux/wireguard.h index a2815f4f2910..a100b9715b08 100644 --- a/include/uapi/linux/wireguard.h +++ b/include/uapi/linux/wireguard.h @@ -1,32 +1,29 @@ -/* SPDX-License-Identifier: (GPL-2.0 WITH Linux-syscall-note) OR MIT */ -/* - * Copyright (C) 2015-2019 Jason A. Donenfeld . All Rights Reserved. - */ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */ +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/wireguard.yaml */ +/* YNL-GEN uapi header */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ -#ifndef _WG_UAPI_WIREGUARD_H -#define _WG_UAPI_WIREGUARD_H +#ifndef _UAPI_LINUX_WIREGUARD_H +#define _UAPI_LINUX_WIREGUARD_H -#define WG_GENL_NAME "wireguard" -#define WG_GENL_VERSION 1 +#define WG_GENL_NAME "wireguard" +#define WG_GENL_VERSION 1 -#define WG_KEY_LEN 32 +#define WG_KEY_LEN 32 enum wgdevice_flag { - WGDEVICE_F_REPLACE_PEERS = 1U << 0, - __WGDEVICE_F_ALL = WGDEVICE_F_REPLACE_PEERS + WGDEVICE_F_REPLACE_PEERS = 1, }; enum wgpeer_flag { - WGPEER_F_REMOVE_ME = 1U << 0, - WGPEER_F_REPLACE_ALLOWEDIPS = 1U << 1, - WGPEER_F_UPDATE_ONLY = 1U << 2, - __WGPEER_F_ALL = WGPEER_F_REMOVE_ME | WGPEER_F_REPLACE_ALLOWEDIPS | - WGPEER_F_UPDATE_ONLY + WGPEER_F_REMOVE_ME = 1, + WGPEER_F_REPLACE_ALLOWEDIPS = 2, + WGPEER_F_UPDATE_ONLY = 4, }; enum wgallowedip_flag { - WGALLOWEDIP_F_REMOVE_ME = 1U << 0, - __WGALLOWEDIP_F_ALL = WGALLOWEDIP_F_REMOVE_ME + WGALLOWEDIP_F_REMOVE_ME = 1, }; enum wgdevice_attribute { @@ -39,6 +36,7 @@ enum wgdevice_attribute { WGDEVICE_A_LISTEN_PORT, WGDEVICE_A_FWMARK, WGDEVICE_A_PEERS, + __WGDEVICE_A_LAST }; #define WGDEVICE_A_MAX (__WGDEVICE_A_LAST - 1) @@ -55,6 +53,7 @@ enum wgpeer_attribute { WGPEER_A_TX_BYTES, WGPEER_A_ALLOWEDIPS, WGPEER_A_PROTOCOL_VERSION, + __WGPEER_A_LAST }; #define WGPEER_A_MAX (__WGPEER_A_LAST - 1) @@ -65,6 +64,7 @@ enum wgallowedip_attribute { WGALLOWEDIP_A_IPADDR, WGALLOWEDIP_A_CIDR_MASK, WGALLOWEDIP_A_FLAGS, + __WGALLOWEDIP_A_LAST }; #define WGALLOWEDIP_A_MAX (__WGALLOWEDIP_A_LAST - 1) @@ -77,4 +77,4 @@ enum wg_cmd { }; #define WG_CMD_MAX (__WG_CMD_MAX - 1) -#endif /* _WG_UAPI_WIREGUARD_H */ +#endif /* _UAPI_LINUX_WIREGUARD_H */ -- cgit v1.2.3 From c3859de858aa7ae0d0a5ca21e8ee9792f2f256b6 Mon Sep 17 00:00:00 2001 From: Alexey Kardashevskiy Date: Tue, 2 Dec 2025 13:44:47 +1100 Subject: psp-sev: Assign numbers to all status codes and add new Make the definitions explicit. Add some more new codes. The following patches will be using SPDM_REQUEST and EXPAND_BUFFER_LENGTH_REQUEST, others are useful for the PSP FW diagnostics. Signed-off-by: Alexey Kardashevskiy Link: https://patch.msgid.link/20251202024449.542361-3-aik@amd.com Acked-by: Tom Lendacky Signed-off-by: Dan Williams --- include/uapi/linux/psp-sev.h | 66 +++++++++++++++++++++++++++----------------- 1 file changed, 41 insertions(+), 25 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/psp-sev.h b/include/uapi/linux/psp-sev.h index c2fd324623c4..2b5b042eb73b 100644 --- a/include/uapi/linux/psp-sev.h +++ b/include/uapi/linux/psp-sev.h @@ -47,32 +47,32 @@ typedef enum { * with possible values from the specification. */ SEV_RET_NO_FW_CALL = -1, - SEV_RET_SUCCESS = 0, - SEV_RET_INVALID_PLATFORM_STATE, - SEV_RET_INVALID_GUEST_STATE, - SEV_RET_INAVLID_CONFIG, + SEV_RET_SUCCESS = 0, + SEV_RET_INVALID_PLATFORM_STATE = 0x0001, + SEV_RET_INVALID_GUEST_STATE = 0x0002, + SEV_RET_INAVLID_CONFIG = 0x0003, SEV_RET_INVALID_CONFIG = SEV_RET_INAVLID_CONFIG, - SEV_RET_INVALID_LEN, - SEV_RET_ALREADY_OWNED, - SEV_RET_INVALID_CERTIFICATE, - SEV_RET_POLICY_FAILURE, - SEV_RET_INACTIVE, - SEV_RET_INVALID_ADDRESS, - SEV_RET_BAD_SIGNATURE, - SEV_RET_BAD_MEASUREMENT, - SEV_RET_ASID_OWNED, - SEV_RET_INVALID_ASID, - SEV_RET_WBINVD_REQUIRED, - SEV_RET_DFFLUSH_REQUIRED, - SEV_RET_INVALID_GUEST, - SEV_RET_INVALID_COMMAND, - SEV_RET_ACTIVE, - SEV_RET_HWSEV_RET_PLATFORM, - SEV_RET_HWSEV_RET_UNSAFE, - SEV_RET_UNSUPPORTED, - SEV_RET_INVALID_PARAM, - SEV_RET_RESOURCE_LIMIT, - SEV_RET_SECURE_DATA_INVALID, + SEV_RET_INVALID_LEN = 0x0004, + SEV_RET_ALREADY_OWNED = 0x0005, + SEV_RET_INVALID_CERTIFICATE = 0x0006, + SEV_RET_POLICY_FAILURE = 0x0007, + SEV_RET_INACTIVE = 0x0008, + SEV_RET_INVALID_ADDRESS = 0x0009, + SEV_RET_BAD_SIGNATURE = 0x000A, + SEV_RET_BAD_MEASUREMENT = 0x000B, + SEV_RET_ASID_OWNED = 0x000C, + SEV_RET_INVALID_ASID = 0x000D, + SEV_RET_WBINVD_REQUIRED = 0x000E, + SEV_RET_DFFLUSH_REQUIRED = 0x000F, + SEV_RET_INVALID_GUEST = 0x0010, + SEV_RET_INVALID_COMMAND = 0x0011, + SEV_RET_ACTIVE = 0x0012, + SEV_RET_HWSEV_RET_PLATFORM = 0x0013, + SEV_RET_HWSEV_RET_UNSAFE = 0x0014, + SEV_RET_UNSUPPORTED = 0x0015, + SEV_RET_INVALID_PARAM = 0x0016, + SEV_RET_RESOURCE_LIMIT = 0x0017, + SEV_RET_SECURE_DATA_INVALID = 0x0018, SEV_RET_INVALID_PAGE_SIZE = 0x0019, SEV_RET_INVALID_PAGE_STATE = 0x001A, SEV_RET_INVALID_MDATA_ENTRY = 0x001B, @@ -87,6 +87,22 @@ typedef enum { SEV_RET_RESTORE_REQUIRED = 0x0025, SEV_RET_RMP_INITIALIZATION_FAILED = 0x0026, SEV_RET_INVALID_KEY = 0x0027, + SEV_RET_SHUTDOWN_INCOMPLETE = 0x0028, + SEV_RET_INCORRECT_BUFFER_LENGTH = 0x0030, + SEV_RET_EXPAND_BUFFER_LENGTH_REQUEST = 0x0031, + SEV_RET_SPDM_REQUEST = 0x0032, + SEV_RET_SPDM_ERROR = 0x0033, + SEV_RET_SEV_STATUS_ERR_IN_DEV_CONN = 0x0035, + SEV_RET_SEV_STATUS_INVALID_DEV_CTX = 0x0036, + SEV_RET_SEV_STATUS_INVALID_TDI_CTX = 0x0037, + SEV_RET_SEV_STATUS_INVALID_TDI = 0x0038, + SEV_RET_SEV_STATUS_RECLAIM_REQUIRED = 0x0039, + SEV_RET_IN_USE = 0x003A, + SEV_RET_SEV_STATUS_INVALID_DEV_STATE = 0x003B, + SEV_RET_SEV_STATUS_INVALID_TDI_STATE = 0x003C, + SEV_RET_SEV_STATUS_DEV_CERT_CHANGED = 0x003D, + SEV_RET_SEV_STATUS_RESYNC_REQ = 0x003E, + SEV_RET_SEV_STATUS_RESPONSE_TOO_LARGE = 0x003F, SEV_RET_MAX, } sev_ret_code; -- cgit v1.2.3 From f7231cff1f3ff8259bef02dc4999bc132abf29cf Mon Sep 17 00:00:00 2001 From: Jacopo Mondi Date: Wed, 3 Dec 2025 08:55:34 +0000 Subject: media: uapi: c3-isp: Fix documentation warning Building htmldocs generates a warning: WARNING: include/uapi/linux/media/amlogic/c3-isp-config.h:199 error: Cannot parse struct or union! Which correctly highlights that the c3_isp_params_block_header symbol is wrongly documented as a struct while it's a plain #define instead. Fix this by removing the 'struct' identifier from the documentation of the c3_isp_params_block_header symbol. [ribalda: Add Closes:] Reported-by: Stephen Rothwell Closes: https://lore.kernel.org/all/20251127131425.4b5b6644@canb.auug.org.au/ Fixes: 45662082855c ("media: uapi: Convert Amlogic C3 to V4L2 extensible params") Cc: stable@vger.kernel.org Signed-off-by: Jacopo Mondi Signed-off-by: Ricardo Ribalda Signed-off-by: Mauro Carvalho Chehab --- include/uapi/linux/media/amlogic/c3-isp-config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/media/amlogic/c3-isp-config.h b/include/uapi/linux/media/amlogic/c3-isp-config.h index 0a3c1cc55ccb..92db5dcdda18 100644 --- a/include/uapi/linux/media/amlogic/c3-isp-config.h +++ b/include/uapi/linux/media/amlogic/c3-isp-config.h @@ -186,7 +186,7 @@ enum c3_isp_params_block_type { #define C3_ISP_PARAMS_BLOCK_FL_ENABLE V4L2_ISP_PARAMS_FL_BLOCK_ENABLE /** - * struct c3_isp_params_block_header - C3 ISP parameter block header + * c3_isp_params_block_header - C3 ISP parameter block header * * This structure represents the common part of all the ISP configuration * blocks and is identical to :c:type:`v4l2_isp_params_block_header`. -- cgit v1.2.3 From 22a1ffea5f805dfa21b64d1c7b5fe39c0c78c997 Mon Sep 17 00:00:00 2001 From: Stefan Hajnoczi Date: Mon, 1 Dec 2025 16:43:28 -0500 Subject: block: add IOC_PR_READ_KEYS ioctl Add a Persistent Reservations ioctl to read the list of currently registered reservation keys. This calls the pr_ops->read_keys() function that was previously added in commit c787f1baa503 ("block: Add PR callouts for read keys and reservation") but was only used by the in-kernel SCSI target so far. The IOC_PR_READ_KEYS ioctl is necessary so that userspace applications that rely on Persistent Reservations ioctls have a way of inspecting the current state. Cluster managers and validation tests need this functionality. Signed-off-by: Stefan Hajnoczi Reviewed-by: Christoph Hellwig Reviewed-by: Martin K. Petersen Signed-off-by: Jens Axboe --- block/ioctl.c | 56 +++++++++++++++++++++++++++++++++++++++++++++++++ include/uapi/linux/pr.h | 7 +++++++ 2 files changed, 63 insertions(+) (limited to 'include/uapi/linux') diff --git a/block/ioctl.c b/block/ioctl.c index 2b3ab9bfc413..c0802ebf54a6 100644 --- a/block/ioctl.c +++ b/block/ioctl.c @@ -423,6 +423,60 @@ static int blkdev_pr_clear(struct block_device *bdev, blk_mode_t mode, return ops->pr_clear(bdev, c.key); } +static int blkdev_pr_read_keys(struct block_device *bdev, blk_mode_t mode, + struct pr_read_keys __user *arg) +{ + const struct pr_ops *ops = bdev->bd_disk->fops->pr_ops; + struct pr_keys *keys_info; + struct pr_read_keys read_keys; + u64 __user *keys_ptr; + size_t keys_info_len; + size_t keys_copy_len; + int ret; + + if (!blkdev_pr_allowed(bdev, mode)) + return -EPERM; + if (!ops || !ops->pr_read_keys) + return -EOPNOTSUPP; + + if (copy_from_user(&read_keys, arg, sizeof(read_keys))) + return -EFAULT; + + keys_info_len = struct_size(keys_info, keys, read_keys.num_keys); + if (keys_info_len == SIZE_MAX) + return -EINVAL; + + keys_info = kzalloc(keys_info_len, GFP_KERNEL); + if (!keys_info) + return -ENOMEM; + + keys_info->num_keys = read_keys.num_keys; + + ret = ops->pr_read_keys(bdev, keys_info); + if (ret) + goto out; + + /* Copy out individual keys */ + keys_ptr = u64_to_user_ptr(read_keys.keys_ptr); + keys_copy_len = min(read_keys.num_keys, keys_info->num_keys) * + sizeof(keys_info->keys[0]); + + if (copy_to_user(keys_ptr, keys_info->keys, keys_copy_len)) { + ret = -EFAULT; + goto out; + } + + /* Copy out the arg struct */ + read_keys.generation = keys_info->generation; + read_keys.num_keys = keys_info->num_keys; + + if (copy_to_user(arg, &read_keys, sizeof(read_keys))) + ret = -EFAULT; +out: + kfree(keys_info); + return ret; +} + static int blkdev_flushbuf(struct block_device *bdev, unsigned cmd, unsigned long arg) { @@ -645,6 +699,8 @@ static int blkdev_common_ioctl(struct block_device *bdev, blk_mode_t mode, return blkdev_pr_preempt(bdev, mode, argp, true); case IOC_PR_CLEAR: return blkdev_pr_clear(bdev, mode, argp); + case IOC_PR_READ_KEYS: + return blkdev_pr_read_keys(bdev, mode, argp); default: return blk_get_meta_cap(bdev, cmd, argp); } diff --git a/include/uapi/linux/pr.h b/include/uapi/linux/pr.h index d8126415966f..fcb74eab92c8 100644 --- a/include/uapi/linux/pr.h +++ b/include/uapi/linux/pr.h @@ -56,6 +56,12 @@ struct pr_clear { __u32 __pad; }; +struct pr_read_keys { + __u32 generation; + __u32 num_keys; + __u64 keys_ptr; +}; + #define PR_FL_IGNORE_KEY (1 << 0) /* ignore existing key */ #define IOC_PR_REGISTER _IOW('p', 200, struct pr_registration) @@ -64,5 +70,6 @@ struct pr_clear { #define IOC_PR_PREEMPT _IOW('p', 203, struct pr_preempt) #define IOC_PR_PREEMPT_ABORT _IOW('p', 204, struct pr_preempt) #define IOC_PR_CLEAR _IOW('p', 205, struct pr_clear) +#define IOC_PR_READ_KEYS _IOWR('p', 206, struct pr_read_keys) #endif /* _UAPI_PR_H */ -- cgit v1.2.3 From 3e2cb9ee76c27f57bfdb7b4753b909594d4fa31a Mon Sep 17 00:00:00 2001 From: Stefan Hajnoczi Date: Mon, 1 Dec 2025 16:43:29 -0500 Subject: block: add IOC_PR_READ_RESERVATION ioctl Add a Persistent Reservations ioctl to read the current reservation. This calls the pr_ops->read_reservation() function that was previously added in commit c787f1baa503 ("block: Add PR callouts for read keys and reservation") but was only used by the in-kernel SCSI target so far. The IOC_PR_READ_RESERVATION ioctl is necessary so that userspace applications that rely on Persistent Reservations ioctls have a way of inspecting the current state. Cluster managers and validation tests need this functionality. Signed-off-by: Stefan Hajnoczi Reviewed-by: Hannes Reinecke Reviewed-by: Christoph Hellwig Reviewed-by: Martin K. Petersen Signed-off-by: Jens Axboe --- block/ioctl.c | 28 ++++++++++++++++++++++++++++ include/uapi/linux/pr.h | 7 +++++++ 2 files changed, 35 insertions(+) (limited to 'include/uapi/linux') diff --git a/block/ioctl.c b/block/ioctl.c index c0802ebf54a6..61feed686418 100644 --- a/block/ioctl.c +++ b/block/ioctl.c @@ -477,6 +477,32 @@ out: return ret; } +static int blkdev_pr_read_reservation(struct block_device *bdev, + blk_mode_t mode, struct pr_read_reservation __user *arg) +{ + const struct pr_ops *ops = bdev->bd_disk->fops->pr_ops; + struct pr_held_reservation rsv = {}; + struct pr_read_reservation out = {}; + int ret; + + if (!blkdev_pr_allowed(bdev, mode)) + return -EPERM; + if (!ops || !ops->pr_read_reservation) + return -EOPNOTSUPP; + + ret = ops->pr_read_reservation(bdev, &rsv); + if (ret) + return ret; + + out.key = rsv.key; + out.generation = rsv.generation; + out.type = rsv.type; + + if (copy_to_user(arg, &out, sizeof(out))) + return -EFAULT; + return 0; +} + static int blkdev_flushbuf(struct block_device *bdev, unsigned cmd, unsigned long arg) { @@ -701,6 +727,8 @@ static int blkdev_common_ioctl(struct block_device *bdev, blk_mode_t mode, return blkdev_pr_clear(bdev, mode, argp); case IOC_PR_READ_KEYS: return blkdev_pr_read_keys(bdev, mode, argp); + case IOC_PR_READ_RESERVATION: + return blkdev_pr_read_reservation(bdev, mode, argp); default: return blk_get_meta_cap(bdev, cmd, argp); } diff --git a/include/uapi/linux/pr.h b/include/uapi/linux/pr.h index fcb74eab92c8..847f3051057a 100644 --- a/include/uapi/linux/pr.h +++ b/include/uapi/linux/pr.h @@ -62,6 +62,12 @@ struct pr_read_keys { __u64 keys_ptr; }; +struct pr_read_reservation { + __u64 key; + __u32 generation; + __u32 type; +}; + #define PR_FL_IGNORE_KEY (1 << 0) /* ignore existing key */ #define IOC_PR_REGISTER _IOW('p', 200, struct pr_registration) @@ -71,5 +77,6 @@ struct pr_read_keys { #define IOC_PR_PREEMPT_ABORT _IOW('p', 204, struct pr_preempt) #define IOC_PR_CLEAR _IOW('p', 205, struct pr_clear) #define IOC_PR_READ_KEYS _IOWR('p', 206, struct pr_read_keys) +#define IOC_PR_READ_RESERVATION _IOR('p', 207, struct pr_read_reservation) #endif /* _UAPI_PR_H */ -- cgit v1.2.3 From fe93446b5ebdaa89a8f97b15668c077921a65140 Mon Sep 17 00:00:00 2001 From: Thomas Weißschuh Date: Wed, 3 Dec 2025 14:57:57 +0100 Subject: vfs: use UAPI types for new struct delegation definition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Using libc types and headers from the UAPI headers is problematic as it introduces a dependency on a full C toolchain. Use the fixed-width integer types provided by the UAPI headers instead. Fixes: 1602bad16d7d ("vfs: expose delegation support to userland") Fixes: 4be9e04ebf75 ("vfs: add needed headers for new struct delegation definition") Signed-off-by: Thomas Weißschuh Link: https://patch.msgid.link/20251203-uapi-fcntl-v1-1-490c67bf3425@linutronix.de Acked-by: Arnd Bergmann Acked-by: Jeff Layton Signed-off-by: Christian Brauner --- include/uapi/linux/fcntl.h | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/fcntl.h b/include/uapi/linux/fcntl.h index 5e277fd955aa..aadfbf6e0cb3 100644 --- a/include/uapi/linux/fcntl.h +++ b/include/uapi/linux/fcntl.h @@ -4,11 +4,7 @@ #include #include -#ifdef __KERNEL__ #include -#else -#include -#endif #define F_SETLEASE (F_LINUX_SPECIFIC_BASE + 0) #define F_GETLEASE (F_LINUX_SPECIFIC_BASE + 1) @@ -90,9 +86,9 @@ /* Argument structure for F_GETDELEG and F_SETDELEG */ struct delegation { - uint32_t d_flags; /* Must be 0 */ - uint16_t d_type; /* F_RDLCK, F_WRLCK, F_UNLCK */ - uint16_t __pad; /* Must be 0 */ + __u32 d_flags; /* Must be 0 */ + __u16 d_type; /* F_RDLCK, F_WRLCK, F_UNLCK */ + __u16 __pad; /* Must be 0 */ }; /* -- cgit v1.2.3 From 7bfe3b8ea6e30437e01fcb8e4f56ef6e4d986d0f Mon Sep 17 00:00:00 2001 From: Naman Jain Date: Thu, 13 Nov 2025 04:41:49 +0000 Subject: Drivers: hv: Introduce mshv_vtl driver Provide an interface for Virtual Machine Monitor like OpenVMM and its use as OpenHCL paravisor to control VTL0 (Virtual trust Level). Expose devices and support IOCTLs for features like VTL creation, VTL0 memory management, context switch, making hypercalls, mapping VTL0 address space to VTL2 userspace, getting new VMBus messages and channel events in VTL2 etc. Co-developed-by: Roman Kisel Signed-off-by: Roman Kisel Co-developed-by: Saurabh Sengar Signed-off-by: Saurabh Sengar Reviewed-by: Michael Kelley Signed-off-by: Naman Jain Signed-off-by: Wei Liu --- arch/x86/hyperv/Makefile | 10 +- arch/x86/hyperv/hv_vtl.c | 30 + arch/x86/hyperv/mshv-asm-offsets.c | 37 + arch/x86/hyperv/mshv_vtl_asm.S | 116 +++ arch/x86/include/asm/mshyperv.h | 34 + drivers/hv/Kconfig | 27 +- drivers/hv/Makefile | 7 +- drivers/hv/mshv_vtl.h | 25 + drivers/hv/mshv_vtl_main.c | 1392 ++++++++++++++++++++++++++++++++++++ include/hyperv/hvgdk_mini.h | 106 +++ include/uapi/linux/mshv.h | 80 +++ 11 files changed, 1861 insertions(+), 3 deletions(-) create mode 100644 arch/x86/hyperv/mshv-asm-offsets.c create mode 100644 arch/x86/hyperv/mshv_vtl_asm.S create mode 100644 drivers/hv/mshv_vtl.h create mode 100644 drivers/hv/mshv_vtl_main.c (limited to 'include/uapi/linux') diff --git a/arch/x86/hyperv/Makefile b/arch/x86/hyperv/Makefile index 6f5d97cddd80..56292102af62 100644 --- a/arch/x86/hyperv/Makefile +++ b/arch/x86/hyperv/Makefile @@ -1,7 +1,12 @@ # SPDX-License-Identifier: GPL-2.0-only obj-y := hv_init.o mmu.o nested.o irqdomain.o ivm.o obj-$(CONFIG_X86_64) += hv_apic.o -obj-$(CONFIG_HYPERV_VTL_MODE) += hv_vtl.o +obj-$(CONFIG_HYPERV_VTL_MODE) += hv_vtl.o mshv_vtl_asm.o + +$(obj)/mshv_vtl_asm.o: $(obj)/mshv-asm-offsets.h + +$(obj)/mshv-asm-offsets.h: $(obj)/mshv-asm-offsets.s FORCE + $(call filechk,offsets,__MSHV_ASM_OFFSETS_H__) ifdef CONFIG_X86_64 obj-$(CONFIG_PARAVIRT_SPINLOCKS) += hv_spinlock.o @@ -12,3 +17,6 @@ obj-$(CONFIG_PARAVIRT_SPINLOCKS) += hv_spinlock.o obj-$(CONFIG_CRASH_DUMP) += hv_crash.o hv_trampoline.o endif endif + +targets += mshv-asm-offsets.s +clean-files += mshv-asm-offsets.h diff --git a/arch/x86/hyperv/hv_vtl.c b/arch/x86/hyperv/hv_vtl.c index 042e8712d8de..c0edaed0efb3 100644 --- a/arch/x86/hyperv/hv_vtl.c +++ b/arch/x86/hyperv/hv_vtl.c @@ -9,12 +9,17 @@ #include #include #include +#include +#include #include #include #include #include #include +#include +#include #include <../kernel/smpboot.h> +#include "../../kernel/fpu/legacy.h" extern struct boot_params boot_params; static struct real_mode_header hv_vtl_real_mode_header; @@ -249,3 +254,28 @@ int __init hv_vtl_early_init(void) return 0; } + +DEFINE_STATIC_CALL_NULL(__mshv_vtl_return_hypercall, void (*)(void)); + +void mshv_vtl_return_call_init(u64 vtl_return_offset) +{ + static_call_update(__mshv_vtl_return_hypercall, + (void *)((u8 *)hv_hypercall_pg + vtl_return_offset)); +} +EXPORT_SYMBOL(mshv_vtl_return_call_init); + +void mshv_vtl_return_call(struct mshv_vtl_cpu_context *vtl0) +{ + struct hv_vp_assist_page *hvp; + + hvp = hv_vp_assist_page[smp_processor_id()]; + hvp->vtl_ret_x64rax = vtl0->rax; + hvp->vtl_ret_x64rcx = vtl0->rcx; + + kernel_fpu_begin_mask(0); + fxrstor(&vtl0->fx_state); + __mshv_vtl_return_call(vtl0); + fxsave(&vtl0->fx_state); + kernel_fpu_end(); +} +EXPORT_SYMBOL(mshv_vtl_return_call); diff --git a/arch/x86/hyperv/mshv-asm-offsets.c b/arch/x86/hyperv/mshv-asm-offsets.c new file mode 100644 index 000000000000..882c1db6df16 --- /dev/null +++ b/arch/x86/hyperv/mshv-asm-offsets.c @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Generate definitions needed by assembly language modules. + * This code generates raw asm output which is post-processed to extract + * and format the required data. + * + * Copyright (c) 2025, Microsoft Corporation. + * + * Author: + * Naman Jain + */ +#define COMPILE_OFFSETS + +#include +#include + +static void __used common(void) +{ + if (IS_ENABLED(CONFIG_HYPERV_VTL_MODE)) { + OFFSET(MSHV_VTL_CPU_CONTEXT_rax, mshv_vtl_cpu_context, rax); + OFFSET(MSHV_VTL_CPU_CONTEXT_rcx, mshv_vtl_cpu_context, rcx); + OFFSET(MSHV_VTL_CPU_CONTEXT_rdx, mshv_vtl_cpu_context, rdx); + OFFSET(MSHV_VTL_CPU_CONTEXT_rbx, mshv_vtl_cpu_context, rbx); + OFFSET(MSHV_VTL_CPU_CONTEXT_rbp, mshv_vtl_cpu_context, rbp); + OFFSET(MSHV_VTL_CPU_CONTEXT_rsi, mshv_vtl_cpu_context, rsi); + OFFSET(MSHV_VTL_CPU_CONTEXT_rdi, mshv_vtl_cpu_context, rdi); + OFFSET(MSHV_VTL_CPU_CONTEXT_r8, mshv_vtl_cpu_context, r8); + OFFSET(MSHV_VTL_CPU_CONTEXT_r9, mshv_vtl_cpu_context, r9); + OFFSET(MSHV_VTL_CPU_CONTEXT_r10, mshv_vtl_cpu_context, r10); + OFFSET(MSHV_VTL_CPU_CONTEXT_r11, mshv_vtl_cpu_context, r11); + OFFSET(MSHV_VTL_CPU_CONTEXT_r12, mshv_vtl_cpu_context, r12); + OFFSET(MSHV_VTL_CPU_CONTEXT_r13, mshv_vtl_cpu_context, r13); + OFFSET(MSHV_VTL_CPU_CONTEXT_r14, mshv_vtl_cpu_context, r14); + OFFSET(MSHV_VTL_CPU_CONTEXT_r15, mshv_vtl_cpu_context, r15); + OFFSET(MSHV_VTL_CPU_CONTEXT_cr2, mshv_vtl_cpu_context, cr2); + } +} diff --git a/arch/x86/hyperv/mshv_vtl_asm.S b/arch/x86/hyperv/mshv_vtl_asm.S new file mode 100644 index 000000000000..f595eefad9ab --- /dev/null +++ b/arch/x86/hyperv/mshv_vtl_asm.S @@ -0,0 +1,116 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Assembly level code for mshv_vtl VTL transition + * + * Copyright (c) 2025, Microsoft Corporation. + * + * Author: + * Naman Jain + */ + +#include +#include +#include +#include +#include +#include "mshv-asm-offsets.h" + + .text + .section .noinstr.text, "ax" +/* + * void __mshv_vtl_return_call(struct mshv_vtl_cpu_context *vtl0) + * + * This function is used to context switch between different Virtual Trust Levels. + * It is marked as 'noinstr' to prevent against instrumentation and debugging facilities. + * NMIs aren't a problem because the NMI handler saves/restores CR2 specifically to guard + * against #PFs in NMI context clobbering the guest state. + */ +SYM_FUNC_START(__mshv_vtl_return_call) + /* Push callee save registers */ + pushq %rbp + mov %rsp, %rbp + pushq %r12 + pushq %r13 + pushq %r14 + pushq %r15 + pushq %rbx + + /* register switch to VTL0 clobbers all registers except rax/rcx */ + mov %_ASM_ARG1, %rax + + /* grab rbx/rbp/rsi/rdi/r8-r15 */ + mov MSHV_VTL_CPU_CONTEXT_rbx(%rax), %rbx + mov MSHV_VTL_CPU_CONTEXT_rbp(%rax), %rbp + mov MSHV_VTL_CPU_CONTEXT_rsi(%rax), %rsi + mov MSHV_VTL_CPU_CONTEXT_rdi(%rax), %rdi + mov MSHV_VTL_CPU_CONTEXT_r8(%rax), %r8 + mov MSHV_VTL_CPU_CONTEXT_r9(%rax), %r9 + mov MSHV_VTL_CPU_CONTEXT_r10(%rax), %r10 + mov MSHV_VTL_CPU_CONTEXT_r11(%rax), %r11 + mov MSHV_VTL_CPU_CONTEXT_r12(%rax), %r12 + mov MSHV_VTL_CPU_CONTEXT_r13(%rax), %r13 + mov MSHV_VTL_CPU_CONTEXT_r14(%rax), %r14 + mov MSHV_VTL_CPU_CONTEXT_r15(%rax), %r15 + + mov MSHV_VTL_CPU_CONTEXT_cr2(%rax), %rdx + mov %rdx, %cr2 + mov MSHV_VTL_CPU_CONTEXT_rdx(%rax), %rdx + + /* stash host registers on stack */ + pushq %rax + pushq %rcx + + xor %ecx, %ecx + + /* make a hypercall to switch VTL */ + call STATIC_CALL_TRAMP_STR(__mshv_vtl_return_hypercall) + + /* stash guest registers on stack, restore saved host copies */ + pushq %rax + pushq %rcx + mov 16(%rsp), %rcx + mov 24(%rsp), %rax + + mov %rdx, MSHV_VTL_CPU_CONTEXT_rdx(%rax) + mov %cr2, %rdx + mov %rdx, MSHV_VTL_CPU_CONTEXT_cr2(%rax) + pop MSHV_VTL_CPU_CONTEXT_rcx(%rax) + pop MSHV_VTL_CPU_CONTEXT_rax(%rax) + add $16, %rsp + + /* save rbx/rbp/rsi/rdi/r8-r15 */ + mov %rbx, MSHV_VTL_CPU_CONTEXT_rbx(%rax) + mov %rbp, MSHV_VTL_CPU_CONTEXT_rbp(%rax) + mov %rsi, MSHV_VTL_CPU_CONTEXT_rsi(%rax) + mov %rdi, MSHV_VTL_CPU_CONTEXT_rdi(%rax) + mov %r8, MSHV_VTL_CPU_CONTEXT_r8(%rax) + mov %r9, MSHV_VTL_CPU_CONTEXT_r9(%rax) + mov %r10, MSHV_VTL_CPU_CONTEXT_r10(%rax) + mov %r11, MSHV_VTL_CPU_CONTEXT_r11(%rax) + mov %r12, MSHV_VTL_CPU_CONTEXT_r12(%rax) + mov %r13, MSHV_VTL_CPU_CONTEXT_r13(%rax) + mov %r14, MSHV_VTL_CPU_CONTEXT_r14(%rax) + mov %r15, MSHV_VTL_CPU_CONTEXT_r15(%rax) + + /* pop callee-save registers r12-r15, rbx */ + pop %rbx + pop %r15 + pop %r14 + pop %r13 + pop %r12 + + pop %rbp + RET +SYM_FUNC_END(__mshv_vtl_return_call) +/* + * Make sure that static_call_key symbol: __SCK____mshv_vtl_return_hypercall is accessible here. + * Below code is inspired from __ADDRESSABLE(sym) macro. Symbol name is kept simple, to avoid + * naming it something like "__UNIQUE_ID_addressable___SCK____mshv_vtl_return_hypercall_662.0" + * which would otherwise have been generated by the macro. + */ + .section .discard.addressable,"aw" + .align 8 + .type mshv_vtl_return_sym, @object + .size mshv_vtl_return_sym, 8 +mshv_vtl_return_sym: + .quad __SCK____mshv_vtl_return_hypercall diff --git a/arch/x86/include/asm/mshyperv.h b/arch/x86/include/asm/mshyperv.h index 1342d55c2545..10037125099a 100644 --- a/arch/x86/include/asm/mshyperv.h +++ b/arch/x86/include/asm/mshyperv.h @@ -11,6 +11,7 @@ #include #include #include +#include /* * Hyper-V always provides a single IO-APIC at this MMIO address. @@ -269,13 +270,46 @@ static inline u64 hv_get_non_nested_msr(unsigned int reg) { return 0; } static inline int hv_apicid_to_vp_index(u32 apic_id) { return -EINVAL; } #endif /* CONFIG_HYPERV */ +struct mshv_vtl_cpu_context { + union { + struct { + u64 rax; + u64 rcx; + u64 rdx; + u64 rbx; + u64 cr2; + u64 rbp; + u64 rsi; + u64 rdi; + u64 r8; + u64 r9; + u64 r10; + u64 r11; + u64 r12; + u64 r13; + u64 r14; + u64 r15; + }; + u64 gp_regs[16]; + }; + + struct fxregs_state fx_state; +}; #ifdef CONFIG_HYPERV_VTL_MODE void __init hv_vtl_init_platform(void); int __init hv_vtl_early_init(void); +void mshv_vtl_return_call(struct mshv_vtl_cpu_context *vtl0); +void mshv_vtl_return_call_init(u64 vtl_return_offset); +void mshv_vtl_return_hypercall(void); +void __mshv_vtl_return_call(struct mshv_vtl_cpu_context *vtl0); #else static inline void __init hv_vtl_init_platform(void) {} static inline int __init hv_vtl_early_init(void) { return 0; } +static inline void mshv_vtl_return_call(struct mshv_vtl_cpu_context *vtl0) {} +static inline void mshv_vtl_return_call_init(u64 vtl_return_offset) {} +static inline void mshv_vtl_return_hypercall(void) {} +static inline void __mshv_vtl_return_call(struct mshv_vtl_cpu_context *vtl0) {} #endif #include diff --git a/drivers/hv/Kconfig b/drivers/hv/Kconfig index 0b8c391a0342..d4a8d349200c 100644 --- a/drivers/hv/Kconfig +++ b/drivers/hv/Kconfig @@ -17,7 +17,8 @@ config HYPERV config HYPERV_VTL_MODE bool "Enable Linux to boot in VTL context" - depends on (X86_64 || ARM64) && HYPERV + depends on (X86_64 && HAVE_STATIC_CALL) || ARM64 + depends on HYPERV depends on SMP default n help @@ -82,4 +83,28 @@ config MSHV_ROOT If unsure, say N. +config MSHV_VTL + tristate "Microsoft Hyper-V VTL driver" + depends on X86_64 && HYPERV_VTL_MODE + depends on HYPERV_VMBUS + # Mapping VTL0 memory to a userspace process in VTL2 is supported in OpenHCL. + # VTL2 for OpenHCL makes use of Huge Pages to improve performance on VMs, + # specially with large memory requirements. + depends on TRANSPARENT_HUGEPAGE + # MTRRs are controlled by VTL0, and are not specific to individual VTLs. + # Therefore, do not attempt to access or modify MTRRs here. + depends on !MTRR + select CPUMASK_OFFSTACK + select VIRT_XFER_TO_GUEST_WORK + default n + help + Select this option to enable Hyper-V VTL driver support. + This driver provides interfaces for Virtual Machine Manager (VMM) running in VTL2 + userspace to create VTLs and partitions, setup and manage VTL0 memory and + allow userspace to make direct hypercalls. This also allows to map VTL0's address + space to a usermode process in VTL2 and supports getting new VMBus messages and channel + events in VTL2. + + If unsure, say N. + endmenu diff --git a/drivers/hv/Makefile b/drivers/hv/Makefile index 1a1677bf4dac..6d929fb0e13d 100644 --- a/drivers/hv/Makefile +++ b/drivers/hv/Makefile @@ -3,6 +3,7 @@ obj-$(CONFIG_HYPERV_VMBUS) += hv_vmbus.o obj-$(CONFIG_HYPERV_UTILS) += hv_utils.o obj-$(CONFIG_HYPERV_BALLOON) += hv_balloon.o obj-$(CONFIG_MSHV_ROOT) += mshv_root.o +obj-$(CONFIG_MSHV_VTL) += mshv_vtl.o CFLAGS_hv_trace.o = -I$(src) CFLAGS_hv_balloon.o = -I$(src) @@ -14,7 +15,11 @@ hv_vmbus-$(CONFIG_HYPERV_TESTING) += hv_debugfs.o hv_utils-y := hv_util.o hv_kvp.o hv_snapshot.o hv_utils_transport.o mshv_root-y := mshv_root_main.o mshv_synic.o mshv_eventfd.o mshv_irq.o \ mshv_root_hv_call.o mshv_portid_table.o +mshv_vtl-y := mshv_vtl_main.o # Code that must be built-in obj-$(CONFIG_HYPERV) += hv_common.o -obj-$(subst m,y,$(CONFIG_MSHV_ROOT)) += hv_proc.o mshv_common.o +obj-$(subst m,y,$(CONFIG_MSHV_ROOT)) += hv_proc.o +ifneq ($(CONFIG_MSHV_ROOT)$(CONFIG_MSHV_VTL),) + obj-y += mshv_common.o +endif diff --git a/drivers/hv/mshv_vtl.h b/drivers/hv/mshv_vtl.h new file mode 100644 index 000000000000..a6eea52f7aa2 --- /dev/null +++ b/drivers/hv/mshv_vtl.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _MSHV_VTL_H +#define _MSHV_VTL_H + +#include +#include + +struct mshv_vtl_run { + u32 cancel; + u32 vtl_ret_action_size; + u32 pad[2]; + char exit_message[MSHV_MAX_RUN_MSG_SIZE]; + union { + struct mshv_vtl_cpu_context cpu_context; + + /* + * Reserving room for the cpu context to grow and to maintain compatibility + * with user mode. + */ + char reserved[1024]; + }; + char vtl_ret_actions[MSHV_MAX_RUN_MSG_SIZE]; +}; + +#endif /* _MSHV_VTL_H */ diff --git a/drivers/hv/mshv_vtl_main.c b/drivers/hv/mshv_vtl_main.c new file mode 100644 index 000000000000..2cebe9de5a5a --- /dev/null +++ b/drivers/hv/mshv_vtl_main.c @@ -0,0 +1,1392 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2023, Microsoft Corporation. + * + * Author: + * Roman Kisel + * Saurabh Sengar + * Naman Jain + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../kernel/fpu/legacy.h" +#include "mshv.h" +#include "mshv_vtl.h" +#include "hyperv_vmbus.h" + +MODULE_AUTHOR("Microsoft"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Microsoft Hyper-V VTL Driver"); + +#define MSHV_ENTRY_REASON_LOWER_VTL_CALL 0x1 +#define MSHV_ENTRY_REASON_INTERRUPT 0x2 +#define MSHV_ENTRY_REASON_INTERCEPT 0x3 + +#define MSHV_REAL_OFF_SHIFT 16 +#define MSHV_PG_OFF_CPU_MASK (BIT_ULL(MSHV_REAL_OFF_SHIFT) - 1) +#define MSHV_RUN_PAGE_OFFSET 0 +#define MSHV_REG_PAGE_OFFSET 1 +#define VTL2_VMBUS_SINT_INDEX 7 + +static struct device *mem_dev; + +static struct tasklet_struct msg_dpc; +static wait_queue_head_t fd_wait_queue; +static bool has_message; +static struct eventfd_ctx *flag_eventfds[HV_EVENT_FLAGS_COUNT]; +static DEFINE_MUTEX(flag_lock); +static bool __read_mostly mshv_has_reg_page; + +/* hvcall code is of type u16, allocate a bitmap of size (1 << 16) to accommodate it */ +#define MAX_BITMAP_SIZE ((U16_MAX + 1) / 8) + +struct mshv_vtl_hvcall_fd { + u8 allow_bitmap[MAX_BITMAP_SIZE]; + bool allow_map_initialized; + /* + * Used to protect hvcall setup in IOCTLs + */ + struct mutex init_mutex; + struct miscdevice *dev; +}; + +struct mshv_vtl_poll_file { + struct file *file; + wait_queue_entry_t wait; + wait_queue_head_t *wqh; + poll_table pt; + int cpu; +}; + +struct mshv_vtl { + struct device *module_dev; + u64 id; +}; + +struct mshv_vtl_per_cpu { + struct mshv_vtl_run *run; + struct page *reg_page; +}; + +/* SYNIC_OVERLAY_PAGE_MSR - internal, identical to hv_synic_simp */ +union hv_synic_overlay_page_msr { + u64 as_uint64; + struct { + u64 enabled: 1; + u64 reserved: 11; + u64 pfn: 52; + } __packed; +}; + +static struct mutex mshv_vtl_poll_file_lock; +static union hv_register_vsm_page_offsets mshv_vsm_page_offsets; +static union hv_register_vsm_capabilities mshv_vsm_capabilities; + +static DEFINE_PER_CPU(struct mshv_vtl_poll_file, mshv_vtl_poll_file); +static DEFINE_PER_CPU(unsigned long long, num_vtl0_transitions); +static DEFINE_PER_CPU(struct mshv_vtl_per_cpu, mshv_vtl_per_cpu); + +static const union hv_input_vtl input_vtl_zero; +static const union hv_input_vtl input_vtl_normal = { + .use_target_vtl = 1, +}; + +static const struct file_operations mshv_vtl_fops; + +static long +mshv_ioctl_create_vtl(void __user *user_arg, struct device *module_dev) +{ + struct mshv_vtl *vtl; + struct file *file; + int fd; + + vtl = kzalloc(sizeof(*vtl), GFP_KERNEL); + if (!vtl) + return -ENOMEM; + + fd = get_unused_fd_flags(O_CLOEXEC); + if (fd < 0) { + kfree(vtl); + return fd; + } + file = anon_inode_getfile("mshv_vtl", &mshv_vtl_fops, + vtl, O_RDWR); + if (IS_ERR(file)) { + kfree(vtl); + return PTR_ERR(file); + } + vtl->module_dev = module_dev; + fd_install(fd, file); + + return fd; +} + +static long +mshv_ioctl_check_extension(void __user *user_arg) +{ + u32 arg; + + if (copy_from_user(&arg, user_arg, sizeof(arg))) + return -EFAULT; + + switch (arg) { + case MSHV_CAP_CORE_API_STABLE: + return 0; + case MSHV_CAP_REGISTER_PAGE: + return mshv_has_reg_page; + case MSHV_CAP_VTL_RETURN_ACTION: + return mshv_vsm_capabilities.return_action_available; + case MSHV_CAP_DR6_SHARED: + return mshv_vsm_capabilities.dr6_shared; + } + + return -EOPNOTSUPP; +} + +static long +mshv_dev_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg) +{ + struct miscdevice *misc = filp->private_data; + + switch (ioctl) { + case MSHV_CHECK_EXTENSION: + return mshv_ioctl_check_extension((void __user *)arg); + case MSHV_CREATE_VTL: + return mshv_ioctl_create_vtl((void __user *)arg, misc->this_device); + } + + return -ENOTTY; +} + +static const struct file_operations mshv_dev_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = mshv_dev_ioctl, + .llseek = noop_llseek, +}; + +static struct miscdevice mshv_dev = { + .minor = MISC_DYNAMIC_MINOR, + .name = "mshv", + .fops = &mshv_dev_fops, + .mode = 0600, +}; + +static struct mshv_vtl_run *mshv_vtl_this_run(void) +{ + return *this_cpu_ptr(&mshv_vtl_per_cpu.run); +} + +static struct mshv_vtl_run *mshv_vtl_cpu_run(int cpu) +{ + return *per_cpu_ptr(&mshv_vtl_per_cpu.run, cpu); +} + +static struct page *mshv_vtl_cpu_reg_page(int cpu) +{ + return *per_cpu_ptr(&mshv_vtl_per_cpu.reg_page, cpu); +} + +static void mshv_vtl_configure_reg_page(struct mshv_vtl_per_cpu *per_cpu) +{ + struct hv_register_assoc reg_assoc = {}; + union hv_synic_overlay_page_msr overlay = {}; + struct page *reg_page; + + reg_page = alloc_page(GFP_KERNEL | __GFP_ZERO | __GFP_RETRY_MAYFAIL); + if (!reg_page) { + WARN(1, "failed to allocate register page\n"); + return; + } + + overlay.enabled = 1; + overlay.pfn = page_to_hvpfn(reg_page); + reg_assoc.name = HV_X64_REGISTER_REG_PAGE; + reg_assoc.value.reg64 = overlay.as_uint64; + + if (hv_call_set_vp_registers(HV_VP_INDEX_SELF, HV_PARTITION_ID_SELF, + 1, input_vtl_zero, ®_assoc)) { + WARN(1, "failed to setup register page\n"); + __free_page(reg_page); + return; + } + + per_cpu->reg_page = reg_page; + mshv_has_reg_page = true; +} + +static void mshv_vtl_synic_enable_regs(unsigned int cpu) +{ + union hv_synic_sint sint; + + sint.as_uint64 = 0; + sint.vector = HYPERVISOR_CALLBACK_VECTOR; + sint.masked = false; + sint.auto_eoi = hv_recommend_using_aeoi(); + + /* Enable intercepts */ + if (!mshv_vsm_capabilities.intercept_page_available) + hv_set_msr(HV_MSR_SINT0 + HV_SYNIC_INTERCEPTION_SINT_INDEX, + sint.as_uint64); + + /* VTL2 Host VSP SINT is (un)masked when the user mode requests that */ +} + +static int mshv_vtl_get_vsm_regs(void) +{ + struct hv_register_assoc registers[2]; + int ret, count = 2; + + registers[0].name = HV_REGISTER_VSM_CODE_PAGE_OFFSETS; + registers[1].name = HV_REGISTER_VSM_CAPABILITIES; + + ret = hv_call_get_vp_registers(HV_VP_INDEX_SELF, HV_PARTITION_ID_SELF, + count, input_vtl_zero, registers); + if (ret) + return ret; + + mshv_vsm_page_offsets.as_uint64 = registers[0].value.reg64; + mshv_vsm_capabilities.as_uint64 = registers[1].value.reg64; + + return ret; +} + +static int mshv_vtl_configure_vsm_partition(struct device *dev) +{ + union hv_register_vsm_partition_config config; + struct hv_register_assoc reg_assoc; + + config.as_uint64 = 0; + config.default_vtl_protection_mask = HV_MAP_GPA_PERMISSIONS_MASK; + config.enable_vtl_protection = 1; + config.zero_memory_on_reset = 1; + config.intercept_vp_startup = 1; + config.intercept_cpuid_unimplemented = 1; + + if (mshv_vsm_capabilities.intercept_page_available) { + dev_dbg(dev, "using intercept page\n"); + config.intercept_page = 1; + } + + reg_assoc.name = HV_REGISTER_VSM_PARTITION_CONFIG; + reg_assoc.value.reg64 = config.as_uint64; + + return hv_call_set_vp_registers(HV_VP_INDEX_SELF, HV_PARTITION_ID_SELF, + 1, input_vtl_zero, ®_assoc); +} + +static void mshv_vtl_vmbus_isr(void) +{ + struct hv_per_cpu_context *per_cpu; + struct hv_message *msg; + u32 message_type; + union hv_synic_event_flags *event_flags; + struct eventfd_ctx *eventfd; + u16 i; + + per_cpu = this_cpu_ptr(hv_context.cpu_context); + if (smp_processor_id() == 0) { + msg = (struct hv_message *)per_cpu->hyp_synic_message_page + VTL2_VMBUS_SINT_INDEX; + message_type = READ_ONCE(msg->header.message_type); + if (message_type != HVMSG_NONE) + tasklet_schedule(&msg_dpc); + } + + event_flags = (union hv_synic_event_flags *)per_cpu->hyp_synic_event_page + + VTL2_VMBUS_SINT_INDEX; + for_each_set_bit(i, event_flags->flags, HV_EVENT_FLAGS_COUNT) { + if (!sync_test_and_clear_bit(i, event_flags->flags)) + continue; + rcu_read_lock(); + eventfd = READ_ONCE(flag_eventfds[i]); + if (eventfd) + eventfd_signal(eventfd); + rcu_read_unlock(); + } + + vmbus_isr(); +} + +static int mshv_vtl_alloc_context(unsigned int cpu) +{ + struct mshv_vtl_per_cpu *per_cpu = this_cpu_ptr(&mshv_vtl_per_cpu); + + per_cpu->run = (struct mshv_vtl_run *)__get_free_page(GFP_KERNEL | __GFP_ZERO); + if (!per_cpu->run) + return -ENOMEM; + + if (mshv_vsm_capabilities.intercept_page_available) + mshv_vtl_configure_reg_page(per_cpu); + + mshv_vtl_synic_enable_regs(cpu); + + return 0; +} + +static int mshv_vtl_cpuhp_online; + +static int hv_vtl_setup_synic(void) +{ + int ret; + + /* Use our isr to first filter out packets destined for userspace */ + hv_setup_vmbus_handler(mshv_vtl_vmbus_isr); + + ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "hyperv/vtl:online", + mshv_vtl_alloc_context, NULL); + if (ret < 0) { + hv_setup_vmbus_handler(vmbus_isr); + return ret; + } + + mshv_vtl_cpuhp_online = ret; + + return 0; +} + +static void hv_vtl_remove_synic(void) +{ + cpuhp_remove_state(mshv_vtl_cpuhp_online); + hv_setup_vmbus_handler(vmbus_isr); +} + +static int vtl_get_vp_register(struct hv_register_assoc *reg) +{ + return hv_call_get_vp_registers(HV_VP_INDEX_SELF, HV_PARTITION_ID_SELF, + 1, input_vtl_normal, reg); +} + +static int vtl_set_vp_register(struct hv_register_assoc *reg) +{ + return hv_call_set_vp_registers(HV_VP_INDEX_SELF, HV_PARTITION_ID_SELF, + 1, input_vtl_normal, reg); +} + +static int mshv_vtl_ioctl_add_vtl0_mem(struct mshv_vtl *vtl, void __user *arg) +{ + struct mshv_vtl_ram_disposition vtl0_mem; + struct dev_pagemap *pgmap; + void *addr; + + if (copy_from_user(&vtl0_mem, arg, sizeof(vtl0_mem))) + return -EFAULT; + /* vtl0_mem.last_pfn is excluded in the pagemap range for VTL0 as per design */ + if (vtl0_mem.last_pfn <= vtl0_mem.start_pfn) { + dev_err(vtl->module_dev, "range start pfn (%llx) > end pfn (%llx)\n", + vtl0_mem.start_pfn, vtl0_mem.last_pfn); + return -EFAULT; + } + + pgmap = kzalloc(sizeof(*pgmap), GFP_KERNEL); + if (!pgmap) + return -ENOMEM; + + pgmap->ranges[0].start = PFN_PHYS(vtl0_mem.start_pfn); + pgmap->ranges[0].end = PFN_PHYS(vtl0_mem.last_pfn) - 1; + pgmap->nr_range = 1; + pgmap->type = MEMORY_DEVICE_GENERIC; + + /* + * Determine the highest page order that can be used for the given memory range. + * This works best when the range is aligned; i.e. both the start and the length. + */ + pgmap->vmemmap_shift = count_trailing_zeros(vtl0_mem.start_pfn | vtl0_mem.last_pfn); + dev_dbg(vtl->module_dev, + "Add VTL0 memory: start: 0x%llx, end_pfn: 0x%llx, page order: %lu\n", + vtl0_mem.start_pfn, vtl0_mem.last_pfn, pgmap->vmemmap_shift); + + addr = devm_memremap_pages(mem_dev, pgmap); + if (IS_ERR(addr)) { + dev_err(vtl->module_dev, "devm_memremap_pages error: %ld\n", PTR_ERR(addr)); + kfree(pgmap); + return -EFAULT; + } + + /* Don't free pgmap, since it has to stick around until the memory + * is unmapped, which will never happen as there is no scenario + * where VTL0 can be released/shutdown without bringing down VTL2. + */ + return 0; +} + +static void mshv_vtl_cancel(int cpu) +{ + int here = get_cpu(); + + if (here != cpu) { + if (!xchg_relaxed(&mshv_vtl_cpu_run(cpu)->cancel, 1)) + smp_send_reschedule(cpu); + } else { + WRITE_ONCE(mshv_vtl_this_run()->cancel, 1); + } + put_cpu(); +} + +static int mshv_vtl_poll_file_wake(wait_queue_entry_t *wait, unsigned int mode, int sync, void *key) +{ + struct mshv_vtl_poll_file *poll_file = container_of(wait, struct mshv_vtl_poll_file, wait); + + mshv_vtl_cancel(poll_file->cpu); + + return 0; +} + +static void mshv_vtl_ptable_queue_proc(struct file *file, wait_queue_head_t *wqh, poll_table *pt) +{ + struct mshv_vtl_poll_file *poll_file = container_of(pt, struct mshv_vtl_poll_file, pt); + + WARN_ON(poll_file->wqh); + poll_file->wqh = wqh; + add_wait_queue(wqh, &poll_file->wait); +} + +static int mshv_vtl_ioctl_set_poll_file(struct mshv_vtl_set_poll_file __user *user_input) +{ + struct file *file, *old_file; + struct mshv_vtl_poll_file *poll_file; + struct mshv_vtl_set_poll_file input; + + if (copy_from_user(&input, user_input, sizeof(input))) + return -EFAULT; + + if (input.cpu >= num_possible_cpus() || !cpu_online(input.cpu)) + return -EINVAL; + /* + * CPU Hotplug is not supported in VTL2 in OpenHCL, where this kernel driver exists. + * CPU is expected to remain online after above cpu_online() check. + */ + + file = NULL; + file = fget(input.fd); + if (!file) + return -EBADFD; + + poll_file = per_cpu_ptr(&mshv_vtl_poll_file, READ_ONCE(input.cpu)); + if (!poll_file) + return -EINVAL; + + mutex_lock(&mshv_vtl_poll_file_lock); + + if (poll_file->wqh) + remove_wait_queue(poll_file->wqh, &poll_file->wait); + poll_file->wqh = NULL; + + old_file = poll_file->file; + poll_file->file = file; + poll_file->cpu = input.cpu; + + if (file) { + init_waitqueue_func_entry(&poll_file->wait, mshv_vtl_poll_file_wake); + init_poll_funcptr(&poll_file->pt, mshv_vtl_ptable_queue_proc); + vfs_poll(file, &poll_file->pt); + } + + mutex_unlock(&mshv_vtl_poll_file_lock); + + if (old_file) + fput(old_file); + + return 0; +} + +/* Static table mapping register names to their corresponding actions */ +static const struct { + enum hv_register_name reg_name; + int debug_reg_num; /* -1 if not a debug register */ + u32 msr_addr; /* 0 if not an MSR */ +} reg_table[] = { + /* Debug registers */ + {HV_X64_REGISTER_DR0, 0, 0}, + {HV_X64_REGISTER_DR1, 1, 0}, + {HV_X64_REGISTER_DR2, 2, 0}, + {HV_X64_REGISTER_DR3, 3, 0}, + {HV_X64_REGISTER_DR6, 6, 0}, + /* MTRR MSRs */ + {HV_X64_REGISTER_MSR_MTRR_CAP, -1, MSR_MTRRcap}, + {HV_X64_REGISTER_MSR_MTRR_DEF_TYPE, -1, MSR_MTRRdefType}, + {HV_X64_REGISTER_MSR_MTRR_PHYS_BASE0, -1, MTRRphysBase_MSR(0)}, + {HV_X64_REGISTER_MSR_MTRR_PHYS_BASE1, -1, MTRRphysBase_MSR(1)}, + {HV_X64_REGISTER_MSR_MTRR_PHYS_BASE2, -1, MTRRphysBase_MSR(2)}, + {HV_X64_REGISTER_MSR_MTRR_PHYS_BASE3, -1, MTRRphysBase_MSR(3)}, + {HV_X64_REGISTER_MSR_MTRR_PHYS_BASE4, -1, MTRRphysBase_MSR(4)}, + {HV_X64_REGISTER_MSR_MTRR_PHYS_BASE5, -1, MTRRphysBase_MSR(5)}, + {HV_X64_REGISTER_MSR_MTRR_PHYS_BASE6, -1, MTRRphysBase_MSR(6)}, + {HV_X64_REGISTER_MSR_MTRR_PHYS_BASE7, -1, MTRRphysBase_MSR(7)}, + {HV_X64_REGISTER_MSR_MTRR_PHYS_BASE8, -1, MTRRphysBase_MSR(8)}, + {HV_X64_REGISTER_MSR_MTRR_PHYS_BASE9, -1, MTRRphysBase_MSR(9)}, + {HV_X64_REGISTER_MSR_MTRR_PHYS_BASEA, -1, MTRRphysBase_MSR(0xa)}, + {HV_X64_REGISTER_MSR_MTRR_PHYS_BASEB, -1, MTRRphysBase_MSR(0xb)}, + {HV_X64_REGISTER_MSR_MTRR_PHYS_BASEC, -1, MTRRphysBase_MSR(0xc)}, + {HV_X64_REGISTER_MSR_MTRR_PHYS_BASED, -1, MTRRphysBase_MSR(0xd)}, + {HV_X64_REGISTER_MSR_MTRR_PHYS_BASEE, -1, MTRRphysBase_MSR(0xe)}, + {HV_X64_REGISTER_MSR_MTRR_PHYS_BASEF, -1, MTRRphysBase_MSR(0xf)}, + {HV_X64_REGISTER_MSR_MTRR_PHYS_MASK0, -1, MTRRphysMask_MSR(0)}, + {HV_X64_REGISTER_MSR_MTRR_PHYS_MASK1, -1, MTRRphysMask_MSR(1)}, + {HV_X64_REGISTER_MSR_MTRR_PHYS_MASK2, -1, MTRRphysMask_MSR(2)}, + {HV_X64_REGISTER_MSR_MTRR_PHYS_MASK3, -1, MTRRphysMask_MSR(3)}, + {HV_X64_REGISTER_MSR_MTRR_PHYS_MASK4, -1, MTRRphysMask_MSR(4)}, + {HV_X64_REGISTER_MSR_MTRR_PHYS_MASK5, -1, MTRRphysMask_MSR(5)}, + {HV_X64_REGISTER_MSR_MTRR_PHYS_MASK6, -1, MTRRphysMask_MSR(6)}, + {HV_X64_REGISTER_MSR_MTRR_PHYS_MASK7, -1, MTRRphysMask_MSR(7)}, + {HV_X64_REGISTER_MSR_MTRR_PHYS_MASK8, -1, MTRRphysMask_MSR(8)}, + {HV_X64_REGISTER_MSR_MTRR_PHYS_MASK9, -1, MTRRphysMask_MSR(9)}, + {HV_X64_REGISTER_MSR_MTRR_PHYS_MASKA, -1, MTRRphysMask_MSR(0xa)}, + {HV_X64_REGISTER_MSR_MTRR_PHYS_MASKB, -1, MTRRphysMask_MSR(0xb)}, + {HV_X64_REGISTER_MSR_MTRR_PHYS_MASKC, -1, MTRRphysMask_MSR(0xc)}, + {HV_X64_REGISTER_MSR_MTRR_PHYS_MASKD, -1, MTRRphysMask_MSR(0xd)}, + {HV_X64_REGISTER_MSR_MTRR_PHYS_MASKE, -1, MTRRphysMask_MSR(0xe)}, + {HV_X64_REGISTER_MSR_MTRR_PHYS_MASKF, -1, MTRRphysMask_MSR(0xf)}, + {HV_X64_REGISTER_MSR_MTRR_FIX64K00000, -1, MSR_MTRRfix64K_00000}, + {HV_X64_REGISTER_MSR_MTRR_FIX16K80000, -1, MSR_MTRRfix16K_80000}, + {HV_X64_REGISTER_MSR_MTRR_FIX16KA0000, -1, MSR_MTRRfix16K_A0000}, + {HV_X64_REGISTER_MSR_MTRR_FIX4KC0000, -1, MSR_MTRRfix4K_C0000}, + {HV_X64_REGISTER_MSR_MTRR_FIX4KC8000, -1, MSR_MTRRfix4K_C8000}, + {HV_X64_REGISTER_MSR_MTRR_FIX4KD0000, -1, MSR_MTRRfix4K_D0000}, + {HV_X64_REGISTER_MSR_MTRR_FIX4KD8000, -1, MSR_MTRRfix4K_D8000}, + {HV_X64_REGISTER_MSR_MTRR_FIX4KE0000, -1, MSR_MTRRfix4K_E0000}, + {HV_X64_REGISTER_MSR_MTRR_FIX4KE8000, -1, MSR_MTRRfix4K_E8000}, + {HV_X64_REGISTER_MSR_MTRR_FIX4KF0000, -1, MSR_MTRRfix4K_F0000}, + {HV_X64_REGISTER_MSR_MTRR_FIX4KF8000, -1, MSR_MTRRfix4K_F8000}, +}; + +static int mshv_vtl_get_set_reg(struct hv_register_assoc *regs, bool set) +{ + u64 *reg64; + enum hv_register_name gpr_name; + int i; + + gpr_name = regs->name; + reg64 = ®s->value.reg64; + + /* Search for the register in the table */ + for (i = 0; i < ARRAY_SIZE(reg_table); i++) { + if (reg_table[i].reg_name != gpr_name) + continue; + if (reg_table[i].debug_reg_num != -1) { + /* Handle debug registers */ + if (gpr_name == HV_X64_REGISTER_DR6 && + !mshv_vsm_capabilities.dr6_shared) + goto hypercall; + if (set) + native_set_debugreg(reg_table[i].debug_reg_num, *reg64); + else + *reg64 = native_get_debugreg(reg_table[i].debug_reg_num); + } else { + /* Handle MSRs */ + if (set) + wrmsrl(reg_table[i].msr_addr, *reg64); + else + rdmsrl(reg_table[i].msr_addr, *reg64); + } + return 0; + } + +hypercall: + return 1; +} + +static void mshv_vtl_return(struct mshv_vtl_cpu_context *vtl0) +{ + struct hv_vp_assist_page *hvp; + + hvp = hv_vp_assist_page[smp_processor_id()]; + + /* + * Process signal event direct set in the run page, if any. + */ + if (mshv_vsm_capabilities.return_action_available) { + u32 offset = READ_ONCE(mshv_vtl_this_run()->vtl_ret_action_size); + + WRITE_ONCE(mshv_vtl_this_run()->vtl_ret_action_size, 0); + + /* + * Hypervisor will take care of clearing out the actions + * set in the assist page. + */ + memcpy(hvp->vtl_ret_actions, + mshv_vtl_this_run()->vtl_ret_actions, + min_t(u32, offset, sizeof(hvp->vtl_ret_actions))); + } + + mshv_vtl_return_call(vtl0); +} + +static bool mshv_vtl_process_intercept(void) +{ + struct hv_per_cpu_context *mshv_cpu; + void *synic_message_page; + struct hv_message *msg; + u32 message_type; + + mshv_cpu = this_cpu_ptr(hv_context.cpu_context); + synic_message_page = mshv_cpu->hyp_synic_message_page; + if (unlikely(!synic_message_page)) + return true; + + msg = (struct hv_message *)synic_message_page + HV_SYNIC_INTERCEPTION_SINT_INDEX; + message_type = READ_ONCE(msg->header.message_type); + if (message_type == HVMSG_NONE) + return true; + + memcpy(mshv_vtl_this_run()->exit_message, msg, sizeof(*msg)); + vmbus_signal_eom(msg, message_type); + + return false; +} + +static int mshv_vtl_ioctl_return_to_lower_vtl(void) +{ + preempt_disable(); + for (;;) { + unsigned long irq_flags; + struct hv_vp_assist_page *hvp; + int ret; + + if (__xfer_to_guest_mode_work_pending()) { + preempt_enable(); + ret = xfer_to_guest_mode_handle_work(); + if (ret) + return ret; + preempt_disable(); + } + + local_irq_save(irq_flags); + if (READ_ONCE(mshv_vtl_this_run()->cancel)) { + local_irq_restore(irq_flags); + preempt_enable(); + return -EINTR; + } + + mshv_vtl_return(&mshv_vtl_this_run()->cpu_context); + local_irq_restore(irq_flags); + + hvp = hv_vp_assist_page[smp_processor_id()]; + this_cpu_inc(num_vtl0_transitions); + switch (hvp->vtl_entry_reason) { + case MSHV_ENTRY_REASON_INTERRUPT: + if (!mshv_vsm_capabilities.intercept_page_available && + likely(!mshv_vtl_process_intercept())) + goto done; + break; + + case MSHV_ENTRY_REASON_INTERCEPT: + WARN_ON(!mshv_vsm_capabilities.intercept_page_available); + memcpy(mshv_vtl_this_run()->exit_message, hvp->intercept_message, + sizeof(hvp->intercept_message)); + goto done; + + default: + panic("unknown entry reason: %d", hvp->vtl_entry_reason); + } + } + +done: + preempt_enable(); + + return 0; +} + +static long +mshv_vtl_ioctl_get_regs(void __user *user_args) +{ + struct mshv_vp_registers args; + struct hv_register_assoc reg; + long ret; + + if (copy_from_user(&args, user_args, sizeof(args))) + return -EFAULT; + + /* This IOCTL supports processing only one register at a time. */ + if (args.count != 1) + return -EINVAL; + + if (copy_from_user(®, (void __user *)args.regs_ptr, + sizeof(reg))) + return -EFAULT; + + ret = mshv_vtl_get_set_reg(®, false); + if (!ret) + goto copy_args; /* No need of hypercall */ + ret = vtl_get_vp_register(®); + if (ret) + return ret; + +copy_args: + if (copy_to_user((void __user *)args.regs_ptr, ®, sizeof(reg))) + ret = -EFAULT; + + return ret; +} + +static long +mshv_vtl_ioctl_set_regs(void __user *user_args) +{ + struct mshv_vp_registers args; + struct hv_register_assoc reg; + long ret; + + if (copy_from_user(&args, user_args, sizeof(args))) + return -EFAULT; + + /* This IOCTL supports processing only one register at a time. */ + if (args.count != 1) + return -EINVAL; + + if (copy_from_user(®, (void __user *)args.regs_ptr, sizeof(reg))) + return -EFAULT; + + ret = mshv_vtl_get_set_reg(®, true); + if (!ret) + return ret; /* No need of hypercall */ + ret = vtl_set_vp_register(®); + + return ret; +} + +static long +mshv_vtl_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg) +{ + long ret; + struct mshv_vtl *vtl = filp->private_data; + + switch (ioctl) { + case MSHV_SET_POLL_FILE: + ret = mshv_vtl_ioctl_set_poll_file((struct mshv_vtl_set_poll_file __user *)arg); + break; + case MSHV_GET_VP_REGISTERS: + ret = mshv_vtl_ioctl_get_regs((void __user *)arg); + break; + case MSHV_SET_VP_REGISTERS: + ret = mshv_vtl_ioctl_set_regs((void __user *)arg); + break; + case MSHV_RETURN_TO_LOWER_VTL: + ret = mshv_vtl_ioctl_return_to_lower_vtl(); + break; + case MSHV_ADD_VTL0_MEMORY: + ret = mshv_vtl_ioctl_add_vtl0_mem(vtl, (void __user *)arg); + break; + default: + dev_err(vtl->module_dev, "invalid vtl ioctl: %#x\n", ioctl); + ret = -ENOTTY; + } + + return ret; +} + +static vm_fault_t mshv_vtl_fault(struct vm_fault *vmf) +{ + struct page *page; + int cpu = vmf->pgoff & MSHV_PG_OFF_CPU_MASK; + int real_off = vmf->pgoff >> MSHV_REAL_OFF_SHIFT; + + if (!cpu_online(cpu)) + return VM_FAULT_SIGBUS; + /* + * CPU Hotplug is not supported in VTL2 in OpenHCL, where this kernel driver exists. + * CPU is expected to remain online after above cpu_online() check. + */ + + if (real_off == MSHV_RUN_PAGE_OFFSET) { + page = virt_to_page(mshv_vtl_cpu_run(cpu)); + } else if (real_off == MSHV_REG_PAGE_OFFSET) { + if (!mshv_has_reg_page) + return VM_FAULT_SIGBUS; + page = mshv_vtl_cpu_reg_page(cpu); + } else { + return VM_FAULT_NOPAGE; + } + + get_page(page); + vmf->page = page; + + return 0; +} + +static const struct vm_operations_struct mshv_vtl_vm_ops = { + .fault = mshv_vtl_fault, +}; + +static int mshv_vtl_mmap(struct file *filp, struct vm_area_struct *vma) +{ + vma->vm_ops = &mshv_vtl_vm_ops; + + return 0; +} + +static int mshv_vtl_release(struct inode *inode, struct file *filp) +{ + struct mshv_vtl *vtl = filp->private_data; + + kfree(vtl); + + return 0; +} + +static const struct file_operations mshv_vtl_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = mshv_vtl_ioctl, + .release = mshv_vtl_release, + .mmap = mshv_vtl_mmap, +}; + +static void mshv_vtl_synic_mask_vmbus_sint(const u8 *mask) +{ + union hv_synic_sint sint; + + sint.as_uint64 = 0; + sint.vector = HYPERVISOR_CALLBACK_VECTOR; + sint.masked = (*mask != 0); + sint.auto_eoi = hv_recommend_using_aeoi(); + + hv_set_msr(HV_MSR_SINT0 + VTL2_VMBUS_SINT_INDEX, + sint.as_uint64); + + if (!sint.masked) + pr_debug("%s: Unmasking VTL2 VMBUS SINT on VP %d\n", __func__, smp_processor_id()); + else + pr_debug("%s: Masking VTL2 VMBUS SINT on VP %d\n", __func__, smp_processor_id()); +} + +static void mshv_vtl_read_remote(void *buffer) +{ + struct hv_per_cpu_context *mshv_cpu = this_cpu_ptr(hv_context.cpu_context); + struct hv_message *msg = (struct hv_message *)mshv_cpu->hyp_synic_message_page + + VTL2_VMBUS_SINT_INDEX; + u32 message_type = READ_ONCE(msg->header.message_type); + + WRITE_ONCE(has_message, false); + if (message_type == HVMSG_NONE) + return; + + memcpy(buffer, msg, sizeof(*msg)); + vmbus_signal_eom(msg, message_type); +} + +static bool vtl_synic_mask_vmbus_sint_masked = true; + +static ssize_t mshv_vtl_sint_read(struct file *filp, char __user *arg, size_t size, loff_t *offset) +{ + struct hv_message msg = {}; + int ret; + + if (size < sizeof(msg)) + return -EINVAL; + + for (;;) { + smp_call_function_single(VMBUS_CONNECT_CPU, mshv_vtl_read_remote, &msg, true); + if (msg.header.message_type != HVMSG_NONE) + break; + + if (READ_ONCE(vtl_synic_mask_vmbus_sint_masked)) + return 0; /* EOF */ + + if (filp->f_flags & O_NONBLOCK) + return -EAGAIN; + + ret = wait_event_interruptible(fd_wait_queue, + READ_ONCE(has_message) || + READ_ONCE(vtl_synic_mask_vmbus_sint_masked)); + if (ret) + return ret; + } + + if (copy_to_user(arg, &msg, sizeof(msg))) + return -EFAULT; + + return sizeof(msg); +} + +static __poll_t mshv_vtl_sint_poll(struct file *filp, poll_table *wait) +{ + __poll_t mask = 0; + + poll_wait(filp, &fd_wait_queue, wait); + if (READ_ONCE(has_message) || READ_ONCE(vtl_synic_mask_vmbus_sint_masked)) + mask |= EPOLLIN | EPOLLRDNORM; + + return mask; +} + +static void mshv_vtl_sint_on_msg_dpc(unsigned long data) +{ + WRITE_ONCE(has_message, true); + wake_up_interruptible_poll(&fd_wait_queue, EPOLLIN); +} + +static int mshv_vtl_sint_ioctl_post_msg(struct mshv_vtl_sint_post_msg __user *arg) +{ + struct mshv_vtl_sint_post_msg message; + u8 payload[HV_MESSAGE_PAYLOAD_BYTE_COUNT]; + + if (copy_from_user(&message, arg, sizeof(message))) + return -EFAULT; + if (message.payload_size > HV_MESSAGE_PAYLOAD_BYTE_COUNT) + return -EINVAL; + if (copy_from_user(payload, (void __user *)message.payload_ptr, + message.payload_size)) + return -EFAULT; + + return hv_post_message((union hv_connection_id)message.connection_id, + message.message_type, (void *)payload, + message.payload_size); +} + +static int mshv_vtl_sint_ioctl_signal_event(struct mshv_vtl_signal_event __user *arg) +{ + u64 input, status; + struct mshv_vtl_signal_event signal_event; + + if (copy_from_user(&signal_event, arg, sizeof(signal_event))) + return -EFAULT; + + input = signal_event.connection_id | ((u64)signal_event.flag << 32); + + status = hv_do_fast_hypercall8(HVCALL_SIGNAL_EVENT, input); + + return hv_result_to_errno(status); +} + +static int mshv_vtl_sint_ioctl_set_eventfd(struct mshv_vtl_set_eventfd __user *arg) +{ + struct mshv_vtl_set_eventfd set_eventfd; + struct eventfd_ctx *eventfd, *old_eventfd; + + if (copy_from_user(&set_eventfd, arg, sizeof(set_eventfd))) + return -EFAULT; + if (set_eventfd.flag >= HV_EVENT_FLAGS_COUNT) + return -EINVAL; + + eventfd = NULL; + if (set_eventfd.fd >= 0) { + eventfd = eventfd_ctx_fdget(set_eventfd.fd); + if (IS_ERR(eventfd)) + return PTR_ERR(eventfd); + } + + guard(mutex)(&flag_lock); + old_eventfd = READ_ONCE(flag_eventfds[set_eventfd.flag]); + WRITE_ONCE(flag_eventfds[set_eventfd.flag], eventfd); + + if (old_eventfd) { + synchronize_rcu(); + eventfd_ctx_put(old_eventfd); + } + + return 0; +} + +static int mshv_vtl_sint_ioctl_pause_msg_stream(struct mshv_sint_mask __user *arg) +{ + static DEFINE_MUTEX(vtl2_vmbus_sint_mask_mutex); + struct mshv_sint_mask mask; + + if (copy_from_user(&mask, arg, sizeof(mask))) + return -EFAULT; + guard(mutex)(&vtl2_vmbus_sint_mask_mutex); + on_each_cpu((smp_call_func_t)mshv_vtl_synic_mask_vmbus_sint, &mask.mask, 1); + WRITE_ONCE(vtl_synic_mask_vmbus_sint_masked, mask.mask != 0); + if (mask.mask) + wake_up_interruptible_poll(&fd_wait_queue, EPOLLIN); + + return 0; +} + +static long mshv_vtl_sint_ioctl(struct file *f, unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + case MSHV_SINT_POST_MESSAGE: + return mshv_vtl_sint_ioctl_post_msg((struct mshv_vtl_sint_post_msg __user *)arg); + case MSHV_SINT_SIGNAL_EVENT: + return mshv_vtl_sint_ioctl_signal_event((struct mshv_vtl_signal_event __user *)arg); + case MSHV_SINT_SET_EVENTFD: + return mshv_vtl_sint_ioctl_set_eventfd((struct mshv_vtl_set_eventfd __user *)arg); + case MSHV_SINT_PAUSE_MESSAGE_STREAM: + return mshv_vtl_sint_ioctl_pause_msg_stream((struct mshv_sint_mask __user *)arg); + default: + return -ENOIOCTLCMD; + } +} + +static const struct file_operations mshv_vtl_sint_ops = { + .owner = THIS_MODULE, + .read = mshv_vtl_sint_read, + .poll = mshv_vtl_sint_poll, + .unlocked_ioctl = mshv_vtl_sint_ioctl, +}; + +static struct miscdevice mshv_vtl_sint_dev = { + .name = "mshv_sint", + .fops = &mshv_vtl_sint_ops, + .mode = 0600, + .minor = MISC_DYNAMIC_MINOR, +}; + +static int mshv_vtl_hvcall_dev_open(struct inode *node, struct file *f) +{ + struct miscdevice *dev = f->private_data; + struct mshv_vtl_hvcall_fd *fd; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + fd = vzalloc(sizeof(*fd)); + if (!fd) + return -ENOMEM; + fd->dev = dev; + f->private_data = fd; + mutex_init(&fd->init_mutex); + + return 0; +} + +static int mshv_vtl_hvcall_dev_release(struct inode *node, struct file *f) +{ + struct mshv_vtl_hvcall_fd *fd; + + fd = f->private_data; + if (fd) { + vfree(fd); + f->private_data = NULL; + } + + return 0; +} + +static int mshv_vtl_hvcall_do_setup(struct mshv_vtl_hvcall_fd *fd, + struct mshv_vtl_hvcall_setup __user *hvcall_setup_user) +{ + struct mshv_vtl_hvcall_setup hvcall_setup; + + guard(mutex)(&fd->init_mutex); + + if (fd->allow_map_initialized) { + dev_err(fd->dev->this_device, + "Hypercall allow map has already been set, pid %d\n", + current->pid); + return -EINVAL; + } + + if (copy_from_user(&hvcall_setup, hvcall_setup_user, + sizeof(struct mshv_vtl_hvcall_setup))) { + return -EFAULT; + } + if (hvcall_setup.bitmap_array_size > ARRAY_SIZE(fd->allow_bitmap)) + return -EINVAL; + + if (copy_from_user(&fd->allow_bitmap, + (void __user *)hvcall_setup.allow_bitmap_ptr, + hvcall_setup.bitmap_array_size)) { + return -EFAULT; + } + + dev_info(fd->dev->this_device, "Hypercall allow map has been set, pid %d\n", + current->pid); + fd->allow_map_initialized = true; + return 0; +} + +static bool mshv_vtl_hvcall_is_allowed(struct mshv_vtl_hvcall_fd *fd, u16 call_code) +{ + return test_bit(call_code, (unsigned long *)fd->allow_bitmap); +} + +static int mshv_vtl_hvcall_call(struct mshv_vtl_hvcall_fd *fd, + struct mshv_vtl_hvcall __user *hvcall_user) +{ + struct mshv_vtl_hvcall hvcall; + void *in, *out; + int ret; + + if (copy_from_user(&hvcall, hvcall_user, sizeof(struct mshv_vtl_hvcall))) + return -EFAULT; + if (hvcall.input_size > HV_HYP_PAGE_SIZE) + return -EINVAL; + if (hvcall.output_size > HV_HYP_PAGE_SIZE) + return -EINVAL; + + /* + * By default, all hypercalls are not allowed. + * The user mode code has to set up the allow bitmap once. + */ + + if (!mshv_vtl_hvcall_is_allowed(fd, hvcall.control & 0xFFFF)) { + dev_err(fd->dev->this_device, + "Hypercall with control data %#llx isn't allowed\n", + hvcall.control); + return -EPERM; + } + + /* + * This may create a problem for Confidential VM (CVM) usecase where we need to use + * Hyper-V driver allocated per-cpu input and output pages (hyperv_pcpu_input_arg and + * hyperv_pcpu_output_arg) for making a hypervisor call. + * + * TODO: Take care of this when CVM support is added. + */ + in = (void *)__get_free_page(GFP_KERNEL); + out = (void *)__get_free_page(GFP_KERNEL); + + if (copy_from_user(in, (void __user *)hvcall.input_ptr, hvcall.input_size)) { + ret = -EFAULT; + goto free_pages; + } + + hvcall.status = hv_do_hypercall(hvcall.control, in, out); + + if (copy_to_user((void __user *)hvcall.output_ptr, out, hvcall.output_size)) { + ret = -EFAULT; + goto free_pages; + } + ret = put_user(hvcall.status, &hvcall_user->status); +free_pages: + free_page((unsigned long)in); + free_page((unsigned long)out); + + return ret; +} + +static long mshv_vtl_hvcall_dev_ioctl(struct file *f, unsigned int cmd, unsigned long arg) +{ + struct mshv_vtl_hvcall_fd *fd = f->private_data; + + switch (cmd) { + case MSHV_HVCALL_SETUP: + return mshv_vtl_hvcall_do_setup(fd, (struct mshv_vtl_hvcall_setup __user *)arg); + case MSHV_HVCALL: + return mshv_vtl_hvcall_call(fd, (struct mshv_vtl_hvcall __user *)arg); + default: + break; + } + + return -ENOIOCTLCMD; +} + +static const struct file_operations mshv_vtl_hvcall_dev_file_ops = { + .owner = THIS_MODULE, + .open = mshv_vtl_hvcall_dev_open, + .release = mshv_vtl_hvcall_dev_release, + .unlocked_ioctl = mshv_vtl_hvcall_dev_ioctl, +}; + +static struct miscdevice mshv_vtl_hvcall_dev = { + .name = "mshv_hvcall", + .nodename = "mshv_hvcall", + .fops = &mshv_vtl_hvcall_dev_file_ops, + .mode = 0600, + .minor = MISC_DYNAMIC_MINOR, +}; + +static int mshv_vtl_low_open(struct inode *inodep, struct file *filp) +{ + pid_t pid = task_pid_vnr(current); + uid_t uid = current_uid().val; + int ret = 0; + + pr_debug("%s: Opening VTL low, task group %d, uid %d\n", __func__, pid, uid); + + if (capable(CAP_SYS_ADMIN)) { + filp->private_data = inodep; + } else { + pr_err("%s: VTL low open failed: CAP_SYS_ADMIN required. task group %d, uid %d", + __func__, pid, uid); + ret = -EPERM; + } + + return ret; +} + +static bool can_fault(struct vm_fault *vmf, unsigned long size, unsigned long *pfn) +{ + unsigned long mask = size - 1; + unsigned long start = vmf->address & ~mask; + unsigned long end = start + size; + bool is_valid; + + is_valid = (vmf->address & mask) == ((vmf->pgoff << PAGE_SHIFT) & mask) && + start >= vmf->vma->vm_start && + end <= vmf->vma->vm_end; + + if (is_valid) + *pfn = vmf->pgoff & ~(mask >> PAGE_SHIFT); + + return is_valid; +} + +static vm_fault_t mshv_vtl_low_huge_fault(struct vm_fault *vmf, unsigned int order) +{ + unsigned long pfn = vmf->pgoff; + vm_fault_t ret = VM_FAULT_FALLBACK; + + switch (order) { + case 0: + return vmf_insert_mixed(vmf->vma, vmf->address, pfn); + + case PMD_ORDER: + if (can_fault(vmf, PMD_SIZE, &pfn)) + ret = vmf_insert_pfn_pmd(vmf, pfn, vmf->flags & FAULT_FLAG_WRITE); + return ret; + + case PUD_ORDER: + if (can_fault(vmf, PUD_SIZE, &pfn)) + ret = vmf_insert_pfn_pud(vmf, pfn, vmf->flags & FAULT_FLAG_WRITE); + return ret; + + default: + return VM_FAULT_SIGBUS; + } +} + +static vm_fault_t mshv_vtl_low_fault(struct vm_fault *vmf) +{ + return mshv_vtl_low_huge_fault(vmf, 0); +} + +static const struct vm_operations_struct mshv_vtl_low_vm_ops = { + .fault = mshv_vtl_low_fault, + .huge_fault = mshv_vtl_low_huge_fault, +}; + +static int mshv_vtl_low_mmap(struct file *filp, struct vm_area_struct *vma) +{ + vma->vm_ops = &mshv_vtl_low_vm_ops; + vm_flags_set(vma, VM_HUGEPAGE | VM_MIXEDMAP); + + return 0; +} + +static const struct file_operations mshv_vtl_low_file_ops = { + .owner = THIS_MODULE, + .open = mshv_vtl_low_open, + .mmap = mshv_vtl_low_mmap, +}; + +static struct miscdevice mshv_vtl_low = { + .name = "mshv_vtl_low", + .nodename = "mshv_vtl_low", + .fops = &mshv_vtl_low_file_ops, + .mode = 0600, + .minor = MISC_DYNAMIC_MINOR, +}; + +static int __init mshv_vtl_init(void) +{ + int ret; + struct device *dev = mshv_dev.this_device; + + /* + * This creates /dev/mshv which provides functionality to create VTLs and partitions. + */ + ret = misc_register(&mshv_dev); + if (ret) { + dev_err(dev, "mshv device register failed: %d\n", ret); + goto free_dev; + } + + tasklet_init(&msg_dpc, mshv_vtl_sint_on_msg_dpc, 0); + init_waitqueue_head(&fd_wait_queue); + + if (mshv_vtl_get_vsm_regs()) { + dev_emerg(dev, "Unable to get VSM capabilities !!\n"); + ret = -ENODEV; + goto free_dev; + } + if (mshv_vtl_configure_vsm_partition(dev)) { + dev_emerg(dev, "VSM configuration failed !!\n"); + ret = -ENODEV; + goto free_dev; + } + + mshv_vtl_return_call_init(mshv_vsm_page_offsets.vtl_return_offset); + ret = hv_vtl_setup_synic(); + if (ret) + goto free_dev; + + /* + * mshv_sint device adds VMBus relay ioctl support. + * This provides a channel for VTL0 to communicate with VTL2. + */ + ret = misc_register(&mshv_vtl_sint_dev); + if (ret) + goto free_synic; + + /* + * mshv_hvcall device adds interface to enable userspace for direct hypercalls support. + */ + ret = misc_register(&mshv_vtl_hvcall_dev); + if (ret) + goto free_sint; + + /* + * mshv_vtl_low device is used to map VTL0 address space to a user-mode process in VTL2. + * It implements mmap() to allow a user-mode process in VTL2 to map to the address of VTL0. + */ + ret = misc_register(&mshv_vtl_low); + if (ret) + goto free_hvcall; + + /* + * "mshv vtl mem dev" device is later used to setup VTL0 memory. + */ + mem_dev = kzalloc(sizeof(*mem_dev), GFP_KERNEL); + if (!mem_dev) { + ret = -ENOMEM; + goto free_low; + } + + mutex_init(&mshv_vtl_poll_file_lock); + + device_initialize(mem_dev); + dev_set_name(mem_dev, "mshv vtl mem dev"); + ret = device_add(mem_dev); + if (ret) { + dev_err(dev, "mshv vtl mem dev add: %d\n", ret); + goto free_mem; + } + + return 0; + +free_mem: + kfree(mem_dev); +free_low: + misc_deregister(&mshv_vtl_low); +free_hvcall: + misc_deregister(&mshv_vtl_hvcall_dev); +free_sint: + misc_deregister(&mshv_vtl_sint_dev); +free_synic: + hv_vtl_remove_synic(); +free_dev: + misc_deregister(&mshv_dev); + + return ret; +} + +static void __exit mshv_vtl_exit(void) +{ + device_del(mem_dev); + kfree(mem_dev); + misc_deregister(&mshv_vtl_low); + misc_deregister(&mshv_vtl_hvcall_dev); + misc_deregister(&mshv_vtl_sint_dev); + hv_vtl_remove_synic(); + misc_deregister(&mshv_dev); +} + +module_init(mshv_vtl_init); +module_exit(mshv_vtl_exit); diff --git a/include/hyperv/hvgdk_mini.h b/include/hyperv/hvgdk_mini.h index 7499a679e60a..1d5ce11be8b6 100644 --- a/include/hyperv/hvgdk_mini.h +++ b/include/hyperv/hvgdk_mini.h @@ -885,6 +885,48 @@ struct hv_get_vp_from_apic_id_in { u32 apic_ids[]; } __packed; +union hv_register_vsm_partition_config { + u64 as_uint64; + struct { + u64 enable_vtl_protection : 1; + u64 default_vtl_protection_mask : 4; + u64 zero_memory_on_reset : 1; + u64 deny_lower_vtl_startup : 1; + u64 intercept_acceptance : 1; + u64 intercept_enable_vtl_protection : 1; + u64 intercept_vp_startup : 1; + u64 intercept_cpuid_unimplemented : 1; + u64 intercept_unrecoverable_exception : 1; + u64 intercept_page : 1; + u64 mbz : 51; + } __packed; +}; + +union hv_register_vsm_capabilities { + u64 as_uint64; + struct { + u64 dr6_shared: 1; + u64 mbec_vtl_mask: 16; + u64 deny_lower_vtl_startup: 1; + u64 supervisor_shadow_stack: 1; + u64 hardware_hvpt_available: 1; + u64 software_hvpt_available: 1; + u64 hardware_hvpt_range_bits: 6; + u64 intercept_page_available: 1; + u64 return_action_available: 1; + u64 reserved: 35; + } __packed; +}; + +union hv_register_vsm_page_offsets { + struct { + u64 vtl_call_offset : 12; + u64 vtl_return_offset : 12; + u64 reserved_mbz : 40; + } __packed; + u64 as_uint64; +}; + struct hv_nested_enlightenments_control { struct { u32 directhypercall : 1; @@ -1007,6 +1049,70 @@ enum hv_register_name { /* VSM */ HV_REGISTER_VSM_VP_STATUS = 0x000D0003, + + /* Synthetic VSM registers */ + HV_REGISTER_VSM_CODE_PAGE_OFFSETS = 0x000D0002, + HV_REGISTER_VSM_CAPABILITIES = 0x000D0006, + HV_REGISTER_VSM_PARTITION_CONFIG = 0x000D0007, + +#if defined(CONFIG_X86) + /* X64 Debug Registers */ + HV_X64_REGISTER_DR0 = 0x00050000, + HV_X64_REGISTER_DR1 = 0x00050001, + HV_X64_REGISTER_DR2 = 0x00050002, + HV_X64_REGISTER_DR3 = 0x00050003, + HV_X64_REGISTER_DR6 = 0x00050004, + HV_X64_REGISTER_DR7 = 0x00050005, + + /* X64 Cache control MSRs */ + HV_X64_REGISTER_MSR_MTRR_CAP = 0x0008000D, + HV_X64_REGISTER_MSR_MTRR_DEF_TYPE = 0x0008000E, + HV_X64_REGISTER_MSR_MTRR_PHYS_BASE0 = 0x00080010, + HV_X64_REGISTER_MSR_MTRR_PHYS_BASE1 = 0x00080011, + HV_X64_REGISTER_MSR_MTRR_PHYS_BASE2 = 0x00080012, + HV_X64_REGISTER_MSR_MTRR_PHYS_BASE3 = 0x00080013, + HV_X64_REGISTER_MSR_MTRR_PHYS_BASE4 = 0x00080014, + HV_X64_REGISTER_MSR_MTRR_PHYS_BASE5 = 0x00080015, + HV_X64_REGISTER_MSR_MTRR_PHYS_BASE6 = 0x00080016, + HV_X64_REGISTER_MSR_MTRR_PHYS_BASE7 = 0x00080017, + HV_X64_REGISTER_MSR_MTRR_PHYS_BASE8 = 0x00080018, + HV_X64_REGISTER_MSR_MTRR_PHYS_BASE9 = 0x00080019, + HV_X64_REGISTER_MSR_MTRR_PHYS_BASEA = 0x0008001A, + HV_X64_REGISTER_MSR_MTRR_PHYS_BASEB = 0x0008001B, + HV_X64_REGISTER_MSR_MTRR_PHYS_BASEC = 0x0008001C, + HV_X64_REGISTER_MSR_MTRR_PHYS_BASED = 0x0008001D, + HV_X64_REGISTER_MSR_MTRR_PHYS_BASEE = 0x0008001E, + HV_X64_REGISTER_MSR_MTRR_PHYS_BASEF = 0x0008001F, + HV_X64_REGISTER_MSR_MTRR_PHYS_MASK0 = 0x00080040, + HV_X64_REGISTER_MSR_MTRR_PHYS_MASK1 = 0x00080041, + HV_X64_REGISTER_MSR_MTRR_PHYS_MASK2 = 0x00080042, + HV_X64_REGISTER_MSR_MTRR_PHYS_MASK3 = 0x00080043, + HV_X64_REGISTER_MSR_MTRR_PHYS_MASK4 = 0x00080044, + HV_X64_REGISTER_MSR_MTRR_PHYS_MASK5 = 0x00080045, + HV_X64_REGISTER_MSR_MTRR_PHYS_MASK6 = 0x00080046, + HV_X64_REGISTER_MSR_MTRR_PHYS_MASK7 = 0x00080047, + HV_X64_REGISTER_MSR_MTRR_PHYS_MASK8 = 0x00080048, + HV_X64_REGISTER_MSR_MTRR_PHYS_MASK9 = 0x00080049, + HV_X64_REGISTER_MSR_MTRR_PHYS_MASKA = 0x0008004A, + HV_X64_REGISTER_MSR_MTRR_PHYS_MASKB = 0x0008004B, + HV_X64_REGISTER_MSR_MTRR_PHYS_MASKC = 0x0008004C, + HV_X64_REGISTER_MSR_MTRR_PHYS_MASKD = 0x0008004D, + HV_X64_REGISTER_MSR_MTRR_PHYS_MASKE = 0x0008004E, + HV_X64_REGISTER_MSR_MTRR_PHYS_MASKF = 0x0008004F, + HV_X64_REGISTER_MSR_MTRR_FIX64K00000 = 0x00080070, + HV_X64_REGISTER_MSR_MTRR_FIX16K80000 = 0x00080071, + HV_X64_REGISTER_MSR_MTRR_FIX16KA0000 = 0x00080072, + HV_X64_REGISTER_MSR_MTRR_FIX4KC0000 = 0x00080073, + HV_X64_REGISTER_MSR_MTRR_FIX4KC8000 = 0x00080074, + HV_X64_REGISTER_MSR_MTRR_FIX4KD0000 = 0x00080075, + HV_X64_REGISTER_MSR_MTRR_FIX4KD8000 = 0x00080076, + HV_X64_REGISTER_MSR_MTRR_FIX4KE0000 = 0x00080077, + HV_X64_REGISTER_MSR_MTRR_FIX4KE8000 = 0x00080078, + HV_X64_REGISTER_MSR_MTRR_FIX4KF0000 = 0x00080079, + HV_X64_REGISTER_MSR_MTRR_FIX4KF8000 = 0x0008007A, + + HV_X64_REGISTER_REG_PAGE = 0x0009001C, +#endif }; /* diff --git a/include/uapi/linux/mshv.h b/include/uapi/linux/mshv.h index b645d17cc531..dee3ece28ce5 100644 --- a/include/uapi/linux/mshv.h +++ b/include/uapi/linux/mshv.h @@ -322,4 +322,84 @@ struct mshv_get_set_vp_state { * #define MSHV_ROOT_HVCALL _IOWR(MSHV_IOCTL, 0x07, struct mshv_root_hvcall) */ +/* Structure definitions, macros and IOCTLs for mshv_vtl */ + +#define MSHV_CAP_CORE_API_STABLE 0x0 +#define MSHV_CAP_REGISTER_PAGE 0x1 +#define MSHV_CAP_VTL_RETURN_ACTION 0x2 +#define MSHV_CAP_DR6_SHARED 0x3 +#define MSHV_MAX_RUN_MSG_SIZE 256 + +struct mshv_vp_registers { + __u32 count; /* supports only 1 register at a time */ + __u32 reserved; /* Reserved for alignment or future use */ + __u64 regs_ptr; /* pointer to struct hv_register_assoc */ +}; + +struct mshv_vtl_set_eventfd { + __s32 fd; + __u32 flag; +}; + +struct mshv_vtl_signal_event { + __u32 connection_id; + __u32 flag; +}; + +struct mshv_vtl_sint_post_msg { + __u64 message_type; + __u32 connection_id; + __u32 payload_size; /* Must not exceed HV_MESSAGE_PAYLOAD_BYTE_COUNT */ + __u64 payload_ptr; /* pointer to message payload (bytes) */ +}; + +struct mshv_vtl_ram_disposition { + __u64 start_pfn; + __u64 last_pfn; +}; + +struct mshv_vtl_set_poll_file { + __u32 cpu; + __u32 fd; +}; + +struct mshv_vtl_hvcall_setup { + __u64 bitmap_array_size; /* stores number of bytes */ + __u64 allow_bitmap_ptr; +}; + +struct mshv_vtl_hvcall { + __u64 control; /* Hypercall control code */ + __u64 input_size; /* Size of the input data */ + __u64 input_ptr; /* Pointer to the input struct */ + __u64 status; /* Status of the hypercall (output) */ + __u64 output_size; /* Size of the output data */ + __u64 output_ptr; /* Pointer to the output struct */ +}; + +struct mshv_sint_mask { + __u8 mask; + __u8 reserved[7]; +}; + +/* /dev/mshv device IOCTL */ +#define MSHV_CHECK_EXTENSION _IOW(MSHV_IOCTL, 0x00, __u32) + +/* vtl device */ +#define MSHV_CREATE_VTL _IOR(MSHV_IOCTL, 0x1D, char) +#define MSHV_ADD_VTL0_MEMORY _IOW(MSHV_IOCTL, 0x21, struct mshv_vtl_ram_disposition) +#define MSHV_SET_POLL_FILE _IOW(MSHV_IOCTL, 0x25, struct mshv_vtl_set_poll_file) +#define MSHV_RETURN_TO_LOWER_VTL _IO(MSHV_IOCTL, 0x27) +#define MSHV_GET_VP_REGISTERS _IOWR(MSHV_IOCTL, 0x05, struct mshv_vp_registers) +#define MSHV_SET_VP_REGISTERS _IOW(MSHV_IOCTL, 0x06, struct mshv_vp_registers) + +/* VMBus device IOCTLs */ +#define MSHV_SINT_SIGNAL_EVENT _IOW(MSHV_IOCTL, 0x22, struct mshv_vtl_signal_event) +#define MSHV_SINT_POST_MESSAGE _IOW(MSHV_IOCTL, 0x23, struct mshv_vtl_sint_post_msg) +#define MSHV_SINT_SET_EVENTFD _IOW(MSHV_IOCTL, 0x24, struct mshv_vtl_set_eventfd) +#define MSHV_SINT_PAUSE_MESSAGE_STREAM _IOW(MSHV_IOCTL, 0x25, struct mshv_sint_mask) + +/* hv_hvcall device */ +#define MSHV_HVCALL_SETUP _IOW(MSHV_IOCTL, 0x1E, struct mshv_vtl_hvcall_setup) +#define MSHV_HVCALL _IOWR(MSHV_IOCTL, 0x1F, struct mshv_vtl_hvcall) #endif -- cgit v1.2.3 From b5709f6d26d65f6bb9711f4b5f98469fd507cb5b Mon Sep 17 00:00:00 2001 From: Amery Hung Date: Wed, 3 Dec 2025 15:37:44 -0800 Subject: bpf: Support associating BPF program with struct_ops Add a new BPF command BPF_PROG_ASSOC_STRUCT_OPS to allow associating a BPF program with a struct_ops map. This command takes a file descriptor of a struct_ops map and a BPF program and set prog->aux->st_ops_assoc to the kdata of the struct_ops map. The command does not accept a struct_ops program nor a non-struct_ops map. Programs of a struct_ops map is automatically associated with the map during map update. If a program is shared between two struct_ops maps, prog->aux->st_ops_assoc will be poisoned to indicate that the associated struct_ops is ambiguous. The pointer, once poisoned, cannot be reset since we have lost track of associated struct_ops. For other program types, the associated struct_ops map, once set, cannot be changed later. This restriction may be lifted in the future if there is a use case. A kernel helper bpf_prog_get_assoc_struct_ops() can be used to retrieve the associated struct_ops pointer. The returned pointer, if not NULL, is guaranteed to be valid and point to a fully updated struct_ops struct. For struct_ops program reused in multiple struct_ops map, the return will be NULL. prog->aux->st_ops_assoc is protected by bumping the refcount for non-struct_ops programs and RCU for struct_ops programs. Since it would be inefficient to track programs associated with a struct_ops map, every non-struct_ops program will bump the refcount of the map to make sure st_ops_assoc stays valid. For a struct_ops program, it is protected by RCU as map_free will wait for an RCU grace period before disassociating the program with the map. The helper must be called in BPF program context or RCU read-side critical section. struct_ops implementers should note that the struct_ops returned may not be initialized nor attached yet. The struct_ops implementer will be responsible for tracking and checking the state of the associated struct_ops map if the use case expects an initialized or attached struct_ops. Signed-off-by: Amery Hung Signed-off-by: Andrii Nakryiko Acked-by: Andrii Nakryiko Acked-by: Martin KaFai Lau Link: https://lore.kernel.org/bpf/20251203233748.668365-3-ameryhung@gmail.com --- include/linux/bpf.h | 16 ++++++++ include/uapi/linux/bpf.h | 17 ++++++++ kernel/bpf/bpf_struct_ops.c | 88 ++++++++++++++++++++++++++++++++++++++++++ kernel/bpf/core.c | 3 ++ kernel/bpf/syscall.c | 46 ++++++++++++++++++++++ tools/include/uapi/linux/bpf.h | 17 ++++++++ 6 files changed, 187 insertions(+) (limited to 'include/uapi/linux') diff --git a/include/linux/bpf.h b/include/linux/bpf.h index 6498be4c44f8..28d8d6b7bb1e 100644 --- a/include/linux/bpf.h +++ b/include/linux/bpf.h @@ -1739,6 +1739,8 @@ struct bpf_prog_aux { struct rcu_head rcu; }; struct bpf_stream stream[2]; + struct mutex st_ops_assoc_mutex; + struct bpf_map __rcu *st_ops_assoc; }; struct bpf_prog { @@ -2041,6 +2043,9 @@ static inline void bpf_module_put(const void *data, struct module *owner) module_put(owner); } int bpf_struct_ops_link_create(union bpf_attr *attr); +int bpf_prog_assoc_struct_ops(struct bpf_prog *prog, struct bpf_map *map); +void bpf_prog_disassoc_struct_ops(struct bpf_prog *prog); +void *bpf_prog_get_assoc_struct_ops(const struct bpf_prog_aux *aux); u32 bpf_struct_ops_id(const void *kdata); #ifdef CONFIG_NET @@ -2088,6 +2093,17 @@ static inline int bpf_struct_ops_link_create(union bpf_attr *attr) { return -EOPNOTSUPP; } +static inline int bpf_prog_assoc_struct_ops(struct bpf_prog *prog, struct bpf_map *map) +{ + return -EOPNOTSUPP; +} +static inline void bpf_prog_disassoc_struct_ops(struct bpf_prog *prog) +{ +} +static inline void *bpf_prog_get_assoc_struct_ops(const struct bpf_prog_aux *aux) +{ + return NULL; +} static inline void bpf_map_struct_ops_info_fill(struct bpf_map_info *info, struct bpf_map *map) { } diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index f8d8513eda27..84ced3ed2d21 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -918,6 +918,16 @@ union bpf_iter_link_info { * Number of bytes read from the stream on success, or -1 if an * error occurred (in which case, *errno* is set appropriately). * + * BPF_PROG_ASSOC_STRUCT_OPS + * Description + * Associate a BPF program with a struct_ops map. The struct_ops + * map is identified by *map_fd* and the BPF program is + * identified by *prog_fd*. + * + * Return + * 0 on success or -1 if an error occurred (in which case, + * *errno* is set appropriately). + * * NOTES * eBPF objects (maps and programs) can be shared between processes. * @@ -974,6 +984,7 @@ enum bpf_cmd { BPF_PROG_BIND_MAP, BPF_TOKEN_CREATE, BPF_PROG_STREAM_READ_BY_FD, + BPF_PROG_ASSOC_STRUCT_OPS, __MAX_BPF_CMD, }; @@ -1894,6 +1905,12 @@ union bpf_attr { __u32 prog_fd; } prog_stream_read; + struct { + __u32 map_fd; + __u32 prog_fd; + __u32 flags; + } prog_assoc_struct_ops; + } __attribute__((aligned(8))); /* The description below is an attempt at providing documentation to eBPF diff --git a/kernel/bpf/bpf_struct_ops.c b/kernel/bpf/bpf_struct_ops.c index 278490683d28..c43346cb3d76 100644 --- a/kernel/bpf/bpf_struct_ops.c +++ b/kernel/bpf/bpf_struct_ops.c @@ -533,6 +533,17 @@ static void bpf_struct_ops_map_put_progs(struct bpf_struct_ops_map *st_map) } } +static void bpf_struct_ops_map_dissoc_progs(struct bpf_struct_ops_map *st_map) +{ + u32 i; + + for (i = 0; i < st_map->funcs_cnt; i++) { + if (!st_map->links[i]) + break; + bpf_prog_disassoc_struct_ops(st_map->links[i]->prog); + } +} + static void bpf_struct_ops_map_free_image(struct bpf_struct_ops_map *st_map) { int i; @@ -801,6 +812,9 @@ static long bpf_struct_ops_map_update_elem(struct bpf_map *map, void *key, goto reset_unlock; } + /* Poison pointer on error instead of return for backward compatibility */ + bpf_prog_assoc_struct_ops(prog, &st_map->map); + link = kzalloc(sizeof(*link), GFP_USER); if (!link) { bpf_prog_put(prog); @@ -980,6 +994,8 @@ static void bpf_struct_ops_map_free(struct bpf_map *map) if (btf_is_module(st_map->btf)) module_put(st_map->st_ops_desc->st_ops->owner); + bpf_struct_ops_map_dissoc_progs(st_map); + bpf_struct_ops_map_del_ksyms(st_map); /* The struct_ops's function may switch to another struct_ops. @@ -1396,6 +1412,78 @@ err_out: return err; } +int bpf_prog_assoc_struct_ops(struct bpf_prog *prog, struct bpf_map *map) +{ + struct bpf_map *st_ops_assoc; + + guard(mutex)(&prog->aux->st_ops_assoc_mutex); + + st_ops_assoc = rcu_dereference_protected(prog->aux->st_ops_assoc, + lockdep_is_held(&prog->aux->st_ops_assoc_mutex)); + if (st_ops_assoc && st_ops_assoc == map) + return 0; + + if (st_ops_assoc) { + if (prog->type != BPF_PROG_TYPE_STRUCT_OPS) + return -EBUSY; + + rcu_assign_pointer(prog->aux->st_ops_assoc, BPF_PTR_POISON); + } else { + /* + * struct_ops map does not track associated non-struct_ops programs. + * Bump the refcount to make sure st_ops_assoc is always valid. + */ + if (prog->type != BPF_PROG_TYPE_STRUCT_OPS) + bpf_map_inc(map); + + rcu_assign_pointer(prog->aux->st_ops_assoc, map); + } + + return 0; +} + +void bpf_prog_disassoc_struct_ops(struct bpf_prog *prog) +{ + struct bpf_map *st_ops_assoc; + + guard(mutex)(&prog->aux->st_ops_assoc_mutex); + + st_ops_assoc = rcu_dereference_protected(prog->aux->st_ops_assoc, + lockdep_is_held(&prog->aux->st_ops_assoc_mutex)); + if (!st_ops_assoc || st_ops_assoc == BPF_PTR_POISON) + return; + + if (prog->type != BPF_PROG_TYPE_STRUCT_OPS) + bpf_map_put(st_ops_assoc); + + RCU_INIT_POINTER(prog->aux->st_ops_assoc, NULL); +} + +/* + * Get a reference to the struct_ops struct (i.e., kdata) associated with a + * program. Should only be called in BPF program context (e.g., in a kfunc). + * + * If the returned pointer is not NULL, it must points to a valid struct_ops. + * The struct_ops map is not guaranteed to be initialized nor attached. + * Kernel struct_ops implementers are responsible for tracking and checking + * the state of the struct_ops if the use case requires an initialized or + * attached struct_ops. + */ +void *bpf_prog_get_assoc_struct_ops(const struct bpf_prog_aux *aux) +{ + struct bpf_struct_ops_map *st_map; + struct bpf_map *st_ops_assoc; + + st_ops_assoc = rcu_dereference_check(aux->st_ops_assoc, bpf_rcu_lock_held()); + if (!st_ops_assoc || st_ops_assoc == BPF_PTR_POISON) + return NULL; + + st_map = (struct bpf_struct_ops_map *)st_ops_assoc; + + return &st_map->kvalue.data; +} +EXPORT_SYMBOL_GPL(bpf_prog_get_assoc_struct_ops); + void bpf_map_struct_ops_info_fill(struct bpf_map_info *info, struct bpf_map *map) { struct bpf_struct_ops_map *st_map = (struct bpf_struct_ops_map *)map; diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c index c8ae6ab31651..67226145a4db 100644 --- a/kernel/bpf/core.c +++ b/kernel/bpf/core.c @@ -136,6 +136,7 @@ struct bpf_prog *bpf_prog_alloc_no_stats(unsigned int size, gfp_t gfp_extra_flag mutex_init(&fp->aux->used_maps_mutex); mutex_init(&fp->aux->ext_mutex); mutex_init(&fp->aux->dst_mutex); + mutex_init(&fp->aux->st_ops_assoc_mutex); #ifdef CONFIG_BPF_SYSCALL bpf_prog_stream_init(fp); @@ -286,6 +287,7 @@ void __bpf_prog_free(struct bpf_prog *fp) if (fp->aux) { mutex_destroy(&fp->aux->used_maps_mutex); mutex_destroy(&fp->aux->dst_mutex); + mutex_destroy(&fp->aux->st_ops_assoc_mutex); kfree(fp->aux->poke_tab); kfree(fp->aux); } @@ -2896,6 +2898,7 @@ static void bpf_prog_free_deferred(struct work_struct *work) #endif bpf_free_used_maps(aux); bpf_free_used_btfs(aux); + bpf_prog_disassoc_struct_ops(aux->prog); if (bpf_prog_is_dev_bound(aux)) bpf_prog_dev_bound_destroy(aux->prog); #ifdef CONFIG_PERF_EVENTS diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c index 6589acc89ef8..3080cc48bfc3 100644 --- a/kernel/bpf/syscall.c +++ b/kernel/bpf/syscall.c @@ -6122,6 +6122,49 @@ static int prog_stream_read(union bpf_attr *attr) return ret; } +#define BPF_PROG_ASSOC_STRUCT_OPS_LAST_FIELD prog_assoc_struct_ops.prog_fd + +static int prog_assoc_struct_ops(union bpf_attr *attr) +{ + struct bpf_prog *prog; + struct bpf_map *map; + int ret; + + if (CHECK_ATTR(BPF_PROG_ASSOC_STRUCT_OPS)) + return -EINVAL; + + if (attr->prog_assoc_struct_ops.flags) + return -EINVAL; + + prog = bpf_prog_get(attr->prog_assoc_struct_ops.prog_fd); + if (IS_ERR(prog)) + return PTR_ERR(prog); + + if (prog->type == BPF_PROG_TYPE_STRUCT_OPS) { + ret = -EINVAL; + goto put_prog; + } + + map = bpf_map_get(attr->prog_assoc_struct_ops.map_fd); + if (IS_ERR(map)) { + ret = PTR_ERR(map); + goto put_prog; + } + + if (map->map_type != BPF_MAP_TYPE_STRUCT_OPS) { + ret = -EINVAL; + goto put_map; + } + + ret = bpf_prog_assoc_struct_ops(prog, map); + +put_map: + bpf_map_put(map); +put_prog: + bpf_prog_put(prog); + return ret; +} + static int __sys_bpf(enum bpf_cmd cmd, bpfptr_t uattr, unsigned int size) { union bpf_attr attr; @@ -6261,6 +6304,9 @@ static int __sys_bpf(enum bpf_cmd cmd, bpfptr_t uattr, unsigned int size) case BPF_PROG_STREAM_READ_BY_FD: err = prog_stream_read(&attr); break; + case BPF_PROG_ASSOC_STRUCT_OPS: + err = prog_assoc_struct_ops(&attr); + break; default: err = -EINVAL; break; diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h index be7d8e060e10..6b92b0847ec2 100644 --- a/tools/include/uapi/linux/bpf.h +++ b/tools/include/uapi/linux/bpf.h @@ -918,6 +918,16 @@ union bpf_iter_link_info { * Number of bytes read from the stream on success, or -1 if an * error occurred (in which case, *errno* is set appropriately). * + * BPF_PROG_ASSOC_STRUCT_OPS + * Description + * Associate a BPF program with a struct_ops map. The struct_ops + * map is identified by *map_fd* and the BPF program is + * identified by *prog_fd*. + * + * Return + * 0 on success or -1 if an error occurred (in which case, + * *errno* is set appropriately). + * * NOTES * eBPF objects (maps and programs) can be shared between processes. * @@ -974,6 +984,7 @@ enum bpf_cmd { BPF_PROG_BIND_MAP, BPF_TOKEN_CREATE, BPF_PROG_STREAM_READ_BY_FD, + BPF_PROG_ASSOC_STRUCT_OPS, __MAX_BPF_CMD, }; @@ -1894,6 +1905,12 @@ union bpf_attr { __u32 prog_fd; } prog_stream_read; + struct { + __u32 map_fd; + __u32 prog_fd; + __u32 flags; + } prog_assoc_struct_ops; + } __attribute__((aligned(8))); /* The description below is an attempt at providing documentation to eBPF -- cgit v1.2.3 From cc6b66d661fda4fb94c0099dd92b83f8de5c1bf4 Mon Sep 17 00:00:00 2001 From: Zhu Lingshan Date: Thu, 21 Aug 2025 17:22:44 +0800 Subject: amdkfd: introduce new ioctl AMDKFD_IOC_CREATE_PROCESS This commit implemetns a new ioctl AMDKFD_IOC_CREATE_PROCESS that creates a new secondary kfd_progress on the FD. To keep backward compatibility, userspace programs need to invoke this ioctl explicitly on a FD to create a secondary kfd_process which replacing its primary kfd_process. This commit bumps ioctl minor version. Signed-off-by: Zhu Lingshan Reviewed-by: Felix Kuehling Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_chardev.c | 45 ++++++++++++++++++++++++++++++++ drivers/gpu/drm/amd/amdkfd/kfd_priv.h | 1 + drivers/gpu/drm/amd/amdkfd/kfd_process.c | 3 +-- include/uapi/linux/kfd_ioctl.h | 8 ++++-- 4 files changed, 53 insertions(+), 4 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c index 092a2b8aaea1..041237861107 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c @@ -3150,6 +3150,48 @@ out: return r; } +/* userspace programs need to invoke this ioctl explicitly on a FD to + * create a secondary kfd_process which replacing its primary kfd_process + */ +static int kfd_ioctl_create_process(struct file *filep, struct kfd_process *p, void *data) +{ + struct kfd_process *process; + int ret; + + /* Each FD owns only one kfd_process */ + if (p->context_id != KFD_CONTEXT_ID_PRIMARY) + return -EINVAL; + + if (!filep->private_data || !p) + return -EINVAL; + + mutex_lock(&kfd_processes_mutex); + if (p != filep->private_data) { + mutex_unlock(&kfd_processes_mutex); + return -EINVAL; + } + + process = create_process(current, false); + if (IS_ERR(process)) { + mutex_unlock(&kfd_processes_mutex); + return PTR_ERR(process); + } + + filep->private_data = process; + mutex_unlock(&kfd_processes_mutex); + + ret = kfd_create_process_sysfs(process); + if (ret) + pr_warn("Failed to create sysfs entry for the kfd_process"); + + /* Each open() increases kref of the primary kfd_process, + * so we need to reduce it here when we create a new secondary process replacing it + */ + kfd_unref_process(p); + + return 0; +} + #define AMDKFD_IOCTL_DEF(ioctl, _func, _flags) \ [_IOC_NR(ioctl)] = {.cmd = ioctl, .func = _func, .flags = _flags, \ .cmd_drv = 0, .name = #ioctl} @@ -3268,6 +3310,9 @@ static const struct amdkfd_ioctl_desc amdkfd_ioctls[] = { AMDKFD_IOCTL_DEF(AMDKFD_IOC_DBG_TRAP, kfd_ioctl_set_debug_trap, 0), + + AMDKFD_IOCTL_DEF(AMDKFD_IOC_CREATE_PROCESS, + kfd_ioctl_create_process, 0), }; #define AMDKFD_CORE_IOCTL_COUNT ARRAY_SIZE(amdkfd_ioctls) diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_priv.h b/drivers/gpu/drm/amd/amdkfd/kfd_priv.h index 399f32689678..12f640a9370a 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_priv.h +++ b/drivers/gpu/drm/amd/amdkfd/kfd_priv.h @@ -1053,6 +1053,7 @@ struct amdkfd_ioctl_desc { }; bool kfd_dev_is_large_bar(struct kfd_node *dev); +struct kfd_process *create_process(const struct task_struct *thread, bool primary); int kfd_process_create_wq(void); void kfd_process_destroy_wq(void); void kfd_cleanup_processes(void); diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_process.c b/drivers/gpu/drm/amd/amdkfd/kfd_process.c index c52e56aa9316..b4982da9234b 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_process.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_process.c @@ -68,7 +68,6 @@ static struct workqueue_struct *kfd_restore_wq; static struct kfd_process *find_process(const struct task_struct *thread, bool ref); static void kfd_process_ref_release(struct kref *ref); -static struct kfd_process *create_process(const struct task_struct *thread, bool primary); static void evict_process_worker(struct work_struct *work); static void restore_process_worker(struct work_struct *work); @@ -1582,7 +1581,7 @@ void kfd_process_set_trap_debug_flag(struct qcm_process_device *qpd, * On return the kfd_process is fully operational and will be freed when the * mm is released */ -static struct kfd_process *create_process(const struct task_struct *thread, bool primary) +struct kfd_process *create_process(const struct task_struct *thread, bool primary) { struct kfd_process *process; struct mmu_notifier *mn; diff --git a/include/uapi/linux/kfd_ioctl.h b/include/uapi/linux/kfd_ioctl.h index 5d1727a6d040..84aa24c02715 100644 --- a/include/uapi/linux/kfd_ioctl.h +++ b/include/uapi/linux/kfd_ioctl.h @@ -44,9 +44,10 @@ * - 1.16 - Add contiguous VRAM allocation flag * - 1.17 - Add SDMA queue creation with target SDMA engine ID * - 1.18 - Rename pad in set_memory_policy_args to misc_process_flag + * - 1.19 - Add a new ioctl to craete secondary kfd processes */ #define KFD_IOCTL_MAJOR_VERSION 1 -#define KFD_IOCTL_MINOR_VERSION 18 +#define KFD_IOCTL_MINOR_VERSION 19 struct kfd_ioctl_get_version_args { __u32 major_version; /* from KFD */ @@ -1671,7 +1672,10 @@ struct kfd_ioctl_dbg_trap_args { #define AMDKFD_IOC_DBG_TRAP \ AMDKFD_IOWR(0x26, struct kfd_ioctl_dbg_trap_args) +#define AMDKFD_IOC_CREATE_PROCESS \ + AMDKFD_IO(0x27) + #define AMDKFD_COMMAND_START 0x01 -#define AMDKFD_COMMAND_END 0x27 +#define AMDKFD_COMMAND_END 0x28 #endif -- cgit v1.2.3 From 7a5fb05b5b18e531989aa55b10dfa4be0633207e Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Fri, 5 Dec 2025 08:04:41 -0600 Subject: amdkfd: Bump ABI to indicate presence of Trap handler support for expert scheduling commit 0f0c8a6983db ("drm/amdkfd: Trap handler support for expert scheduling mode") introduced support for a trap handler when expert scheduling mode. However userspace needs to know whether or not a trap handler support is present. Bump the KFD IOCTL API so that userspace can key off this to decide. Suggested-by: Stella Laurenzo Fixes: 423888879412 ("drm/amdkfd: Trap handler support for expert scheduling mode") Reviewed-by: Kent Russell Signed-off-by: Mario Limonciello Signed-off-by: Alex Deucher --- include/uapi/linux/kfd_ioctl.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/kfd_ioctl.h b/include/uapi/linux/kfd_ioctl.h index 84aa24c02715..4d0c1a53f9d5 100644 --- a/include/uapi/linux/kfd_ioctl.h +++ b/include/uapi/linux/kfd_ioctl.h @@ -45,9 +45,10 @@ * - 1.17 - Add SDMA queue creation with target SDMA engine ID * - 1.18 - Rename pad in set_memory_policy_args to misc_process_flag * - 1.19 - Add a new ioctl to craete secondary kfd processes + * - 1.20 - Trap handler support for expert scheduling mode available */ #define KFD_IOCTL_MAJOR_VERSION 1 -#define KFD_IOCTL_MINOR_VERSION 19 +#define KFD_IOCTL_MINOR_VERSION 20 struct kfd_ioctl_get_version_args { __u32 major_version; /* from KFD */ -- cgit v1.2.3 From e56cadaa27fd156106c5583ed98976927c6febc9 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Sat, 6 Dec 2025 16:47:40 -0800 Subject: ynl: add regen hint to new headers Recent commit 68e83f347266 ("tools: ynl-gen: add regeneration comment") added a hint how to regenerate the code to the headers. Update the new headers from this release cycle to also include it. Reviewed-by: Simon Horman Link: https://patch.msgid.link/20251207004740.1657799-1-kuba@kernel.org Signed-off-by: Jakub Kicinski --- include/uapi/linux/energy_model.h | 1 + kernel/power/em_netlink_autogen.c | 1 + kernel/power/em_netlink_autogen.h | 1 + 3 files changed, 3 insertions(+) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/energy_model.h b/include/uapi/linux/energy_model.h index 4ec4c0eabbbb..0bcad967854f 100644 --- a/include/uapi/linux/energy_model.h +++ b/include/uapi/linux/energy_model.h @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/em.yaml */ /* YNL-GEN uapi header */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #ifndef _UAPI_LINUX_ENERGY_MODEL_H #define _UAPI_LINUX_ENERGY_MODEL_H diff --git a/kernel/power/em_netlink_autogen.c b/kernel/power/em_netlink_autogen.c index a7a09ab1d1c2..ceb3b2bb6ebe 100644 --- a/kernel/power/em_netlink_autogen.c +++ b/kernel/power/em_netlink_autogen.c @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/em.yaml */ /* YNL-GEN kernel source */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #include #include diff --git a/kernel/power/em_netlink_autogen.h b/kernel/power/em_netlink_autogen.h index 78ce609641f1..140ab548103c 100644 --- a/kernel/power/em_netlink_autogen.h +++ b/kernel/power/em_netlink_autogen.h @@ -2,6 +2,7 @@ /* Do not edit directly, auto-generated from: */ /* Documentation/netlink/specs/em.yaml */ /* YNL-GEN kernel header */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ #ifndef _LINUX_EM_GEN_H #define _LINUX_EM_GEN_H -- cgit v1.2.3 From 0ace3297a7301911e52d8195cb1006414897c859 Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Fri, 5 Dec 2025 19:55:14 +0100 Subject: mptcp: pm: ignore unknown endpoint flags Before this patch, the kernel was saving any flags set by the userspace, even unknown ones. This doesn't cause critical issues because the kernel is only looking at specific ones. But on the other hand, endpoints dumps could tell the userspace some recent flags seem to be supported on older kernel versions. Instead, ignore all unknown flags when parsing them. By doing that, the userspace can continue to set unsupported flags, but it has a way to verify what is supported by the kernel. Note that it sounds better to continue accepting unsupported flags not to change the behaviour, but also that eases things on the userspace side by adding "optional" endpoint types only supported by newer kernel versions without having to deal with the different kernel versions. A note for the backports: there will be conflicts in mptcp.h on older versions not having the mentioned flags, the new line should still be added last, and the '5' needs to be adapted to have the same value as the last entry. Fixes: 01cacb00b35c ("mptcp: add netlink-based PM") Cc: stable@vger.kernel.org Reviewed-by: Mat Martineau Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20251205-net-mptcp-misc-fixes-6-19-rc1-v1-1-9e4781a6c1b8@kernel.org Signed-off-by: Jakub Kicinski --- include/uapi/linux/mptcp.h | 1 + net/mptcp/pm_netlink.c | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/mptcp.h b/include/uapi/linux/mptcp.h index 04eea6d1d0a9..72a5d030154e 100644 --- a/include/uapi/linux/mptcp.h +++ b/include/uapi/linux/mptcp.h @@ -40,6 +40,7 @@ #define MPTCP_PM_ADDR_FLAG_FULLMESH _BITUL(3) #define MPTCP_PM_ADDR_FLAG_IMPLICIT _BITUL(4) #define MPTCP_PM_ADDR_FLAG_LAMINAR _BITUL(5) +#define MPTCP_PM_ADDR_FLAGS_MASK GENMASK(5, 0) struct mptcp_info { __u8 mptcpi_subflows; diff --git a/net/mptcp/pm_netlink.c b/net/mptcp/pm_netlink.c index d5b383870f79..7aa42de9c47b 100644 --- a/net/mptcp/pm_netlink.c +++ b/net/mptcp/pm_netlink.c @@ -119,7 +119,8 @@ int mptcp_pm_parse_entry(struct nlattr *attr, struct genl_info *info, } if (tb[MPTCP_PM_ADDR_ATTR_FLAGS]) - entry->flags = nla_get_u32(tb[MPTCP_PM_ADDR_ATTR_FLAGS]); + entry->flags = nla_get_u32(tb[MPTCP_PM_ADDR_ATTR_FLAGS]) & + MPTCP_PM_ADDR_FLAGS_MASK; if (tb[MPTCP_PM_ADDR_ATTR_PORT]) entry->addr.port = htons(nla_get_u16(tb[MPTCP_PM_ADDR_ATTR_PORT])); -- cgit v1.2.3 From 0e5032237ee5530147fbdf33134297e1490d5ec3 Mon Sep 17 00:00:00 2001 From: Bhavik Sachdev Date: Sat, 29 Nov 2025 14:41:21 +0530 Subject: statmount: accept fd as a parameter Extend `struct mnt_id_req` to take in a fd and introduce STATMOUNT_BY_FD flag. When a valid fd is provided and STATMOUNT_BY_FD is set, statmount will return mountinfo about the mount the fd is on. This even works for "unmounted" mounts (mounts that have been umounted using umount2(mnt, MNT_DETACH)), if you have access to a file descriptor on that mount. These "umounted" mounts will have no mountpoint and no valid mount namespace. Hence, we unset the STATMOUNT_MNT_POINT and STATMOUNT_MNT_NS_ID in statmount.mask for "unmounted" mounts. In case of STATMOUNT_BY_FD, given that we already have access to an fd on the mount, accessing mount information without a capability check seems fine because of the following reasons: - All fs related information is available via fstatfs() without any capability check. - Mount information is also available via /proc/pid/mountinfo (without any capability check). - Given that we have access to a fd on the mount which tells us that we had access to the mount at some point (or someone that had access gave us the fd). So, we should be able to access mount info. Co-developed-by: Pavel Tikhomirov Signed-off-by: Pavel Tikhomirov Signed-off-by: Bhavik Sachdev Link: https://patch.msgid.link/20251129091455.757724-3-b.sachdev1904@gmail.com Acked-by: Andrei Vagin Signed-off-by: Christian Brauner --- fs/namespace.c | 102 +++++++++++++++++++++++++++++---------------- include/uapi/linux/mount.h | 10 ++++- 2 files changed, 76 insertions(+), 36 deletions(-) (limited to 'include/uapi/linux') diff --git a/fs/namespace.c b/fs/namespace.c index f6879f282dae..ec3b16fedd9f 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -5547,31 +5547,49 @@ static int grab_requested_root(struct mnt_namespace *ns, struct path *root) /* locks: namespace_shared */ static int do_statmount(struct kstatmount *s, u64 mnt_id, u64 mnt_ns_id, - struct mnt_namespace *ns) + struct file *mnt_file, struct mnt_namespace *ns) { - struct mount *m; int err; - /* Has the namespace already been emptied? */ - if (mnt_ns_id && mnt_ns_empty(ns)) - return -ENOENT; + if (mnt_file) { + WARN_ON_ONCE(ns != NULL); - s->mnt = lookup_mnt_in_ns(mnt_id, ns); - if (!s->mnt) - return -ENOENT; + s->mnt = mnt_file->f_path.mnt; + ns = real_mount(s->mnt)->mnt_ns; + if (!ns) + /* + * We can't set mount point and mnt_ns_id since we don't have a + * ns for the mount. This can happen if the mount is unmounted + * with MNT_DETACH. + */ + s->mask &= ~(STATMOUNT_MNT_POINT | STATMOUNT_MNT_NS_ID); + } else { + /* Has the namespace already been emptied? */ + if (mnt_ns_id && mnt_ns_empty(ns)) + return -ENOENT; - err = grab_requested_root(ns, &s->root); - if (err) - return err; + s->mnt = lookup_mnt_in_ns(mnt_id, ns); + if (!s->mnt) + return -ENOENT; + } - /* - * Don't trigger audit denials. We just want to determine what - * mounts to show users. - */ - m = real_mount(s->mnt); - if (!is_path_reachable(m, m->mnt.mnt_root, &s->root) && - !ns_capable_noaudit(ns->user_ns, CAP_SYS_ADMIN)) - return -EPERM; + if (ns) { + err = grab_requested_root(ns, &s->root); + if (err) + return err; + + if (!mnt_file) { + struct mount *m; + /* + * Don't trigger audit denials. We just want to determine what + * mounts to show users. + */ + m = real_mount(s->mnt); + if (!is_path_reachable(m, m->mnt.mnt_root, &s->root) && + !ns_capable_noaudit(ns->user_ns, CAP_SYS_ADMIN)) + return -EPERM; + } + } err = security_sb_statfs(s->mnt->mnt_root); if (err) @@ -5693,7 +5711,7 @@ static int prepare_kstatmount(struct kstatmount *ks, struct mnt_id_req *kreq, } static int copy_mnt_id_req(const struct mnt_id_req __user *req, - struct mnt_id_req *kreq) + struct mnt_id_req *kreq, unsigned int flags) { int ret; size_t usize; @@ -5711,11 +5729,17 @@ static int copy_mnt_id_req(const struct mnt_id_req __user *req, ret = copy_struct_from_user(kreq, sizeof(*kreq), req, usize); if (ret) return ret; - if (kreq->mnt_ns_fd != 0 && kreq->mnt_ns_id) - return -EINVAL; - /* The first valid unique mount id is MNT_UNIQUE_ID_OFFSET + 1. */ - if (kreq->mnt_id <= MNT_UNIQUE_ID_OFFSET) - return -EINVAL; + + if (flags & STATMOUNT_BY_FD) { + if (kreq->mnt_id || kreq->mnt_ns_id) + return -EINVAL; + } else { + if (kreq->mnt_ns_fd != 0 && kreq->mnt_ns_id) + return -EINVAL; + /* The first valid unique mount id is MNT_UNIQUE_ID_OFFSET + 1. */ + if (kreq->mnt_id <= MNT_UNIQUE_ID_OFFSET) + return -EINVAL; + } return 0; } @@ -5762,25 +5786,33 @@ SYSCALL_DEFINE4(statmount, const struct mnt_id_req __user *, req, { struct mnt_namespace *ns __free(mnt_ns_release) = NULL; struct kstatmount *ks __free(kfree) = NULL; + struct file *mnt_file __free(fput) = NULL; struct mnt_id_req kreq; /* We currently support retrieval of 3 strings. */ size_t seq_size = 3 * PATH_MAX; int ret; - if (flags) + if (flags & ~STATMOUNT_BY_FD) return -EINVAL; - ret = copy_mnt_id_req(req, &kreq); + ret = copy_mnt_id_req(req, &kreq, flags); if (ret) return ret; - ns = grab_requested_mnt_ns(&kreq); - if (IS_ERR(ns)) - return PTR_ERR(ns); + if (flags & STATMOUNT_BY_FD) { + mnt_file = fget_raw(kreq.mnt_fd); + if (!mnt_file) + return -EBADF; + /* do_statmount sets ns in case of STATMOUNT_BY_FD */ + } else { + ns = grab_requested_mnt_ns(&kreq); + if (IS_ERR(ns)) + return PTR_ERR(ns); - if (kreq.mnt_ns_id && (ns != current->nsproxy->mnt_ns) && - !ns_capable_noaudit(ns->user_ns, CAP_SYS_ADMIN)) - return -EPERM; + if (kreq.mnt_ns_id && (ns != current->nsproxy->mnt_ns) && + !ns_capable_noaudit(ns->user_ns, CAP_SYS_ADMIN)) + return -EPERM; + } ks = kmalloc(sizeof(*ks), GFP_KERNEL_ACCOUNT); if (!ks) @@ -5792,7 +5824,7 @@ retry: return ret; scoped_guard(namespace_shared) - ret = do_statmount(ks, kreq.mnt_id, kreq.mnt_ns_id, ns); + ret = do_statmount(ks, kreq.mnt_id, kreq.mnt_ns_id, mnt_file, ns); if (!ret) ret = copy_statmount_to_user(ks); @@ -5932,7 +5964,7 @@ SYSCALL_DEFINE4(listmount, const struct mnt_id_req __user *, req, if (!access_ok(mnt_ids, nr_mnt_ids * sizeof(*mnt_ids))) return -EFAULT; - ret = copy_mnt_id_req(req, &kreq); + ret = copy_mnt_id_req(req, &kreq, 0); if (ret) return ret; diff --git a/include/uapi/linux/mount.h b/include/uapi/linux/mount.h index 5d3f8c9e3a62..18c624405268 100644 --- a/include/uapi/linux/mount.h +++ b/include/uapi/linux/mount.h @@ -197,7 +197,10 @@ struct statmount { */ struct mnt_id_req { __u32 size; - __u32 mnt_ns_fd; + union { + __u32 mnt_ns_fd; + __u32 mnt_fd; + }; __u64 mnt_id; __u64 param; __u64 mnt_ns_id; @@ -232,4 +235,9 @@ struct mnt_id_req { #define LSMT_ROOT 0xffffffffffffffff /* root mount */ #define LISTMOUNT_REVERSE (1 << 0) /* List later mounts first */ +/* + * @flag bits for statmount(2) + */ +#define STATMOUNT_BY_FD 0x00000001U /* want mountinfo for given fd */ + #endif /* _UAPI_LINUX_MOUNT_H */ -- cgit v1.2.3 From e83f63da2ac776fbc30861e4ce8b798df6ee8a7a Mon Sep 17 00:00:00 2001 From: Jonathan Kim Date: Mon, 23 Jun 2025 14:12:58 -0400 Subject: drm/amdkfd: allow debug subscription to lds violations on gfx 1250 GFX 1250 allows the debugger to subcribe to LDS out-of-range read/write memory violations. Bump IOCTL minor version and flag KFD capabilities for enablement hint. Signed-off-by: Jonathan Kim Reviewed-by: Felix Kuehling Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_debug.c | 7 +++++++ drivers/gpu/drm/amd/amdkfd/kfd_topology.c | 4 ++++ include/uapi/linux/kfd_ioctl.h | 4 +++- include/uapi/linux/kfd_sysfs.h | 3 ++- 4 files changed, 16 insertions(+), 2 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_debug.c b/drivers/gpu/drm/amd/amdkfd/kfd_debug.c index ba9a09b6589a..f83e1238c1b3 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_debug.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_debug.c @@ -519,6 +519,7 @@ int kfd_dbg_trap_set_flags(struct kfd_process *target, uint32_t *flags) struct kfd_topology_device *topo_dev = kfd_topology_device_by_id(target->pdds[i]->dev->id); uint32_t caps = topo_dev->node_props.capability; + uint32_t caps2 = topo_dev->node_props.capability2; if (!(caps & HSA_CAP_TRAP_DEBUG_PRECISE_MEMORY_OPERATIONS_SUPPORTED) && (*flags & KFD_DBG_TRAP_FLAG_SINGLE_MEM_OP)) { @@ -531,6 +532,12 @@ int kfd_dbg_trap_set_flags(struct kfd_process *target, uint32_t *flags) *flags = prev_flags; return -EACCES; } + + if (!(caps2 & HSA_CAP2_TRAP_DEBUG_LDS_OUT_OF_ADDR_RANGE_SUPPORTED) && + (*flags & KFD_DBG_TRAP_FLAG_LDS_OUT_OF_ADDR_RANGE)) { + *flags = prev_flags; + return -EACCES; + } } target->dbg_flags = *flags; diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_topology.c b/drivers/gpu/drm/amd/amdkfd/kfd_topology.c index a0990dd2378c..7a402c9c1b6e 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_topology.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_topology.c @@ -2028,6 +2028,10 @@ static void kfd_topology_set_capabilities(struct kfd_topology_device *dev) if (KFD_GC_VERSION(dev->gpu) >= IP_VERSION(12, 0, 0)) dev->node_props.capability |= HSA_CAP_TRAP_DEBUG_PRECISE_ALU_OPERATIONS_SUPPORTED; + + if (KFD_GC_VERSION(dev->gpu) >= IP_VERSION(12, 1, 0)) + dev->node_props.capability2 |= + HSA_CAP2_TRAP_DEBUG_LDS_OUT_OF_ADDR_RANGE_SUPPORTED; } kfd_topology_set_dbg_firmware_support(dev); diff --git a/include/uapi/linux/kfd_ioctl.h b/include/uapi/linux/kfd_ioctl.h index 4d0c1a53f9d5..6e91875c10ba 100644 --- a/include/uapi/linux/kfd_ioctl.h +++ b/include/uapi/linux/kfd_ioctl.h @@ -46,9 +46,10 @@ * - 1.18 - Rename pad in set_memory_policy_args to misc_process_flag * - 1.19 - Add a new ioctl to craete secondary kfd processes * - 1.20 - Trap handler support for expert scheduling mode available + * - 1.21 - Debugger support to subscribe to LDS out-of-address exceptions */ #define KFD_IOCTL_MAJOR_VERSION 1 -#define KFD_IOCTL_MINOR_VERSION 20 +#define KFD_IOCTL_MINOR_VERSION 21 struct kfd_ioctl_get_version_args { __u32 major_version; /* from KFD */ @@ -947,6 +948,7 @@ enum kfd_dbg_trap_address_watch_mode { enum kfd_dbg_trap_flags { KFD_DBG_TRAP_FLAG_SINGLE_MEM_OP = 1, KFD_DBG_TRAP_FLAG_SINGLE_ALU_OP = 2, + KFD_DBG_TRAP_FLAG_LDS_OUT_OF_ADDR_RANGE = 4 }; /* Trap exceptions */ diff --git a/include/uapi/linux/kfd_sysfs.h b/include/uapi/linux/kfd_sysfs.h index 1125fe47959f..0b6ce2f3c887 100644 --- a/include/uapi/linux/kfd_sysfs.h +++ b/include/uapi/linux/kfd_sysfs.h @@ -64,7 +64,8 @@ #define HSA_CAP_RESERVED 0x000f8000 #define HSA_CAP2_PER_SDMA_QUEUE_RESET_SUPPORTED 0x00000001 -#define HSA_CAP2_RESERVED 0xfffffffe +#define HSA_CAP2_TRAP_DEBUG_LDS_OUT_OF_ADDR_RANGE_SUPPORTED 0x00000002 +#define HSA_CAP2_RESERVED 0xfffffffc /* debug_prop bits in node properties */ #define HSA_DBG_WATCH_ADDR_MASK_LO_BIT_MASK 0x0000000f -- cgit v1.2.3 From a58383fa45c706bda3bf4a1955c3a0327dbec7e7 Mon Sep 17 00:00:00 2001 From: Deepanshu Kartikey Date: Wed, 17 Dec 2025 07:17:12 +0530 Subject: block: add allocation size check in blkdev_pr_read_keys() blkdev_pr_read_keys() takes num_keys from userspace and uses it to calculate the allocation size for keys_info via struct_size(). While there is a check for SIZE_MAX (integer overflow), there is no upper bound validation on the allocation size itself. A malicious or buggy userspace can pass a large num_keys value that doesn't trigger overflow but still results in an excessive allocation attempt, causing a warning in the page allocator when the order exceeds MAX_PAGE_ORDER. Fix this by introducing PR_KEYS_MAX to limit the number of keys to a sane value. This makes the SIZE_MAX check redundant, so remove it. Also switch to kvzalloc/kvfree to handle larger allocations gracefully. Fixes: 22a1ffea5f80 ("block: add IOC_PR_READ_KEYS ioctl") Tested-by: syzbot+660d079d90f8a1baf54d@syzkaller.appspotmail.com Reported-by: syzbot+660d079d90f8a1baf54d@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=660d079d90f8a1baf54d Link: https://lore.kernel.org/all/20251212013510.3576091-1-kartikey406@gmail.com/T/ [v1] Signed-off-by: Deepanshu Kartikey Reviewed-by: Martin K. Petersen Reviewed-by: Stefan Hajnoczi Signed-off-by: Jens Axboe --- block/ioctl.c | 9 +++++---- include/uapi/linux/pr.h | 2 ++ 2 files changed, 7 insertions(+), 4 deletions(-) (limited to 'include/uapi/linux') diff --git a/block/ioctl.c b/block/ioctl.c index 61feed686418..344478348a54 100644 --- a/block/ioctl.c +++ b/block/ioctl.c @@ -442,11 +442,12 @@ static int blkdev_pr_read_keys(struct block_device *bdev, blk_mode_t mode, if (copy_from_user(&read_keys, arg, sizeof(read_keys))) return -EFAULT; - keys_info_len = struct_size(keys_info, keys, read_keys.num_keys); - if (keys_info_len == SIZE_MAX) + if (read_keys.num_keys > PR_KEYS_MAX) return -EINVAL; - keys_info = kzalloc(keys_info_len, GFP_KERNEL); + keys_info_len = struct_size(keys_info, keys, read_keys.num_keys); + + keys_info = kvzalloc(keys_info_len, GFP_KERNEL); if (!keys_info) return -ENOMEM; @@ -473,7 +474,7 @@ static int blkdev_pr_read_keys(struct block_device *bdev, blk_mode_t mode, if (copy_to_user(arg, &read_keys, sizeof(read_keys))) ret = -EFAULT; out: - kfree(keys_info); + kvfree(keys_info); return ret; } diff --git a/include/uapi/linux/pr.h b/include/uapi/linux/pr.h index 847f3051057a..f0ecb1677317 100644 --- a/include/uapi/linux/pr.h +++ b/include/uapi/linux/pr.h @@ -79,4 +79,6 @@ struct pr_read_reservation { #define IOC_PR_READ_KEYS _IOWR('p', 206, struct pr_read_keys) #define IOC_PR_READ_RESERVATION _IOR('p', 207, struct pr_read_reservation) +#define PR_KEYS_MAX (1u << 16) + #endif /* _UAPI_PR_H */ -- cgit v1.2.3 From 733a8924229ff8c0385121a30fcd00bf70644743 Mon Sep 17 00:00:00 2001 From: Gergo Koteles Date: Thu, 13 Nov 2025 17:02:58 +0100 Subject: Input: add ABS_SND_PROFILE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ABS_SND_PROFILE used to describe the state of a multi-value sound profile switch. This will be used for the alert-slider on OnePlus phones or other phones. Profile values added as SND_PROFLE_(SILENT|VIBRATE|RING) identifiers to input-event-codes.h so they can be used from DTS. Signed-off-by: Gergo Koteles Reviewed-by: Bjorn Andersson Tested-by: Guido Günther # oneplus,fajita & oneplus,enchilada Reviewed-by: Guido Günther Signed-off-by: David Heidelberg Reviewed-by: Pavel Machek Link: https://patch.msgid.link/20251113-op6-tri-state-v8-1-54073f3874bc@ixit.cz Signed-off-by: Dmitry Torokhov --- Documentation/input/event-codes.rst | 6 ++++++ drivers/hid/hid-debug.c | 1 + include/uapi/linux/input-event-codes.h | 9 +++++++++ 3 files changed, 16 insertions(+) (limited to 'include/uapi/linux') diff --git a/Documentation/input/event-codes.rst b/Documentation/input/event-codes.rst index 4424cbff251f..77a6c9b3956d 100644 --- a/Documentation/input/event-codes.rst +++ b/Documentation/input/event-codes.rst @@ -241,6 +241,12 @@ A few EV_ABS codes have special meanings: emitted only when the selected profile changes, indicating the newly selected profile value. +* ABS_SND_PROFILE: + + - Used to describe the state of a multi-value sound profile switch. + An event is emitted only when the selected profile changes, + indicating the newly selected profile value. + * ABS_MT_: - Used to describe multitouch input events. Please see diff --git a/drivers/hid/hid-debug.c b/drivers/hid/hid-debug.c index 337d2dc81b4c..c5865b0d2aaa 100644 --- a/drivers/hid/hid-debug.c +++ b/drivers/hid/hid-debug.c @@ -3513,6 +3513,7 @@ static const char *absolutes[ABS_CNT] = { [ABS_DISTANCE] = "Distance", [ABS_TILT_X] = "XTilt", [ABS_TILT_Y] = "YTilt", [ABS_TOOL_WIDTH] = "ToolWidth", [ABS_VOLUME] = "Volume", [ABS_PROFILE] = "Profile", + [ABS_SND_PROFILE] = "SoundProfile", [ABS_MISC] = "Misc", [ABS_MT_SLOT] = "MTSlot", [ABS_MT_TOUCH_MAJOR] = "MTMajor", diff --git a/include/uapi/linux/input-event-codes.h b/include/uapi/linux/input-event-codes.h index 30f3c9eaafaa..4bdb6a165987 100644 --- a/include/uapi/linux/input-event-codes.h +++ b/include/uapi/linux/input-event-codes.h @@ -891,6 +891,7 @@ #define ABS_VOLUME 0x20 #define ABS_PROFILE 0x21 +#define ABS_SND_PROFILE 0x22 #define ABS_MISC 0x28 @@ -1000,4 +1001,12 @@ #define SND_MAX 0x07 #define SND_CNT (SND_MAX+1) +/* + * ABS_SND_PROFILE values + */ + +#define SND_PROFILE_SILENT 0x00 +#define SND_PROFILE_VIBRATE 0x01 +#define SND_PROFILE_RING 0x02 + #endif -- cgit v1.2.3 From cb8fe62f87ad21f4c174aec480694c9b4b8b01c4 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sat, 20 Dec 2025 03:04:26 +0900 Subject: nilfs2: convert nilfs_super_block to kernel-doc Eliminate 40+ kernel-doc warnings in nilfs2_ondisk.h by converting all of the struct member comments to kernel-doc comments. Fix one misnamed struct member in nilfs_direct_node. Object files before and after are the same size and content. Examples of warnings: Warning: include/uapi/linux/nilfs2_ondisk.h:202 struct member 's_rev_level' not described in 'nilfs_super_block' Warning: include/uapi/linux/nilfs2_ondisk.h:202 struct member 's_minor_rev_level' not described in 'nilfs_super_block' Warning: include/uapi/linux/nilfs2_ondisk.h:202 struct member 's_magic' not described in 'nilfs_super_block' Warning: include/uapi/linux/nilfs2_ondisk.h:202 struct member 's_bytes' not described in 'nilfs_super_block' Warning: include/uapi/linux/nilfs2_ondisk.h:202 struct member 's_flags' not described in 'nilfs_super_block' Signed-off-by: Randy Dunlap Signed-off-by: Ryusuke Konishi Signed-off-by: Viacheslav Dubeyko --- include/uapi/linux/nilfs2_ondisk.h | 163 ++++++++++++++++++++++--------------- 1 file changed, 97 insertions(+), 66 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/nilfs2_ondisk.h b/include/uapi/linux/nilfs2_ondisk.h index 3196cc44a002..b3442b16ff6a 100644 --- a/include/uapi/linux/nilfs2_ondisk.h +++ b/include/uapi/linux/nilfs2_ondisk.h @@ -133,73 +133,104 @@ struct nilfs_super_root { /** * struct nilfs_super_block - structure of super block on disk + * @s_rev_level: Revision level + * @s_minor_rev_level: minor revision level + * @s_magic: Magic signature + * @s_bytes: Bytes count of CRC calculation for + * this structure. s_reserved is excluded. + * @s_flags: flags + * @s_crc_seed: Seed value of CRC calculation + * @s_sum: Check sum of super block + * @s_log_block_size: Block size represented as follows: + * blocksize = 1 << (s_log_block_size + 10) + * @s_nsegments: Number of segments in filesystem + * @s_dev_size: block device size in bytes + * @s_first_data_block: 1st seg disk block number + * @s_blocks_per_segment: number of blocks per full segment + * @s_r_segments_percentage: Reserved segments percentage + * @s_last_cno: Last checkpoint number + * @s_last_pseg: disk block addr pseg written last + * @s_last_seq: seq. number of seg written last + * @s_free_blocks_count: Free blocks count + * @s_ctime: Creation time (execution time of newfs) + * @s_mtime: Mount time + * @s_wtime: Write time + * @s_mnt_count: Mount count + * @s_max_mnt_count: Maximal mount count + * @s_state: File system state + * @s_errors: Behaviour when detecting errors + * @s_lastcheck: time of last check + * @s_checkinterval: max. time between checks + * @s_creator_os: OS + * @s_def_resuid: Default uid for reserved blocks + * @s_def_resgid: Default gid for reserved blocks + * @s_first_ino: First non-reserved inode + * @s_inode_size: Size of an inode + * @s_dat_entry_size: Size of a dat entry + * @s_checkpoint_size: Size of a checkpoint + * @s_segment_usage_size: Size of a segment usage + * @s_uuid: 128-bit uuid for volume + * @s_volume_name: volume name + * @s_c_interval: Commit interval of segment + * @s_c_block_max: Threshold of data amount for the + * segment construction + * @s_feature_compat: Compatible feature set + * @s_feature_compat_ro: Read-only compatible feature set + * @s_feature_incompat: Incompatible feature set + * @s_reserved: padding to the end of the block */ struct nilfs_super_block { -/*00*/ __le32 s_rev_level; /* Revision level */ - __le16 s_minor_rev_level; /* minor revision level */ - __le16 s_magic; /* Magic signature */ - - __le16 s_bytes; /* - * Bytes count of CRC calculation - * for this structure. s_reserved - * is excluded. - */ - __le16 s_flags; /* flags */ - __le32 s_crc_seed; /* Seed value of CRC calculation */ -/*10*/ __le32 s_sum; /* Check sum of super block */ - - __le32 s_log_block_size; /* - * Block size represented as follows - * blocksize = - * 1 << (s_log_block_size + 10) - */ - __le64 s_nsegments; /* Number of segments in filesystem */ -/*20*/ __le64 s_dev_size; /* block device size in bytes */ - __le64 s_first_data_block; /* 1st seg disk block number */ -/*30*/ __le32 s_blocks_per_segment; /* number of blocks per full segment */ - __le32 s_r_segments_percentage; /* Reserved segments percentage */ - - __le64 s_last_cno; /* Last checkpoint number */ -/*40*/ __le64 s_last_pseg; /* disk block addr pseg written last */ - __le64 s_last_seq; /* seq. number of seg written last */ -/*50*/ __le64 s_free_blocks_count; /* Free blocks count */ - - __le64 s_ctime; /* - * Creation time (execution time of - * newfs) - */ -/*60*/ __le64 s_mtime; /* Mount time */ - __le64 s_wtime; /* Write time */ -/*70*/ __le16 s_mnt_count; /* Mount count */ - __le16 s_max_mnt_count; /* Maximal mount count */ - __le16 s_state; /* File system state */ - __le16 s_errors; /* Behaviour when detecting errors */ - __le64 s_lastcheck; /* time of last check */ - -/*80*/ __le32 s_checkinterval; /* max. time between checks */ - __le32 s_creator_os; /* OS */ - __le16 s_def_resuid; /* Default uid for reserved blocks */ - __le16 s_def_resgid; /* Default gid for reserved blocks */ - __le32 s_first_ino; /* First non-reserved inode */ - -/*90*/ __le16 s_inode_size; /* Size of an inode */ - __le16 s_dat_entry_size; /* Size of a dat entry */ - __le16 s_checkpoint_size; /* Size of a checkpoint */ - __le16 s_segment_usage_size; /* Size of a segment usage */ - -/*98*/ __u8 s_uuid[16]; /* 128-bit uuid for volume */ -/*A8*/ char s_volume_name[80] /* volume name */ - __kernel_nonstring; - -/*F8*/ __le32 s_c_interval; /* Commit interval of segment */ - __le32 s_c_block_max; /* - * Threshold of data amount for - * the segment construction - */ -/*100*/ __le64 s_feature_compat; /* Compatible feature set */ - __le64 s_feature_compat_ro; /* Read-only compatible feature set */ - __le64 s_feature_incompat; /* Incompatible feature set */ - __u32 s_reserved[186]; /* padding to the end of the block */ +/*00*/ __le32 s_rev_level; + __le16 s_minor_rev_level; + __le16 s_magic; + + __le16 s_bytes; + __le16 s_flags; + __le32 s_crc_seed; +/*10*/ __le32 s_sum; + + __le32 s_log_block_size; + __le64 s_nsegments; +/*20*/ __le64 s_dev_size; + __le64 s_first_data_block; +/*30*/ __le32 s_blocks_per_segment; + __le32 s_r_segments_percentage; + + __le64 s_last_cno; +/*40*/ __le64 s_last_pseg; + __le64 s_last_seq; +/*50*/ __le64 s_free_blocks_count; + + __le64 s_ctime; +/*60*/ __le64 s_mtime; + __le64 s_wtime; +/*70*/ __le16 s_mnt_count; + __le16 s_max_mnt_count; + __le16 s_state; + __le16 s_errors; + __le64 s_lastcheck; + +/*80*/ __le32 s_checkinterval; + __le32 s_creator_os; + __le16 s_def_resuid; + __le16 s_def_resgid; + __le32 s_first_ino; + +/*90*/ __le16 s_inode_size; + __le16 s_dat_entry_size; + __le16 s_checkpoint_size; + __le16 s_segment_usage_size; + +/*98*/ __u8 s_uuid[16]; +/*A8*/ char s_volume_name[80] __kernel_nonstring; + +/*F8*/ __le32 s_c_interval; + __le32 s_c_block_max; + +/*100*/ __le64 s_feature_compat; + __le64 s_feature_compat_ro; + __le64 s_feature_incompat; + __u32 s_reserved[186]; }; /* @@ -449,7 +480,7 @@ struct nilfs_btree_node { /** * struct nilfs_direct_node - header of built-in bmap array * @dn_flags: flags - * @dn_pad: padding + * @pad: padding */ struct nilfs_direct_node { __u8 dn_flags; -- cgit v1.2.3 From 6fd8a09f48d6fee184207f4e15e939898a3947f9 Mon Sep 17 00:00:00 2001 From: Ryusuke Konishi Date: Sat, 20 Dec 2025 03:04:27 +0900 Subject: nilfs2: fix missing struct keywords in nilfs2_api.h kernel-doc Eliminate the following kernel-doc warnings in nilfs2_api.h: Warning: include/uapi/linux/nilfs2_api.h:65 cannot understand function prototype: 'struct nilfs_suinfo' Warning: include/uapi/linux/nilfs2_api.h:101 cannot understand function prototype: 'struct nilfs_suinfo_update' This ensures that the documentation for nilfs_suinfo and nilfs_suinfo_update is correctly parsed and generated by adding the missing 'struct' keyword to their kernel-doc comments. Signed-off-by: Ryusuke Konishi Signed-off-by: Viacheslav Dubeyko --- include/uapi/linux/nilfs2_api.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/nilfs2_api.h b/include/uapi/linux/nilfs2_api.h index 8b9b89104f3d..d1b6fcde2fb8 100644 --- a/include/uapi/linux/nilfs2_api.h +++ b/include/uapi/linux/nilfs2_api.h @@ -58,7 +58,7 @@ NILFS_CPINFO_FNS(INVALID, invalid) NILFS_CPINFO_FNS(MINOR, minor) /** - * nilfs_suinfo - segment usage information + * struct nilfs_suinfo - segment usage information * @sui_lastmod: timestamp of last modification * @sui_nblocks: number of written blocks in segment * @sui_flags: segment usage flags @@ -93,7 +93,7 @@ static inline int nilfs_suinfo_clean(const struct nilfs_suinfo *si) } /** - * nilfs_suinfo_update - segment usage information update + * struct nilfs_suinfo_update - segment usage information update * @sup_segnum: segment number * @sup_flags: flags for which fields are active in sup_sui * @sup_reserved: reserved necessary for alignment -- cgit v1.2.3 From 98b9f207afa53aff2edb0e52910c4348b456b37d Mon Sep 17 00:00:00 2001 From: Thomas Weißschuh Date: Mon, 22 Dec 2025 09:04:13 +0100 Subject: dmaengine: idxd: uapi: use UAPI types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Using libc types and headers from the UAPI headers is problematic as it introduces a dependency on a full C toolchain. Use the fixed-width integer types provided by the UAPI headers instead. Signed-off-by: Thomas Weißschuh Acked-by: Arnd Bergmann Link: https://patch.msgid.link/20251222-uapi-idxd-v1-1-baa183adb20d@linutronix.de Signed-off-by: Vinod Koul --- include/uapi/linux/idxd.h | 270 +++++++++++++++++++++++----------------------- 1 file changed, 133 insertions(+), 137 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/idxd.h b/include/uapi/linux/idxd.h index 3d1987e1bb2d..fdcc8eefb925 100644 --- a/include/uapi/linux/idxd.h +++ b/include/uapi/linux/idxd.h @@ -3,11 +3,7 @@ #ifndef _USR_IDXD_H_ #define _USR_IDXD_H_ -#ifdef __KERNEL__ #include -#else -#include -#endif /* Driver command error status */ enum idxd_scmd_stat { @@ -176,132 +172,132 @@ enum iax_completion_status { #define DSA_COMP_STATUS(status) ((status) & DSA_COMP_STATUS_MASK) struct dsa_hw_desc { - uint32_t pasid:20; - uint32_t rsvd:11; - uint32_t priv:1; - uint32_t flags:24; - uint32_t opcode:8; - uint64_t completion_addr; + __u32 pasid:20; + __u32 rsvd:11; + __u32 priv:1; + __u32 flags:24; + __u32 opcode:8; + __u64 completion_addr; union { - uint64_t src_addr; - uint64_t rdback_addr; - uint64_t pattern; - uint64_t desc_list_addr; - uint64_t pattern_lower; - uint64_t transl_fetch_addr; + __u64 src_addr; + __u64 rdback_addr; + __u64 pattern; + __u64 desc_list_addr; + __u64 pattern_lower; + __u64 transl_fetch_addr; }; union { - uint64_t dst_addr; - uint64_t rdback_addr2; - uint64_t src2_addr; - uint64_t comp_pattern; + __u64 dst_addr; + __u64 rdback_addr2; + __u64 src2_addr; + __u64 comp_pattern; }; union { - uint32_t xfer_size; - uint32_t desc_count; - uint32_t region_size; + __u32 xfer_size; + __u32 desc_count; + __u32 region_size; }; - uint16_t int_handle; - uint16_t rsvd1; + __u16 int_handle; + __u16 rsvd1; union { - uint8_t expected_res; + __u8 expected_res; /* create delta record */ struct { - uint64_t delta_addr; - uint32_t max_delta_size; - uint32_t delt_rsvd; - uint8_t expected_res_mask; + __u64 delta_addr; + __u32 max_delta_size; + __u32 delt_rsvd; + __u8 expected_res_mask; }; - uint32_t delta_rec_size; - uint64_t dest2; + __u32 delta_rec_size; + __u64 dest2; /* CRC */ struct { - uint32_t crc_seed; - uint32_t crc_rsvd; - uint64_t seed_addr; + __u32 crc_seed; + __u32 crc_rsvd; + __u64 seed_addr; }; /* DIF check or strip */ struct { - uint8_t src_dif_flags; - uint8_t dif_chk_res; - uint8_t dif_chk_flags; - uint8_t dif_chk_res2[5]; - uint32_t chk_ref_tag_seed; - uint16_t chk_app_tag_mask; - uint16_t chk_app_tag_seed; + __u8 src_dif_flags; + __u8 dif_chk_res; + __u8 dif_chk_flags; + __u8 dif_chk_res2[5]; + __u32 chk_ref_tag_seed; + __u16 chk_app_tag_mask; + __u16 chk_app_tag_seed; }; /* DIF insert */ struct { - uint8_t dif_ins_res; - uint8_t dest_dif_flag; - uint8_t dif_ins_flags; - uint8_t dif_ins_res2[13]; - uint32_t ins_ref_tag_seed; - uint16_t ins_app_tag_mask; - uint16_t ins_app_tag_seed; + __u8 dif_ins_res; + __u8 dest_dif_flag; + __u8 dif_ins_flags; + __u8 dif_ins_res2[13]; + __u32 ins_ref_tag_seed; + __u16 ins_app_tag_mask; + __u16 ins_app_tag_seed; }; /* DIF update */ struct { - uint8_t src_upd_flags; - uint8_t upd_dest_flags; - uint8_t dif_upd_flags; - uint8_t dif_upd_res[5]; - uint32_t src_ref_tag_seed; - uint16_t src_app_tag_mask; - uint16_t src_app_tag_seed; - uint32_t dest_ref_tag_seed; - uint16_t dest_app_tag_mask; - uint16_t dest_app_tag_seed; + __u8 src_upd_flags; + __u8 upd_dest_flags; + __u8 dif_upd_flags; + __u8 dif_upd_res[5]; + __u32 src_ref_tag_seed; + __u16 src_app_tag_mask; + __u16 src_app_tag_seed; + __u32 dest_ref_tag_seed; + __u16 dest_app_tag_mask; + __u16 dest_app_tag_seed; }; /* Fill */ - uint64_t pattern_upper; + __u64 pattern_upper; /* Translation fetch */ struct { - uint64_t transl_fetch_res; - uint32_t region_stride; + __u64 transl_fetch_res; + __u32 region_stride; }; /* DIX generate */ struct { - uint8_t dix_gen_res; - uint8_t dest_dif_flags; - uint8_t dif_flags; - uint8_t dix_gen_res2[13]; - uint32_t ref_tag_seed; - uint16_t app_tag_mask; - uint16_t app_tag_seed; + __u8 dix_gen_res; + __u8 dest_dif_flags; + __u8 dif_flags; + __u8 dix_gen_res2[13]; + __u32 ref_tag_seed; + __u16 app_tag_mask; + __u16 app_tag_seed; }; - uint8_t op_specific[24]; + __u8 op_specific[24]; }; } __attribute__((packed)); struct iax_hw_desc { - uint32_t pasid:20; - uint32_t rsvd:11; - uint32_t priv:1; - uint32_t flags:24; - uint32_t opcode:8; - uint64_t completion_addr; - uint64_t src1_addr; - uint64_t dst_addr; - uint32_t src1_size; - uint16_t int_handle; + __u32 pasid:20; + __u32 rsvd:11; + __u32 priv:1; + __u32 flags:24; + __u32 opcode:8; + __u64 completion_addr; + __u64 src1_addr; + __u64 dst_addr; + __u32 src1_size; + __u16 int_handle; union { - uint16_t compr_flags; - uint16_t decompr_flags; + __u16 compr_flags; + __u16 decompr_flags; }; - uint64_t src2_addr; - uint32_t max_dst_size; - uint32_t src2_size; - uint32_t filter_flags; - uint32_t num_inputs; + __u64 src2_addr; + __u32 max_dst_size; + __u32 src2_size; + __u32 filter_flags; + __u32 num_inputs; } __attribute__((packed)); struct dsa_raw_desc { - uint64_t field[8]; + __u64 field[8]; } __attribute__((packed)); /* @@ -309,91 +305,91 @@ struct dsa_raw_desc { * volatile and prevent the compiler from optimize the read. */ struct dsa_completion_record { - volatile uint8_t status; + volatile __u8 status; union { - uint8_t result; - uint8_t dif_status; + __u8 result; + __u8 dif_status; }; - uint8_t fault_info; - uint8_t rsvd; + __u8 fault_info; + __u8 rsvd; union { - uint32_t bytes_completed; - uint32_t descs_completed; + __u32 bytes_completed; + __u32 descs_completed; }; - uint64_t fault_addr; + __u64 fault_addr; union { /* common record */ struct { - uint32_t invalid_flags:24; - uint32_t rsvd2:8; + __u32 invalid_flags:24; + __u32 rsvd2:8; }; - uint32_t delta_rec_size; - uint64_t crc_val; + __u32 delta_rec_size; + __u64 crc_val; /* DIF check & strip */ struct { - uint32_t dif_chk_ref_tag; - uint16_t dif_chk_app_tag_mask; - uint16_t dif_chk_app_tag; + __u32 dif_chk_ref_tag; + __u16 dif_chk_app_tag_mask; + __u16 dif_chk_app_tag; }; /* DIF insert */ struct { - uint64_t dif_ins_res; - uint32_t dif_ins_ref_tag; - uint16_t dif_ins_app_tag_mask; - uint16_t dif_ins_app_tag; + __u64 dif_ins_res; + __u32 dif_ins_ref_tag; + __u16 dif_ins_app_tag_mask; + __u16 dif_ins_app_tag; }; /* DIF update */ struct { - uint32_t dif_upd_src_ref_tag; - uint16_t dif_upd_src_app_tag_mask; - uint16_t dif_upd_src_app_tag; - uint32_t dif_upd_dest_ref_tag; - uint16_t dif_upd_dest_app_tag_mask; - uint16_t dif_upd_dest_app_tag; + __u32 dif_upd_src_ref_tag; + __u16 dif_upd_src_app_tag_mask; + __u16 dif_upd_src_app_tag; + __u32 dif_upd_dest_ref_tag; + __u16 dif_upd_dest_app_tag_mask; + __u16 dif_upd_dest_app_tag; }; /* DIX generate */ struct { - uint64_t dix_gen_res; - uint32_t dix_ref_tag; - uint16_t dix_app_tag_mask; - uint16_t dix_app_tag; + __u64 dix_gen_res; + __u32 dix_ref_tag; + __u16 dix_app_tag_mask; + __u16 dix_app_tag; }; - uint8_t op_specific[16]; + __u8 op_specific[16]; }; } __attribute__((packed)); struct dsa_raw_completion_record { - uint64_t field[4]; + __u64 field[4]; } __attribute__((packed)); struct iax_completion_record { - volatile uint8_t status; - uint8_t error_code; - uint8_t fault_info; - uint8_t rsvd; - uint32_t bytes_completed; - uint64_t fault_addr; - uint32_t invalid_flags; - uint32_t rsvd2; - uint32_t output_size; - uint8_t output_bits; - uint8_t rsvd3; - uint16_t xor_csum; - uint32_t crc; - uint32_t min; - uint32_t max; - uint32_t sum; - uint64_t rsvd4[2]; + volatile __u8 status; + __u8 error_code; + __u8 fault_info; + __u8 rsvd; + __u32 bytes_completed; + __u64 fault_addr; + __u32 invalid_flags; + __u32 rsvd2; + __u32 output_size; + __u8 output_bits; + __u8 rsvd3; + __u16 xor_csum; + __u32 crc; + __u32 min; + __u32 max; + __u32 sum; + __u64 rsvd4[2]; } __attribute__((packed)); struct iax_raw_completion_record { - uint64_t field[8]; + __u64 field[8]; } __attribute__((packed)); #endif -- cgit v1.2.3 From 9e541b3cee70a3bbe86b176c903c23b29fe033cd Mon Sep 17 00:00:00 2001 From: Shuai Xue Date: Wed, 10 Dec 2025 21:29:05 +0800 Subject: PCI: trace: Add generic RAS tracepoint for hotplug event MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hotplug events are critical indicators for analyzing hardware health, and surprise link downs can significantly impact system performance and reliability. Define a new TRACING_SYSTEM named "pci", add a generic RAS tracepoint for hotplug event to help health checks. Add enum pci_hotplug_event in include/uapi/linux/pci.h so applications like rasdaemon can register tracepoint event handlers for it. The following output is generated when a device is hotplugged: $ echo 1 > /sys/kernel/debug/tracing/events/pci/pci_hp_event/enable $ cat /sys/kernel/debug/tracing/trace_pipe irq/51-pciehp-88 [001] ..... 1311.177459: pci_hp_event: 0000:00:02.0 slot:10, event:CARD_PRESENT irq/51-pciehp-88 [001] ..... 1311.177566: pci_hp_event: 0000:00:02.0 slot:10, event:LINK_UP Suggested-by: Lukas Wunner Signed-off-by: Shuai Xue Signed-off-by: Bjorn Helgaas Reviewed-by: Lukas Wunner Reviewed-by: Jonathan Cameron Reviewed-by: Steven Rostedt (Google) # for trace event Reviewed-by: Ilpo Järvinen Link: https://patch.msgid.link/20251210132907.58799-2-xueshuai@linux.alibaba.com --- drivers/pci/Makefile | 3 ++ drivers/pci/hotplug/pciehp_ctrl.c | 31 +++++++++++++---- drivers/pci/trace.c | 11 ++++++ include/trace/events/pci.h | 72 +++++++++++++++++++++++++++++++++++++++ include/uapi/linux/pci.h | 7 ++++ 5 files changed, 118 insertions(+), 6 deletions(-) create mode 100644 drivers/pci/trace.c create mode 100644 include/trace/events/pci.h (limited to 'include/uapi/linux') diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile index e10cfe5a280b..8c259a9a8796 100644 --- a/drivers/pci/Makefile +++ b/drivers/pci/Makefile @@ -47,3 +47,6 @@ obj-y += controller/ obj-y += switch/ subdir-ccflags-$(CONFIG_PCI_DEBUG) := -DDEBUG + +CFLAGS_trace.o := -I$(src) +obj-$(CONFIG_TRACING) += trace.o diff --git a/drivers/pci/hotplug/pciehp_ctrl.c b/drivers/pci/hotplug/pciehp_ctrl.c index bcc938d4420f..7805f697a02c 100644 --- a/drivers/pci/hotplug/pciehp_ctrl.c +++ b/drivers/pci/hotplug/pciehp_ctrl.c @@ -19,6 +19,7 @@ #include #include #include +#include #include "../pci.h" #include "pciehp.h" @@ -244,12 +245,20 @@ void pciehp_handle_presence_or_link_change(struct controller *ctrl, u32 events) case ON_STATE: ctrl->state = POWEROFF_STATE; mutex_unlock(&ctrl->state_lock); - if (events & PCI_EXP_SLTSTA_DLLSC) + if (events & PCI_EXP_SLTSTA_DLLSC) { ctrl_info(ctrl, "Slot(%s): Link Down\n", slot_name(ctrl)); - if (events & PCI_EXP_SLTSTA_PDC) + trace_pci_hp_event(pci_name(ctrl->pcie->port), + slot_name(ctrl), + PCI_HOTPLUG_LINK_DOWN); + } + if (events & PCI_EXP_SLTSTA_PDC) { ctrl_info(ctrl, "Slot(%s): Card not present\n", slot_name(ctrl)); + trace_pci_hp_event(pci_name(ctrl->pcie->port), + slot_name(ctrl), + PCI_HOTPLUG_CARD_NOT_PRESENT); + } pciehp_disable_slot(ctrl, SURPRISE_REMOVAL); break; default: @@ -269,6 +278,9 @@ void pciehp_handle_presence_or_link_change(struct controller *ctrl, u32 events) INDICATOR_NOOP); ctrl_info(ctrl, "Slot(%s): Card not present\n", slot_name(ctrl)); + trace_pci_hp_event(pci_name(ctrl->pcie->port), + slot_name(ctrl), + PCI_HOTPLUG_CARD_NOT_PRESENT); } mutex_unlock(&ctrl->state_lock); return; @@ -281,12 +293,19 @@ void pciehp_handle_presence_or_link_change(struct controller *ctrl, u32 events) case OFF_STATE: ctrl->state = POWERON_STATE; mutex_unlock(&ctrl->state_lock); - if (present) + if (present) { ctrl_info(ctrl, "Slot(%s): Card present\n", slot_name(ctrl)); - if (link_active) - ctrl_info(ctrl, "Slot(%s): Link Up\n", - slot_name(ctrl)); + trace_pci_hp_event(pci_name(ctrl->pcie->port), + slot_name(ctrl), + PCI_HOTPLUG_CARD_PRESENT); + } + if (link_active) { + ctrl_info(ctrl, "Slot(%s): Link Up\n", slot_name(ctrl)); + trace_pci_hp_event(pci_name(ctrl->pcie->port), + slot_name(ctrl), + PCI_HOTPLUG_LINK_UP); + } ctrl->request_result = pciehp_enable_slot(ctrl); break; default: diff --git a/drivers/pci/trace.c b/drivers/pci/trace.c new file mode 100644 index 000000000000..cf11abca8602 --- /dev/null +++ b/drivers/pci/trace.c @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Tracepoints for PCI system + * + * Copyright (C) 2025 Alibaba Corporation + */ + +#include + +#define CREATE_TRACE_POINTS +#include diff --git a/include/trace/events/pci.h b/include/trace/events/pci.h new file mode 100644 index 000000000000..39e512a167ee --- /dev/null +++ b/include/trace/events/pci.h @@ -0,0 +1,72 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#undef TRACE_SYSTEM +#define TRACE_SYSTEM pci + +#if !defined(_TRACE_HW_EVENT_PCI_H) || defined(TRACE_HEADER_MULTI_READ) +#define _TRACE_HW_EVENT_PCI_H + +#include + +#define PCI_HOTPLUG_EVENT \ + EM(PCI_HOTPLUG_LINK_UP, "LINK_UP") \ + EM(PCI_HOTPLUG_LINK_DOWN, "LINK_DOWN") \ + EM(PCI_HOTPLUG_CARD_PRESENT, "CARD_PRESENT") \ + EMe(PCI_HOTPLUG_CARD_NOT_PRESENT, "CARD_NOT_PRESENT") + +/* Enums require being exported to userspace, for user tool parsing */ +#undef EM +#undef EMe +#define EM(a, b) TRACE_DEFINE_ENUM(a); +#define EMe(a, b) TRACE_DEFINE_ENUM(a); + +PCI_HOTPLUG_EVENT + +/* + * Now redefine the EM() and EMe() macros to map the enums to the strings + * that will be printed in the output. + */ +#undef EM +#undef EMe +#define EM(a, b) {a, b}, +#define EMe(a, b) {a, b} + +/* + * Note: For generic PCI hotplug events, we pass already-resolved strings + * (port_name, slot) instead of driver-specific structures like 'struct + * controller'. This is because different PCI hotplug drivers (pciehp, cpqphp, + * ibmphp, shpchp) define their own versions of 'struct controller' with + * different fields and helper functions. Using driver-specific structures would + * make the tracepoint interface non-generic and cause compatibility issues + * across different drivers. + */ +TRACE_EVENT(pci_hp_event, + + TP_PROTO(const char *port_name, + const char *slot, + const int event), + + TP_ARGS(port_name, slot, event), + + TP_STRUCT__entry( + __string( port_name, port_name ) + __string( slot, slot ) + __field( int, event ) + ), + + TP_fast_assign( + __assign_str(port_name); + __assign_str(slot); + __entry->event = event; + ), + + TP_printk("%s slot:%s, event:%s\n", + __get_str(port_name), + __get_str(slot), + __print_symbolic(__entry->event, PCI_HOTPLUG_EVENT) + ) +); + +#endif /* _TRACE_HW_EVENT_PCI_H */ + +/* This part must be outside protection */ +#include diff --git a/include/uapi/linux/pci.h b/include/uapi/linux/pci.h index a769eefc5139..4f150028965d 100644 --- a/include/uapi/linux/pci.h +++ b/include/uapi/linux/pci.h @@ -39,4 +39,11 @@ #define PCIIOC_MMAP_IS_MEM (PCIIOC_BASE | 0x02) /* Set mmap state to MEM space. */ #define PCIIOC_WRITE_COMBINE (PCIIOC_BASE | 0x03) /* Enable/disable write-combining. */ +enum pci_hotplug_event { + PCI_HOTPLUG_LINK_UP, + PCI_HOTPLUG_LINK_DOWN, + PCI_HOTPLUG_CARD_PRESENT, + PCI_HOTPLUG_CARD_NOT_PRESENT, +}; + #endif /* _UAPILINUX_PCI_H */ -- cgit v1.2.3 From 3dd57ddec9e3a98387196a3f53b8c036977d8c0f Mon Sep 17 00:00:00 2001 From: Al Viro Date: Tue, 16 Dec 2025 08:19:39 +0000 Subject: get rid of bogus __user in struct xattr_args::value The first member of struct xattr_args is declared as __aligned_u64 __user value; which makes no sense whatsoever; __user is a qualifier and what that declaration says is "all struct xattr_args instances have .value _stored_ in user address space, no matter where the rest of the structure happens to be". Something like "int __user *p" stands for "value of p is a pointer to an instance of int that happens to live in user address space"; it says nothing about location of p itself, just as const char *p declares a pointer to unmodifiable char rather than an unmodifiable pointer to char. With xattr_args the intent clearly had been "the 64bit value represents a _pointer_ to object in user address space", but __user has nothing to do with that. All it gets us is a couple of bogus warnings in fs/xattr.c where (userland) instance of xattr_args is copied to local variable of that type (in kernel address space), followed by access to its members. Since we've told sparse that args.value must somehow be located in userland memory, we get warned that looking at that 64bit unsigned integer (in a variable already on kernel stack) is not allowed. Note that sparse has no way to express "this integer shall never be cast into a pointer to be dereferenced directly" and I don't see any way to assign a sane semantics to that. In any case, __user is not it. Signed-off-by: Al Viro Link: https://patch.msgid.link/20251216081939.GQ1712166@ZenIV Signed-off-by: Christian Brauner --- include/uapi/linux/xattr.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/xattr.h b/include/uapi/linux/xattr.h index c7c85bb504ba..2e5aef48fa7e 100644 --- a/include/uapi/linux/xattr.h +++ b/include/uapi/linux/xattr.h @@ -23,7 +23,7 @@ #define XATTR_REPLACE 0x2 /* set value, fail if attr does not exist */ struct xattr_args { - __aligned_u64 __user value; + __aligned_u64 value; __u32 size; __u32 flags; }; -- cgit v1.2.3 From 3c4629b68dbe18e454cce4b864c530268cffbeed Mon Sep 17 00:00:00 2001 From: Thomas Weißschuh Date: Mon, 22 Dec 2025 09:00:33 +0100 Subject: virtio: uapi: avoid usage of libc types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Using libc types and headers from the UAPI headers is problematic as it introduces a dependency on a full C toolchain. On Linux 'unsigned long' works as a replacement for 'uintptr_t' and does not depend on libc. Signed-off-by: Thomas Weißschuh Acked-by: Arnd Bergmann Signed-off-by: Michael S. Tsirkin Message-Id: <20251222-uapi-virtio-v1-1-29390f87bcad@linutronix.de> --- include/uapi/linux/virtio_ring.h | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/virtio_ring.h b/include/uapi/linux/virtio_ring.h index f8c20d3de8da..3c478582a3c2 100644 --- a/include/uapi/linux/virtio_ring.h +++ b/include/uapi/linux/virtio_ring.h @@ -31,9 +31,6 @@ * SUCH DAMAGE. * * Copyright Rusty Russell IBM Corporation 2007. */ -#ifndef __KERNEL__ -#include -#endif #include #include @@ -202,7 +199,7 @@ static inline void vring_init(struct vring *vr, unsigned int num, void *p, vr->num = num; vr->desc = p; vr->avail = (struct vring_avail *)((char *)p + num * sizeof(struct vring_desc)); - vr->used = (void *)(((uintptr_t)&vr->avail->ring[num] + sizeof(__virtio16) + vr->used = (void *)(((unsigned long)&vr->avail->ring[num] + sizeof(__virtio16) + align-1) & ~(align - 1)); } -- cgit v1.2.3 From 40fc797ba18328e57ed1cb213b4b5e48f86f4c7c Mon Sep 17 00:00:00 2001 From: Carlos Llamas Date: Mon, 15 Dec 2025 18:17:09 +0000 Subject: binder: fix trivial typo in uapi header As reported by codespell: include/uapi/linux/android/binder.h:281: interupted ==> interrupted Signed-off-by: Carlos Llamas Reviewed-by: Alice Ryhl Link: https://patch.msgid.link/20251215181724.3811977-1-cmllamas@google.com Signed-off-by: Greg Kroah-Hartman --- include/uapi/linux/android/binder.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/android/binder.h b/include/uapi/linux/android/binder.h index 03ee4c7010d7..701cad36de43 100644 --- a/include/uapi/linux/android/binder.h +++ b/include/uapi/linux/android/binder.h @@ -278,7 +278,7 @@ enum { * NOTE: Two special error codes you should check for when calling * in to the driver are: * - * EINTR -- The operation has been interupted. This should be + * EINTR -- The operation has been interrupted. This should be * handled by retrying the ioctl() until a different error code * is returned. * -- cgit v1.2.3 From c6c209ceb87f64a6ceebe61761951dcbbf4a0baa Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 9 Dec 2025 19:28:49 -0500 Subject: NFSD: Remove NFSERR_EAGAIN I haven't found an NFSERR_EAGAIN in RFCs 1094, 1813, 7530, or 8881. None of these RFCs have an NFS status code that match the numeric value "11". Based on the meaning of the EAGAIN errno, I presume the use of this status in NFSD means NFS4ERR_DELAY. So replace the one usage of nfserr_eagain, and remove it from NFSD's NFS status conversion tables. As far as I can tell, NFSERR_EAGAIN has existed since the pre-git era, but was not actually used by any code until commit f4e44b393389 ("NFSD: delay unmount source's export after inter-server copy completed."), at which time it become possible for NFSD to return a status code of 11 (which is not valid NFS protocol). Fixes: f4e44b393389 ("NFSD: delay unmount source's export after inter-server copy completed.") Cc: stable@vger.kernel.org Reviewed-by: NeilBrown Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/nfs_common/common.c | 1 - fs/nfsd/nfs4proc.c | 2 +- fs/nfsd/nfsd.h | 1 - include/trace/misc/nfs.h | 2 -- include/uapi/linux/nfs.h | 1 - 5 files changed, 1 insertion(+), 6 deletions(-) (limited to 'include/uapi/linux') diff --git a/fs/nfs_common/common.c b/fs/nfs_common/common.c index af09aed09fd2..0778743ae2c2 100644 --- a/fs/nfs_common/common.c +++ b/fs/nfs_common/common.c @@ -17,7 +17,6 @@ static const struct { { NFSERR_NOENT, -ENOENT }, { NFSERR_IO, -EIO }, { NFSERR_NXIO, -ENXIO }, -/* { NFSERR_EAGAIN, -EAGAIN }, */ { NFSERR_ACCES, -EACCES }, { NFSERR_EXIST, -EEXIST }, { NFSERR_XDEV, -EXDEV }, diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c index 7f7e6bb23a90..42a6b914c0fe 100644 --- a/fs/nfsd/nfs4proc.c +++ b/fs/nfsd/nfs4proc.c @@ -1506,7 +1506,7 @@ try_again: (schedule_timeout(20*HZ) == 0)) { finish_wait(&nn->nfsd_ssc_waitq, &wait); kfree(work); - return nfserr_eagain; + return nfserr_jukebox; } finish_wait(&nn->nfsd_ssc_waitq, &wait); goto try_again; diff --git a/fs/nfsd/nfsd.h b/fs/nfsd/nfsd.h index 50be785f1d2c..b0283213a8f5 100644 --- a/fs/nfsd/nfsd.h +++ b/fs/nfsd/nfsd.h @@ -233,7 +233,6 @@ void nfsd_lockd_shutdown(void); #define nfserr_noent cpu_to_be32(NFSERR_NOENT) #define nfserr_io cpu_to_be32(NFSERR_IO) #define nfserr_nxio cpu_to_be32(NFSERR_NXIO) -#define nfserr_eagain cpu_to_be32(NFSERR_EAGAIN) #define nfserr_acces cpu_to_be32(NFSERR_ACCES) #define nfserr_exist cpu_to_be32(NFSERR_EXIST) #define nfserr_xdev cpu_to_be32(NFSERR_XDEV) diff --git a/include/trace/misc/nfs.h b/include/trace/misc/nfs.h index c82233e950ac..a394b4d38e18 100644 --- a/include/trace/misc/nfs.h +++ b/include/trace/misc/nfs.h @@ -16,7 +16,6 @@ TRACE_DEFINE_ENUM(NFSERR_PERM); TRACE_DEFINE_ENUM(NFSERR_NOENT); TRACE_DEFINE_ENUM(NFSERR_IO); TRACE_DEFINE_ENUM(NFSERR_NXIO); -TRACE_DEFINE_ENUM(NFSERR_EAGAIN); TRACE_DEFINE_ENUM(NFSERR_ACCES); TRACE_DEFINE_ENUM(NFSERR_EXIST); TRACE_DEFINE_ENUM(NFSERR_XDEV); @@ -52,7 +51,6 @@ TRACE_DEFINE_ENUM(NFSERR_JUKEBOX); { NFSERR_NXIO, "NXIO" }, \ { ECHILD, "CHILD" }, \ { ETIMEDOUT, "TIMEDOUT" }, \ - { NFSERR_EAGAIN, "AGAIN" }, \ { NFSERR_ACCES, "ACCES" }, \ { NFSERR_EXIST, "EXIST" }, \ { NFSERR_XDEV, "XDEV" }, \ diff --git a/include/uapi/linux/nfs.h b/include/uapi/linux/nfs.h index f356f2ba3814..71c7196d3281 100644 --- a/include/uapi/linux/nfs.h +++ b/include/uapi/linux/nfs.h @@ -49,7 +49,6 @@ NFSERR_NOENT = 2, /* v2 v3 v4 */ NFSERR_IO = 5, /* v2 v3 v4 */ NFSERR_NXIO = 6, /* v2 v3 v4 */ - NFSERR_EAGAIN = 11, /* v2 v3 */ NFSERR_ACCES = 13, /* v2 v3 v4 */ NFSERR_EXIST = 17, /* v2 v3 v4 */ NFSERR_XDEV = 18, /* v3 v4 */ -- cgit v1.2.3 From c51bb53d5c68041dd02f66d9b638cda33647623e Mon Sep 17 00:00:00 2001 From: David Yat Sin Date: Tue, 18 Mar 2025 19:49:55 +0000 Subject: drm/amdkfd: Add metadata ring buffer for compute Add support for separate ring-buffer for metadata packets when using compute queues. Userspace application allocate the metadata ring-buffer and the queue ring-buffer with a single allocation. The metadata ring-buffer starts after the queue ring-buffer. Signed-off-by: David Yat Sin Reviewed-by: Philip Yang Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_chardev.c | 8 ++++++++ drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c | 21 +++++++++++++++++++++ drivers/gpu/drm/amd/amdkfd/kfd_priv.h | 3 ++- drivers/gpu/drm/amd/amdkfd/kfd_queue.c | 7 +++++-- include/uapi/linux/kfd_ioctl.h | 5 +++-- 5 files changed, 39 insertions(+), 5 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c index 041237861107..88621cb7d409 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_chardev.c @@ -221,6 +221,11 @@ static int set_queue_properties_from_user(struct queue_properties *q_properties, pr_debug("Size lower. clamped to KFD_MIN_QUEUE_RING_SIZE"); } + if ((args->metadata_ring_size != 0) && !is_power_of_2(args->metadata_ring_size)) { + pr_err("Metadata ring size must be a power of 2 or 0\n"); + return -EINVAL; + } + if (!access_ok((const void __user *) args->read_pointer_address, sizeof(uint32_t))) { pr_err("Can't access read pointer\n"); @@ -255,6 +260,9 @@ static int set_queue_properties_from_user(struct queue_properties *q_properties, q_properties->priority = args->queue_priority; q_properties->queue_address = args->ring_base_address; q_properties->queue_size = args->ring_size; + if (args->queue_type == KFD_IOC_QUEUE_TYPE_COMPUTE_AQL) + q_properties->metadata_queue_size = args->metadata_ring_size; + q_properties->read_ptr = (void __user *)args->read_pointer_address; q_properties->write_ptr = (void __user *)args->write_pointer_address; q_properties->eop_ring_buffer_address = args->eop_buffer_address; diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c index f1c2c9e8cf6b..a06b4e89af8a 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_mqd_manager_v12_1.c @@ -266,6 +266,27 @@ static void update_mqd(struct mqd_manager *mm, void *mqd, m->cp_hqd_pq_base_lo = lower_32_bits((uint64_t)q->queue_address >> 8); m->cp_hqd_pq_base_hi = upper_32_bits((uint64_t)q->queue_address >> 8); + if (q->metadata_queue_size) { + /* On GC 12.1 is 64 DWs which is 4 times size of AQL packet */ + if (q->metadata_queue_size == q->queue_size * 4) { + /* + * User application allocates main queue ring and metadata queue ring + * with a single allocation. metadata queue ring starts after main + * queue ring. + */ + m->cp_hqd_kd_base = + lower_32_bits((q->queue_address + q->queue_size) >> 8); + m->cp_hqd_kd_base_hi = + upper_32_bits((q->queue_address + q->queue_size) >> 8); + + m->cp_hqd_kd_cntl |= CP_HQD_KD_CNTL__KD_FETCHER_ENABLE_MASK; + /* KD_SIZE = 2 for metadata packet = 64 DWs */ + m->cp_hqd_kd_cntl |= 2 << CP_HQD_KD_CNTL__KD_SIZE__SHIFT; + } else { + pr_warn("Invalid metadata ring size, metadata queue will be ignored\n"); + } + } + m->cp_hqd_pq_rptr_report_addr_lo = lower_32_bits((uint64_t)q->read_ptr); m->cp_hqd_pq_rptr_report_addr_hi = upper_32_bits((uint64_t)q->read_ptr); m->cp_hqd_pq_wptr_poll_addr_lo = lower_32_bits((uint64_t)q->write_ptr); diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_priv.h b/drivers/gpu/drm/amd/amdkfd/kfd_priv.h index ebc637c38c04..d798baa7e52e 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_priv.h +++ b/drivers/gpu/drm/amd/amdkfd/kfd_priv.h @@ -506,7 +506,8 @@ struct queue_properties { enum kfd_queue_format format; unsigned int queue_id; uint64_t queue_address; - uint64_t queue_size; + uint64_t queue_size; + uint64_t metadata_queue_size; uint32_t priority; uint32_t queue_percent; void __user *read_ptr; diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_queue.c b/drivers/gpu/drm/amd/amdkfd/kfd_queue.c index 56c97189e7f1..1b465fdb2c64 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_queue.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_queue.c @@ -247,9 +247,12 @@ int kfd_queue_acquire_buffers(struct kfd_process_device *pdd, struct queue_prope properties->format == KFD_QUEUE_FORMAT_AQL && topo_dev->node_props.gfx_target_version >= 70000 && topo_dev->node_props.gfx_target_version < 90000) - expected_queue_size = properties->queue_size / 2; + /* metadata_queue_size not supported on GFX7/GFX8 */ + expected_queue_size = + properties->queue_size / 2; else - expected_queue_size = properties->queue_size; + expected_queue_size = + properties->queue_size + properties->metadata_queue_size; vm = drm_priv_to_vm(pdd->drm_priv); err = amdgpu_bo_reserve(vm->root.bo, false); diff --git a/include/uapi/linux/kfd_ioctl.h b/include/uapi/linux/kfd_ioctl.h index 6e91875c10ba..047bcb1cc078 100644 --- a/include/uapi/linux/kfd_ioctl.h +++ b/include/uapi/linux/kfd_ioctl.h @@ -47,9 +47,10 @@ * - 1.19 - Add a new ioctl to craete secondary kfd processes * - 1.20 - Trap handler support for expert scheduling mode available * - 1.21 - Debugger support to subscribe to LDS out-of-address exceptions + * - 1.22 - Add queue creation with metadata ring base address */ #define KFD_IOCTL_MAJOR_VERSION 1 -#define KFD_IOCTL_MINOR_VERSION 21 +#define KFD_IOCTL_MINOR_VERSION 22 struct kfd_ioctl_get_version_args { __u32 major_version; /* from KFD */ @@ -87,7 +88,7 @@ struct kfd_ioctl_create_queue_args { __u32 ctx_save_restore_size; /* to KFD */ __u32 ctl_stack_size; /* to KFD */ __u32 sdma_engine_id; /* to KFD */ - __u32 pad; + __u32 metadata_ring_size; /* to KFD */ }; struct kfd_ioctl_destroy_queue_args { -- cgit v1.2.3 From 22cd0db47f4f65ebe8afc8c34ab120c47c73da2a Mon Sep 17 00:00:00 2001 From: Jacopo Mondi Date: Mon, 15 Dec 2025 13:08:12 +0100 Subject: media: uapi: mali-c55-config: Remove version identifier The Mali C55 driver uses the v4l2-isp framework, which defines its own versioning number which does not need to be defined again in each platform-specific header. Remove the definition of mali_c55_param_buffer_version enumeration from the Mali C55 uAPI header. Signed-off-by: Jacopo Mondi Reviewed-by: Daniel Scally Signed-off-by: Hans Verkuil --- include/uapi/linux/media/arm/mali-c55-config.h | 9 --------- 1 file changed, 9 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/media/arm/mali-c55-config.h b/include/uapi/linux/media/arm/mali-c55-config.h index 109082c5694f..3d335f950eeb 100644 --- a/include/uapi/linux/media/arm/mali-c55-config.h +++ b/include/uapi/linux/media/arm/mali-c55-config.h @@ -194,15 +194,6 @@ struct mali_c55_stats_buffer { __u32 reserved3[15]; } __attribute__((packed)); -/** - * enum mali_c55_param_buffer_version - Mali-C55 parameters block versioning - * - * @MALI_C55_PARAM_BUFFER_V1: First version of Mali-C55 parameters block - */ -enum mali_c55_param_buffer_version { - MALI_C55_PARAM_BUFFER_V1, -}; - /** * enum mali_c55_param_block_type - Enumeration of Mali-C55 parameter blocks * -- cgit v1.2.3 From 2b421662c7887a0649fe409155a1f101562d0fa9 Mon Sep 17 00:00:00 2001 From: Leon Hwang Date: Wed, 7 Jan 2026 10:20:16 +0800 Subject: bpf: Introduce BPF_F_CPU and BPF_F_ALL_CPUS flags Introduce BPF_F_CPU and BPF_F_ALL_CPUS flags and check them for following APIs: * 'map_lookup_elem()' * 'map_update_elem()' * 'generic_map_lookup_batch()' * 'generic_map_update_batch()' And, get the correct value size for these APIs. Acked-by: Andrii Nakryiko Signed-off-by: Leon Hwang Link: https://lore.kernel.org/r/20260107022022.12843-2-leon.hwang@linux.dev Signed-off-by: Alexei Starovoitov --- include/linux/bpf.h | 23 ++++++++++++++++++++++- include/uapi/linux/bpf.h | 2 ++ kernel/bpf/syscall.c | 31 +++++++++++++++++-------------- tools/include/uapi/linux/bpf.h | 2 ++ 4 files changed, 43 insertions(+), 15 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/linux/bpf.h b/include/linux/bpf.h index a63e47d2109c..108bab1bda9d 100644 --- a/include/linux/bpf.h +++ b/include/linux/bpf.h @@ -3915,14 +3915,35 @@ bpf_prog_update_insn_ptrs(struct bpf_prog *prog, u32 *offsets, void *image) } #endif +static inline bool bpf_map_supports_cpu_flags(enum bpf_map_type map_type) +{ + return false; +} + static inline int bpf_map_check_op_flags(struct bpf_map *map, u64 flags, u64 allowed_flags) { - if (flags & ~allowed_flags) + u32 cpu; + + if ((u32)flags & ~allowed_flags) return -EINVAL; if ((flags & BPF_F_LOCK) && !btf_record_has_field(map->record, BPF_SPIN_LOCK)) return -EINVAL; + if (!(flags & BPF_F_CPU) && flags >> 32) + return -EINVAL; + + if (flags & (BPF_F_CPU | BPF_F_ALL_CPUS)) { + if (!bpf_map_supports_cpu_flags(map->map_type)) + return -EINVAL; + if ((flags & BPF_F_CPU) && (flags & BPF_F_ALL_CPUS)) + return -EINVAL; + + cpu = flags >> 32; + if ((flags & BPF_F_CPU) && cpu >= num_possible_cpus()) + return -ERANGE; + } + return 0; } diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index 84ced3ed2d21..2a2ade4be60f 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -1384,6 +1384,8 @@ enum { BPF_NOEXIST = 1, /* create new element if it didn't exist */ BPF_EXIST = 2, /* update existing element */ BPF_F_LOCK = 4, /* spin_lock-ed map_lookup/map_update */ + BPF_F_CPU = 8, /* cpu flag for percpu maps, upper 32-bit of flags is a cpu number */ + BPF_F_ALL_CPUS = 16, /* update value across all CPUs for percpu maps */ }; /* flags for BPF_MAP_CREATE command */ diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c index 6dd2ad2f9e81..e8cfe9d67e64 100644 --- a/kernel/bpf/syscall.c +++ b/kernel/bpf/syscall.c @@ -133,12 +133,14 @@ bool bpf_map_write_active(const struct bpf_map *map) return atomic64_read(&map->writecnt) != 0; } -static u32 bpf_map_value_size(const struct bpf_map *map) -{ - if (map->map_type == BPF_MAP_TYPE_PERCPU_HASH || - map->map_type == BPF_MAP_TYPE_LRU_PERCPU_HASH || - map->map_type == BPF_MAP_TYPE_PERCPU_ARRAY || - map->map_type == BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE) +static u32 bpf_map_value_size(const struct bpf_map *map, u64 flags) +{ + if (flags & (BPF_F_CPU | BPF_F_ALL_CPUS)) + return map->value_size; + else if (map->map_type == BPF_MAP_TYPE_PERCPU_HASH || + map->map_type == BPF_MAP_TYPE_LRU_PERCPU_HASH || + map->map_type == BPF_MAP_TYPE_PERCPU_ARRAY || + map->map_type == BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE) return round_up(map->value_size, 8) * num_possible_cpus(); else if (IS_FD_MAP(map)) return sizeof(u32); @@ -1729,7 +1731,7 @@ static int map_lookup_elem(union bpf_attr *attr) if (!(map_get_sys_perms(map, f) & FMODE_CAN_READ)) return -EPERM; - err = bpf_map_check_op_flags(map, attr->flags, BPF_F_LOCK); + err = bpf_map_check_op_flags(map, attr->flags, BPF_F_LOCK | BPF_F_CPU); if (err) return err; @@ -1737,7 +1739,7 @@ static int map_lookup_elem(union bpf_attr *attr) if (IS_ERR(key)) return PTR_ERR(key); - value_size = bpf_map_value_size(map); + value_size = bpf_map_value_size(map, attr->flags); err = -ENOMEM; value = kvmalloc(value_size, GFP_USER | __GFP_NOWARN); @@ -1804,7 +1806,7 @@ static int map_update_elem(union bpf_attr *attr, bpfptr_t uattr) goto err_put; } - value_size = bpf_map_value_size(map); + value_size = bpf_map_value_size(map, attr->flags); value = kvmemdup_bpfptr(uvalue, value_size); if (IS_ERR(value)) { err = PTR_ERR(value); @@ -2000,11 +2002,12 @@ int generic_map_update_batch(struct bpf_map *map, struct file *map_file, void *key, *value; int err = 0; - err = bpf_map_check_op_flags(map, attr->batch.elem_flags, BPF_F_LOCK); + err = bpf_map_check_op_flags(map, attr->batch.elem_flags, + BPF_F_LOCK | BPF_F_CPU | BPF_F_ALL_CPUS); if (err) return err; - value_size = bpf_map_value_size(map); + value_size = bpf_map_value_size(map, attr->batch.elem_flags); max_count = attr->batch.count; if (!max_count) @@ -2059,11 +2062,11 @@ int generic_map_lookup_batch(struct bpf_map *map, u32 value_size, cp, max_count; int err; - err = bpf_map_check_op_flags(map, attr->batch.elem_flags, BPF_F_LOCK); + err = bpf_map_check_op_flags(map, attr->batch.elem_flags, BPF_F_LOCK | BPF_F_CPU); if (err) return err; - value_size = bpf_map_value_size(map); + value_size = bpf_map_value_size(map, attr->batch.elem_flags); max_count = attr->batch.count; if (!max_count) @@ -2185,7 +2188,7 @@ static int map_lookup_and_delete_elem(union bpf_attr *attr) goto err_put; } - value_size = bpf_map_value_size(map); + value_size = bpf_map_value_size(map, 0); err = -ENOMEM; value = kvmalloc(value_size, GFP_USER | __GFP_NOWARN); diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h index 6b92b0847ec2..b816bc53d2e1 100644 --- a/tools/include/uapi/linux/bpf.h +++ b/tools/include/uapi/linux/bpf.h @@ -1384,6 +1384,8 @@ enum { BPF_NOEXIST = 1, /* create new element if it didn't exist */ BPF_EXIST = 2, /* update existing element */ BPF_F_LOCK = 4, /* spin_lock-ed map_lookup/map_update */ + BPF_F_CPU = 8, /* cpu flag for percpu maps, upper 32-bit of flags is a cpu number */ + BPF_F_ALL_CPUS = 16, /* update value across all CPUs for percpu maps */ }; /* flags for BPF_MAP_CREATE command */ -- cgit v1.2.3 From caa07a815d6ee32586beb66f67e7e3c103a02efd Mon Sep 17 00:00:00 2001 From: Changwoo Min Date: Thu, 8 Jan 2026 14:32:10 +0900 Subject: PM: EM: Rename em.yaml to dev-energymodel.yaml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The EM YNL specification used many acronyms, including ‘em’, ‘pd’, ‘ps’, etc. While the acronyms are short and convenient, they could be confusing. So, let’s spell them out to be more specific. The following changes were made in the spec. Note that the protocol name cannot exceed GENL_NAMSIZ (16). em -> dev-energymodel pds -> perf-domains pd -> perf-domain pd-id -> perf-domain-id pd-table -> perf-table ps -> perf-state get-pds -> get-perf-domains get-pd-table -> get-perf-table pd-created -> perf-domain-created pd-updated -> perf-domain-updated pd-deleted -> perf-domain-deleted In addition. doc strings were added to the spec. based on the comments in energy_model.h. Two flag attributes (perf-state-flags and perf-domain-flags) were added for easily interpreting the bit flags. Finally, the autogenerated files and em_netlink.c were updated accordingly to reflect the name changes. Suggested-by: Donald Hunter Reviewed-by: Lukasz Luba Reviewed-by: Donald Hunter Signed-off-by: Changwoo Min Link: https://patch.msgid.link/20260108053212.642478-3-changwoo@igalia.com Signed-off-by: Rafael J. Wysocki --- Documentation/netlink/specs/dev-energymodel.yaml | 175 +++++++++++++++++++++++ Documentation/netlink/specs/em.yaml | 116 --------------- MAINTAINERS | 8 +- include/uapi/linux/dev_energymodel.h | 89 ++++++++++++ include/uapi/linux/energy_model.h | 63 -------- kernel/power/em_netlink.c | 135 ++++++++++------- kernel/power/em_netlink_autogen.c | 44 +++--- kernel/power/em_netlink_autogen.h | 20 +-- 8 files changed, 384 insertions(+), 266 deletions(-) create mode 100644 Documentation/netlink/specs/dev-energymodel.yaml delete mode 100644 Documentation/netlink/specs/em.yaml create mode 100644 include/uapi/linux/dev_energymodel.h delete mode 100644 include/uapi/linux/energy_model.h (limited to 'include/uapi/linux') diff --git a/Documentation/netlink/specs/dev-energymodel.yaml b/Documentation/netlink/specs/dev-energymodel.yaml new file mode 100644 index 000000000000..cbc4bc38f23c --- /dev/null +++ b/Documentation/netlink/specs/dev-energymodel.yaml @@ -0,0 +1,175 @@ +# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) +# +# Copyright (c) 2025 Valve Corporation. +# +--- +name: dev-energymodel + +doc: | + Energy model netlink interface to notify its changes. + +protocol: genetlink + +uapi-header: linux/dev_energymodel.h + +definitions: + - + type: flags + name: perf-state-flags + entries: + - + name: perf-state-inefficient + doc: >- + The performance state is inefficient. There is in this perf-domain, + another performance state with a higher frequency but a lower or + equal power cost. + - + type: flags + name: perf-domain-flags + entries: + - + name: perf-domain-microwatts + doc: >- + The power values are in micro-Watts or some other scale. + - + name: perf-domain-skip-inefficiencies + doc: >- + Skip inefficient states when estimating energy consumption. + - + name: perf-domain-artificial + doc: >- + The power values are artificial and might be created by platform + missing real power information. + +attribute-sets: + - + name: perf-domains + doc: >- + Information on all the performance domains. + attributes: + - + name: perf-domain + type: nest + nested-attributes: perf-domain + multi-attr: true + - + name: perf-domain + doc: >- + Information on a single performance domains. + attributes: + - + name: pad + type: pad + - + name: perf-domain-id + type: u32 + doc: >- + A unique ID number for each performance domain. + - + name: flags + type: u64 + doc: >- + Bitmask of performance domain flags. + enum: perf-domain-flags + - + name: cpus + type: string + doc: >- + CPUs that belong to this performance domain. + - + name: perf-table + doc: >- + Performance states table. + attributes: + - + name: perf-domain-id + type: u32 + doc: >- + A unique ID number for each performance domain. + - + name: perf-state + type: nest + nested-attributes: perf-state + multi-attr: true + - + name: perf-state + doc: >- + Performance state of a performance domain. + attributes: + - + name: pad + type: pad + - + name: performance + type: u64 + doc: >- + CPU performance (capacity) at a given frequency. + - + name: frequency + type: u64 + doc: >- + The frequency in KHz, for consistency with CPUFreq. + - + name: power + type: u64 + doc: >- + The power consumed at this level (by 1 CPU or by a registered + device). It can be a total power: static and dynamic. + - + name: cost + type: u64 + doc: >- + The cost coefficient associated with this level, used during energy + calculation. Equal to: power * max_frequency / frequency. + - + name: flags + type: u64 + doc: >- + Bitmask of performance state flags. + enum: perf-state-flags + +operations: + list: + - + name: get-perf-domains + attribute-set: perf-domains + doc: Get the list of information for all performance domains. + do: + reply: + attributes: + - perf-domain + - + name: get-perf-table + attribute-set: perf-table + doc: Get the energy model table of a performance domain. + do: + request: + attributes: + - perf-domain-id + reply: + attributes: + - perf-domain-id + - perf-state + - + name: perf-domain-created + doc: A performance domain is created. + notify: get-perf-table + mcgrp: event + - + name: perf-domain-updated + doc: A performance domain is updated. + notify: get-perf-table + mcgrp: event + - + name: perf-domain-deleted + doc: A performance domain is deleted. + attribute-set: perf-table + event: + attributes: + - perf-domain-id + mcgrp: event + +mcast-groups: + list: + - + name: event diff --git a/Documentation/netlink/specs/em.yaml b/Documentation/netlink/specs/em.yaml deleted file mode 100644 index 0c595a874f08..000000000000 --- a/Documentation/netlink/specs/em.yaml +++ /dev/null @@ -1,116 +0,0 @@ -# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) -# -# Copyright (c) 2025 Valve Corporation. -# ---- -name: em - -doc: | - Energy model netlink interface to notify its changes. - -protocol: genetlink - -uapi-header: linux/energy_model.h - -attribute-sets: - - - name: pds - attributes: - - - name: pd - type: nest - nested-attributes: pd - multi-attr: true - - - name: pd - attributes: - - - name: pad - type: pad - - - name: pd-id - type: u32 - - - name: flags - type: u64 - - - name: cpus - type: string - - - name: pd-table - attributes: - - - name: pd-id - type: u32 - - - name: ps - type: nest - nested-attributes: ps - multi-attr: true - - - name: ps - attributes: - - - name: pad - type: pad - - - name: performance - type: u64 - - - name: frequency - type: u64 - - - name: power - type: u64 - - - name: cost - type: u64 - - - name: flags - type: u64 - -operations: - list: - - - name: get-pds - attribute-set: pds - doc: Get the list of information for all performance domains. - do: - reply: - attributes: - - pd - - - name: get-pd-table - attribute-set: pd-table - doc: Get the energy model table of a performance domain. - do: - request: - attributes: - - pd-id - reply: - attributes: - - pd-id - - ps - - - name: pd-created - doc: A performance domain is created. - notify: get-pd-table - mcgrp: event - - - name: pd-updated - doc: A performance domain is updated. - notify: get-pd-table - mcgrp: event - - - name: pd-deleted - doc: A performance domain is deleted. - attribute-set: pd-table - event: - attributes: - - pd-id - mcgrp: event - -mcast-groups: - list: - - - name: event diff --git a/MAINTAINERS b/MAINTAINERS index 765ad2daa218..1e208243b28e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9298,12 +9298,12 @@ M: Lukasz Luba M: "Rafael J. Wysocki" L: linux-pm@vger.kernel.org S: Maintained -F: kernel/power/energy_model.c -F: include/linux/energy_model.h +F: Documentation/netlink/specs/dev-energymodel.yaml F: Documentation/power/energy-model.rst -F: Documentation/netlink/specs/em.yaml -F: include/uapi/linux/energy_model.h +F: include/linux/energy_model.h +F: include/uapi/linux/dev_energymodel.h F: kernel/power/em_netlink*.* +F: kernel/power/energy_model.c EPAPR HYPERVISOR BYTE CHANNEL DEVICE DRIVER M: Laurentiu Tudor diff --git a/include/uapi/linux/dev_energymodel.h b/include/uapi/linux/dev_energymodel.h new file mode 100644 index 000000000000..3399967e1f93 --- /dev/null +++ b/include/uapi/linux/dev_energymodel.h @@ -0,0 +1,89 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */ +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/dev-energymodel.yaml */ +/* YNL-GEN uapi header */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ + +#ifndef _UAPI_LINUX_DEV_ENERGYMODEL_H +#define _UAPI_LINUX_DEV_ENERGYMODEL_H + +#define DEV_ENERGYMODEL_FAMILY_NAME "dev-energymodel" +#define DEV_ENERGYMODEL_FAMILY_VERSION 1 + +/** + * enum dev_energymodel_perf_state_flags + * @DEV_ENERGYMODEL_PERF_STATE_FLAGS_PERF_STATE_INEFFICIENT: The performance + * state is inefficient. There is in this perf-domain, another performance + * state with a higher frequency but a lower or equal power cost. + */ +enum dev_energymodel_perf_state_flags { + DEV_ENERGYMODEL_PERF_STATE_FLAGS_PERF_STATE_INEFFICIENT = 1, +}; + +/** + * enum dev_energymodel_perf_domain_flags + * @DEV_ENERGYMODEL_PERF_DOMAIN_FLAGS_PERF_DOMAIN_MICROWATTS: The power values + * are in micro-Watts or some other scale. + * @DEV_ENERGYMODEL_PERF_DOMAIN_FLAGS_PERF_DOMAIN_SKIP_INEFFICIENCIES: Skip + * inefficient states when estimating energy consumption. + * @DEV_ENERGYMODEL_PERF_DOMAIN_FLAGS_PERF_DOMAIN_ARTIFICIAL: The power values + * are artificial and might be created by platform missing real power + * information. + */ +enum dev_energymodel_perf_domain_flags { + DEV_ENERGYMODEL_PERF_DOMAIN_FLAGS_PERF_DOMAIN_MICROWATTS = 1, + DEV_ENERGYMODEL_PERF_DOMAIN_FLAGS_PERF_DOMAIN_SKIP_INEFFICIENCIES = 2, + DEV_ENERGYMODEL_PERF_DOMAIN_FLAGS_PERF_DOMAIN_ARTIFICIAL = 4, +}; + +enum { + DEV_ENERGYMODEL_A_PERF_DOMAINS_PERF_DOMAIN = 1, + + __DEV_ENERGYMODEL_A_PERF_DOMAINS_MAX, + DEV_ENERGYMODEL_A_PERF_DOMAINS_MAX = (__DEV_ENERGYMODEL_A_PERF_DOMAINS_MAX - 1) +}; + +enum { + DEV_ENERGYMODEL_A_PERF_DOMAIN_PAD = 1, + DEV_ENERGYMODEL_A_PERF_DOMAIN_PERF_DOMAIN_ID, + DEV_ENERGYMODEL_A_PERF_DOMAIN_FLAGS, + DEV_ENERGYMODEL_A_PERF_DOMAIN_CPUS, + + __DEV_ENERGYMODEL_A_PERF_DOMAIN_MAX, + DEV_ENERGYMODEL_A_PERF_DOMAIN_MAX = (__DEV_ENERGYMODEL_A_PERF_DOMAIN_MAX - 1) +}; + +enum { + DEV_ENERGYMODEL_A_PERF_TABLE_PERF_DOMAIN_ID = 1, + DEV_ENERGYMODEL_A_PERF_TABLE_PERF_STATE, + + __DEV_ENERGYMODEL_A_PERF_TABLE_MAX, + DEV_ENERGYMODEL_A_PERF_TABLE_MAX = (__DEV_ENERGYMODEL_A_PERF_TABLE_MAX - 1) +}; + +enum { + DEV_ENERGYMODEL_A_PERF_STATE_PAD = 1, + DEV_ENERGYMODEL_A_PERF_STATE_PERFORMANCE, + DEV_ENERGYMODEL_A_PERF_STATE_FREQUENCY, + DEV_ENERGYMODEL_A_PERF_STATE_POWER, + DEV_ENERGYMODEL_A_PERF_STATE_COST, + DEV_ENERGYMODEL_A_PERF_STATE_FLAGS, + + __DEV_ENERGYMODEL_A_PERF_STATE_MAX, + DEV_ENERGYMODEL_A_PERF_STATE_MAX = (__DEV_ENERGYMODEL_A_PERF_STATE_MAX - 1) +}; + +enum { + DEV_ENERGYMODEL_CMD_GET_PERF_DOMAINS = 1, + DEV_ENERGYMODEL_CMD_GET_PERF_TABLE, + DEV_ENERGYMODEL_CMD_PERF_DOMAIN_CREATED, + DEV_ENERGYMODEL_CMD_PERF_DOMAIN_UPDATED, + DEV_ENERGYMODEL_CMD_PERF_DOMAIN_DELETED, + + __DEV_ENERGYMODEL_CMD_MAX, + DEV_ENERGYMODEL_CMD_MAX = (__DEV_ENERGYMODEL_CMD_MAX - 1) +}; + +#define DEV_ENERGYMODEL_MCGRP_EVENT "event" + +#endif /* _UAPI_LINUX_DEV_ENERGYMODEL_H */ diff --git a/include/uapi/linux/energy_model.h b/include/uapi/linux/energy_model.h deleted file mode 100644 index 0bcad967854f..000000000000 --- a/include/uapi/linux/energy_model.h +++ /dev/null @@ -1,63 +0,0 @@ -/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */ -/* Do not edit directly, auto-generated from: */ -/* Documentation/netlink/specs/em.yaml */ -/* YNL-GEN uapi header */ -/* To regenerate run: tools/net/ynl/ynl-regen.sh */ - -#ifndef _UAPI_LINUX_ENERGY_MODEL_H -#define _UAPI_LINUX_ENERGY_MODEL_H - -#define EM_FAMILY_NAME "em" -#define EM_FAMILY_VERSION 1 - -enum { - EM_A_PDS_PD = 1, - - __EM_A_PDS_MAX, - EM_A_PDS_MAX = (__EM_A_PDS_MAX - 1) -}; - -enum { - EM_A_PD_PAD = 1, - EM_A_PD_PD_ID, - EM_A_PD_FLAGS, - EM_A_PD_CPUS, - - __EM_A_PD_MAX, - EM_A_PD_MAX = (__EM_A_PD_MAX - 1) -}; - -enum { - EM_A_PD_TABLE_PD_ID = 1, - EM_A_PD_TABLE_PS, - - __EM_A_PD_TABLE_MAX, - EM_A_PD_TABLE_MAX = (__EM_A_PD_TABLE_MAX - 1) -}; - -enum { - EM_A_PS_PAD = 1, - EM_A_PS_PERFORMANCE, - EM_A_PS_FREQUENCY, - EM_A_PS_POWER, - EM_A_PS_COST, - EM_A_PS_FLAGS, - - __EM_A_PS_MAX, - EM_A_PS_MAX = (__EM_A_PS_MAX - 1) -}; - -enum { - EM_CMD_GET_PDS = 1, - EM_CMD_GET_PD_TABLE, - EM_CMD_PD_CREATED, - EM_CMD_PD_UPDATED, - EM_CMD_PD_DELETED, - - __EM_CMD_MAX, - EM_CMD_MAX = (__EM_CMD_MAX - 1) -}; - -#define EM_MCGRP_EVENT "event" - -#endif /* _UAPI_LINUX_ENERGY_MODEL_H */ diff --git a/kernel/power/em_netlink.c b/kernel/power/em_netlink.c index 4b85da138a06..6f6238c465bb 100644 --- a/kernel/power/em_netlink.c +++ b/kernel/power/em_netlink.c @@ -12,27 +12,31 @@ #include #include #include -#include +#include #include "em_netlink.h" #include "em_netlink_autogen.h" -#define EM_A_PD_CPUS_LEN 256 +#define DEV_ENERGYMODEL_A_PERF_DOMAIN_CPUS_LEN 256 /*************************** Command encoding ********************************/ static int __em_nl_get_pd_size(struct em_perf_domain *pd, void *data) { - char cpus_buf[EM_A_PD_CPUS_LEN]; + char cpus_buf[DEV_ENERGYMODEL_A_PERF_DOMAIN_CPUS_LEN]; int *tot_msg_sz = data; int msg_sz, cpus_sz; cpus_sz = snprintf(cpus_buf, sizeof(cpus_buf), "%*pb", cpumask_pr_args(to_cpumask(pd->cpus))); - msg_sz = nla_total_size(0) + /* EM_A_PDS_PD */ - nla_total_size(sizeof(u32)) + /* EM_A_PD_PD_ID */ - nla_total_size_64bit(sizeof(u64)) + /* EM_A_PD_FLAGS */ - nla_total_size(cpus_sz); /* EM_A_PD_CPUS */ + msg_sz = nla_total_size(0) + + /* DEV_ENERGYMODEL_A_PERF_DOMAINS_PERF_DOMAIN */ + nla_total_size(sizeof(u32)) + + /* DEV_ENERGYMODEL_A_PERF_DOMAIN_PERF_DOMAIN_ID */ + nla_total_size_64bit(sizeof(u64)) + + /* DEV_ENERGYMODEL_A_PERF_DOMAIN_FLAGS */ + nla_total_size(cpus_sz); + /* DEV_ENERGYMODEL_A_PERF_DOMAIN_CPUS */ *tot_msg_sz += nlmsg_total_size(genlmsg_msg_size(msg_sz)); return 0; @@ -40,23 +44,26 @@ static int __em_nl_get_pd_size(struct em_perf_domain *pd, void *data) static int __em_nl_get_pd(struct em_perf_domain *pd, void *data) { - char cpus_buf[EM_A_PD_CPUS_LEN]; + char cpus_buf[DEV_ENERGYMODEL_A_PERF_DOMAIN_CPUS_LEN]; struct sk_buff *msg = data; struct nlattr *entry; - entry = nla_nest_start(msg, EM_A_PDS_PD); + entry = nla_nest_start(msg, + DEV_ENERGYMODEL_A_PERF_DOMAINS_PERF_DOMAIN); if (!entry) goto out_cancel_nest; - if (nla_put_u32(msg, EM_A_PD_PD_ID, pd->id)) + if (nla_put_u32(msg, DEV_ENERGYMODEL_A_PERF_DOMAIN_PERF_DOMAIN_ID, + pd->id)) goto out_cancel_nest; - if (nla_put_u64_64bit(msg, EM_A_PD_FLAGS, pd->flags, EM_A_PD_PAD)) + if (nla_put_u64_64bit(msg, DEV_ENERGYMODEL_A_PERF_DOMAIN_FLAGS, + pd->flags, DEV_ENERGYMODEL_A_PERF_DOMAIN_PAD)) goto out_cancel_nest; snprintf(cpus_buf, sizeof(cpus_buf), "%*pb", cpumask_pr_args(to_cpumask(pd->cpus))); - if (nla_put_string(msg, EM_A_PD_CPUS, cpus_buf)) + if (nla_put_string(msg, DEV_ENERGYMODEL_A_PERF_DOMAIN_CPUS, cpus_buf)) goto out_cancel_nest; nla_nest_end(msg, entry); @@ -69,7 +76,8 @@ out_cancel_nest: return -EMSGSIZE; } -int em_nl_get_pds_doit(struct sk_buff *skb, struct genl_info *info) +int dev_energymodel_nl_get_perf_domains_doit(struct sk_buff *skb, + struct genl_info *info) { struct sk_buff *msg; void *hdr; @@ -82,7 +90,7 @@ int em_nl_get_pds_doit(struct sk_buff *skb, struct genl_info *info) if (!msg) return -ENOMEM; - hdr = genlmsg_put_reply(msg, info, &em_nl_family, 0, cmd); + hdr = genlmsg_put_reply(msg, info, &dev_energymodel_nl_family, 0, cmd); if (!hdr) goto out_free_msg; @@ -107,10 +115,10 @@ static struct em_perf_domain *__em_nl_get_pd_table_id(struct nlattr **attrs) struct em_perf_domain *pd; int id; - if (!attrs[EM_A_PD_TABLE_PD_ID]) + if (!attrs[DEV_ENERGYMODEL_A_PERF_TABLE_PERF_DOMAIN_ID]) return NULL; - id = nla_get_u32(attrs[EM_A_PD_TABLE_PD_ID]); + id = nla_get_u32(attrs[DEV_ENERGYMODEL_A_PERF_TABLE_PERF_DOMAIN_ID]); pd = em_perf_domain_get_by_id(id); return pd; } @@ -119,25 +127,34 @@ static int __em_nl_get_pd_table_size(const struct em_perf_domain *pd) { int id_sz, ps_sz; - id_sz = nla_total_size(sizeof(u32)); /* EM_A_PD_TABLE_PD_ID */ - ps_sz = nla_total_size(0) + /* EM_A_PD_TABLE_PS */ - nla_total_size_64bit(sizeof(u64)) + /* EM_A_PS_PERFORMANCE */ - nla_total_size_64bit(sizeof(u64)) + /* EM_A_PS_FREQUENCY */ - nla_total_size_64bit(sizeof(u64)) + /* EM_A_PS_POWER */ - nla_total_size_64bit(sizeof(u64)) + /* EM_A_PS_COST */ - nla_total_size_64bit(sizeof(u64)); /* EM_A_PS_FLAGS */ + id_sz = nla_total_size(sizeof(u32)); + /* DEV_ENERGYMODEL_A_PERF_TABLE_PERF_DOMAIN_ID */ + ps_sz = nla_total_size(0) + + /* DEV_ENERGYMODEL_A_PERF_TABLE_PERF_STATE */ + nla_total_size_64bit(sizeof(u64)) + + /* DEV_ENERGYMODEL_A_PERF_STATE_PERFORMANCE */ + nla_total_size_64bit(sizeof(u64)) + + /* DEV_ENERGYMODEL_A_PERF_STATE_FREQUENCY */ + nla_total_size_64bit(sizeof(u64)) + + /* DEV_ENERGYMODEL_A_PERF_STATE_POWER */ + nla_total_size_64bit(sizeof(u64)) + + /* DEV_ENERGYMODEL_A_PERF_STATE_COST */ + nla_total_size_64bit(sizeof(u64)); + /* DEV_ENERGYMODEL_A_PERF_STATE_FLAGS */ ps_sz *= pd->nr_perf_states; return nlmsg_total_size(genlmsg_msg_size(id_sz + ps_sz)); } -static int __em_nl_get_pd_table(struct sk_buff *msg, const struct em_perf_domain *pd) +static +int __em_nl_get_pd_table(struct sk_buff *msg, const struct em_perf_domain *pd) { struct em_perf_state *table, *ps; struct nlattr *entry; int i; - if (nla_put_u32(msg, EM_A_PD_TABLE_PD_ID, pd->id)) + if (nla_put_u32(msg, DEV_ENERGYMODEL_A_PERF_TABLE_PERF_DOMAIN_ID, + pd->id)) goto out_err; rcu_read_lock(); @@ -146,24 +163,35 @@ static int __em_nl_get_pd_table(struct sk_buff *msg, const struct em_perf_domain for (i = 0; i < pd->nr_perf_states; i++) { ps = &table[i]; - entry = nla_nest_start(msg, EM_A_PD_TABLE_PS); + entry = nla_nest_start(msg, + DEV_ENERGYMODEL_A_PERF_TABLE_PERF_STATE); if (!entry) goto out_unlock_ps; - if (nla_put_u64_64bit(msg, EM_A_PS_PERFORMANCE, - ps->performance, EM_A_PS_PAD)) + if (nla_put_u64_64bit(msg, + DEV_ENERGYMODEL_A_PERF_STATE_PERFORMANCE, + ps->performance, + DEV_ENERGYMODEL_A_PERF_STATE_PAD)) goto out_cancel_ps_nest; - if (nla_put_u64_64bit(msg, EM_A_PS_FREQUENCY, - ps->frequency, EM_A_PS_PAD)) + if (nla_put_u64_64bit(msg, + DEV_ENERGYMODEL_A_PERF_STATE_FREQUENCY, + ps->frequency, + DEV_ENERGYMODEL_A_PERF_STATE_PAD)) goto out_cancel_ps_nest; - if (nla_put_u64_64bit(msg, EM_A_PS_POWER, - ps->power, EM_A_PS_PAD)) + if (nla_put_u64_64bit(msg, + DEV_ENERGYMODEL_A_PERF_STATE_POWER, + ps->power, + DEV_ENERGYMODEL_A_PERF_STATE_PAD)) goto out_cancel_ps_nest; - if (nla_put_u64_64bit(msg, EM_A_PS_COST, - ps->cost, EM_A_PS_PAD)) + if (nla_put_u64_64bit(msg, + DEV_ENERGYMODEL_A_PERF_STATE_COST, + ps->cost, + DEV_ENERGYMODEL_A_PERF_STATE_PAD)) goto out_cancel_ps_nest; - if (nla_put_u64_64bit(msg, EM_A_PS_FLAGS, - ps->flags, EM_A_PS_PAD)) + if (nla_put_u64_64bit(msg, + DEV_ENERGYMODEL_A_PERF_STATE_FLAGS, + ps->flags, + DEV_ENERGYMODEL_A_PERF_STATE_PAD)) goto out_cancel_ps_nest; nla_nest_end(msg, entry); @@ -179,7 +207,8 @@ out_err: return -EMSGSIZE; } -int em_nl_get_pd_table_doit(struct sk_buff *skb, struct genl_info *info) +int dev_energymodel_nl_get_perf_table_doit(struct sk_buff *skb, + struct genl_info *info) { int cmd = info->genlhdr->cmd; int msg_sz, ret = -EMSGSIZE; @@ -197,7 +226,7 @@ int em_nl_get_pd_table_doit(struct sk_buff *skb, struct genl_info *info) if (!msg) return -ENOMEM; - hdr = genlmsg_put_reply(msg, info, &em_nl_family, 0, cmd); + hdr = genlmsg_put_reply(msg, info, &dev_energymodel_nl_family, 0, cmd); if (!hdr) goto out_free_msg; @@ -221,7 +250,7 @@ static void __em_notify_pd_table(const struct em_perf_domain *pd, int ntf_type) int msg_sz, ret = -EMSGSIZE; void *hdr; - if (!genl_has_listeners(&em_nl_family, &init_net, EM_NLGRP_EVENT)) + if (!genl_has_listeners(&dev_energymodel_nl_family, &init_net, DEV_ENERGYMODEL_NLGRP_EVENT)) return; msg_sz = __em_nl_get_pd_table_size(pd); @@ -230,7 +259,7 @@ static void __em_notify_pd_table(const struct em_perf_domain *pd, int ntf_type) if (!msg) return; - hdr = genlmsg_put(msg, 0, 0, &em_nl_family, 0, ntf_type); + hdr = genlmsg_put(msg, 0, 0, &dev_energymodel_nl_family, 0, ntf_type); if (!hdr) goto out_free_msg; @@ -240,28 +269,28 @@ static void __em_notify_pd_table(const struct em_perf_domain *pd, int ntf_type) genlmsg_end(msg, hdr); - genlmsg_multicast(&em_nl_family, msg, 0, EM_NLGRP_EVENT, GFP_KERNEL); + genlmsg_multicast(&dev_energymodel_nl_family, msg, 0, + DEV_ENERGYMODEL_NLGRP_EVENT, GFP_KERNEL); return; out_free_msg: nlmsg_free(msg); - return; } void em_notify_pd_created(const struct em_perf_domain *pd) { - __em_notify_pd_table(pd, EM_CMD_PD_CREATED); + __em_notify_pd_table(pd, DEV_ENERGYMODEL_CMD_PERF_DOMAIN_CREATED); } void em_notify_pd_updated(const struct em_perf_domain *pd) { - __em_notify_pd_table(pd, EM_CMD_PD_UPDATED); + __em_notify_pd_table(pd, DEV_ENERGYMODEL_CMD_PERF_DOMAIN_UPDATED); } static int __em_notify_pd_deleted_size(const struct em_perf_domain *pd) { - int id_sz = nla_total_size(sizeof(u32)); /* EM_A_PD_TABLE_PD_ID */ + int id_sz = nla_total_size(sizeof(u32)); /* DEV_ENERGYMODEL_A_PERF_TABLE_PERF_DOMAIN_ID */ return nlmsg_total_size(genlmsg_msg_size(id_sz)); } @@ -272,7 +301,8 @@ void em_notify_pd_deleted(const struct em_perf_domain *pd) void *hdr; int msg_sz; - if (!genl_has_listeners(&em_nl_family, &init_net, EM_NLGRP_EVENT)) + if (!genl_has_listeners(&dev_energymodel_nl_family, &init_net, + DEV_ENERGYMODEL_NLGRP_EVENT)) return; msg_sz = __em_notify_pd_deleted_size(pd); @@ -281,28 +311,29 @@ void em_notify_pd_deleted(const struct em_perf_domain *pd) if (!msg) return; - hdr = genlmsg_put(msg, 0, 0, &em_nl_family, 0, EM_CMD_PD_DELETED); + hdr = genlmsg_put(msg, 0, 0, &dev_energymodel_nl_family, 0, + DEV_ENERGYMODEL_CMD_PERF_DOMAIN_DELETED); if (!hdr) goto out_free_msg; - if (nla_put_u32(msg, EM_A_PD_TABLE_PD_ID, pd->id)) { + if (nla_put_u32(msg, DEV_ENERGYMODEL_A_PERF_TABLE_PERF_DOMAIN_ID, + pd->id)) goto out_free_msg; - } genlmsg_end(msg, hdr); - genlmsg_multicast(&em_nl_family, msg, 0, EM_NLGRP_EVENT, GFP_KERNEL); + genlmsg_multicast(&dev_energymodel_nl_family, msg, 0, + DEV_ENERGYMODEL_NLGRP_EVENT, GFP_KERNEL); return; out_free_msg: nlmsg_free(msg); - return; } /**************************** Initialization *********************************/ static int __init em_netlink_init(void) { - return genl_register_family(&em_nl_family); + return genl_register_family(&dev_energymodel_nl_family); } postcore_initcall(em_netlink_init); diff --git a/kernel/power/em_netlink_autogen.c b/kernel/power/em_netlink_autogen.c index ceb3b2bb6ebe..44acef0e7df2 100644 --- a/kernel/power/em_netlink_autogen.c +++ b/kernel/power/em_netlink_autogen.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) /* Do not edit directly, auto-generated from: */ -/* Documentation/netlink/specs/em.yaml */ +/* Documentation/netlink/specs/dev-energymodel.yaml */ /* YNL-GEN kernel source */ /* To regenerate run: tools/net/ynl/ynl-regen.sh */ @@ -9,41 +9,41 @@ #include "em_netlink_autogen.h" -#include +#include -/* EM_CMD_GET_PD_TABLE - do */ -static const struct nla_policy em_get_pd_table_nl_policy[EM_A_PD_TABLE_PD_ID + 1] = { - [EM_A_PD_TABLE_PD_ID] = { .type = NLA_U32, }, +/* DEV_ENERGYMODEL_CMD_GET_PERF_TABLE - do */ +static const struct nla_policy dev_energymodel_get_perf_table_nl_policy[DEV_ENERGYMODEL_A_PERF_TABLE_PERF_DOMAIN_ID + 1] = { + [DEV_ENERGYMODEL_A_PERF_TABLE_PERF_DOMAIN_ID] = { .type = NLA_U32, }, }; -/* Ops table for em */ -static const struct genl_split_ops em_nl_ops[] = { +/* Ops table for dev_energymodel */ +static const struct genl_split_ops dev_energymodel_nl_ops[] = { { - .cmd = EM_CMD_GET_PDS, - .doit = em_nl_get_pds_doit, + .cmd = DEV_ENERGYMODEL_CMD_GET_PERF_DOMAINS, + .doit = dev_energymodel_nl_get_perf_domains_doit, .flags = GENL_CMD_CAP_DO, }, { - .cmd = EM_CMD_GET_PD_TABLE, - .doit = em_nl_get_pd_table_doit, - .policy = em_get_pd_table_nl_policy, - .maxattr = EM_A_PD_TABLE_PD_ID, + .cmd = DEV_ENERGYMODEL_CMD_GET_PERF_TABLE, + .doit = dev_energymodel_nl_get_perf_table_doit, + .policy = dev_energymodel_get_perf_table_nl_policy, + .maxattr = DEV_ENERGYMODEL_A_PERF_TABLE_PERF_DOMAIN_ID, .flags = GENL_CMD_CAP_DO, }, }; -static const struct genl_multicast_group em_nl_mcgrps[] = { - [EM_NLGRP_EVENT] = { "event", }, +static const struct genl_multicast_group dev_energymodel_nl_mcgrps[] = { + [DEV_ENERGYMODEL_NLGRP_EVENT] = { "event", }, }; -struct genl_family em_nl_family __ro_after_init = { - .name = EM_FAMILY_NAME, - .version = EM_FAMILY_VERSION, +struct genl_family dev_energymodel_nl_family __ro_after_init = { + .name = DEV_ENERGYMODEL_FAMILY_NAME, + .version = DEV_ENERGYMODEL_FAMILY_VERSION, .netnsok = true, .parallel_ops = true, .module = THIS_MODULE, - .split_ops = em_nl_ops, - .n_split_ops = ARRAY_SIZE(em_nl_ops), - .mcgrps = em_nl_mcgrps, - .n_mcgrps = ARRAY_SIZE(em_nl_mcgrps), + .split_ops = dev_energymodel_nl_ops, + .n_split_ops = ARRAY_SIZE(dev_energymodel_nl_ops), + .mcgrps = dev_energymodel_nl_mcgrps, + .n_mcgrps = ARRAY_SIZE(dev_energymodel_nl_mcgrps), }; diff --git a/kernel/power/em_netlink_autogen.h b/kernel/power/em_netlink_autogen.h index 140ab548103c..f7e4bddcbd53 100644 --- a/kernel/power/em_netlink_autogen.h +++ b/kernel/power/em_netlink_autogen.h @@ -1,24 +1,26 @@ /* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */ /* Do not edit directly, auto-generated from: */ -/* Documentation/netlink/specs/em.yaml */ +/* Documentation/netlink/specs/dev-energymodel.yaml */ /* YNL-GEN kernel header */ /* To regenerate run: tools/net/ynl/ynl-regen.sh */ -#ifndef _LINUX_EM_GEN_H -#define _LINUX_EM_GEN_H +#ifndef _LINUX_DEV_ENERGYMODEL_GEN_H +#define _LINUX_DEV_ENERGYMODEL_GEN_H #include #include -#include +#include -int em_nl_get_pds_doit(struct sk_buff *skb, struct genl_info *info); -int em_nl_get_pd_table_doit(struct sk_buff *skb, struct genl_info *info); +int dev_energymodel_nl_get_perf_domains_doit(struct sk_buff *skb, + struct genl_info *info); +int dev_energymodel_nl_get_perf_table_doit(struct sk_buff *skb, + struct genl_info *info); enum { - EM_NLGRP_EVENT, + DEV_ENERGYMODEL_NLGRP_EVENT, }; -extern struct genl_family em_nl_family; +extern struct genl_family dev_energymodel_nl_family; -#endif /* _LINUX_EM_GEN_H */ +#endif /* _LINUX_DEV_ENERGYMODEL_GEN_H */ -- cgit v1.2.3 From 380ff27af25e49e2cb2ff8fd0ecd7c95be2976ee Mon Sep 17 00:00:00 2001 From: Changwoo Min Date: Thu, 8 Jan 2026 14:32:12 +0900 Subject: PM: EM: Add dump to get-perf-domains in the EM YNL spec Add dump to get-perf-domains, so that a user can fetch either information about a specific performance domain with do or information about all performance domains with dump. Share the reply format of do and dump using perf-domain-attrs, so remove perf-domains. The YNL spec, autogenerated files, and the do implementation are updated, and the dump implementation is added. Suggested-by: Donald Hunter Reviewed-by: Lukasz Luba Reviewed-by: Donald Hunter Signed-off-by: Changwoo Min Link: https://patch.msgid.link/20260108053212.642478-5-changwoo@igalia.com Signed-off-by: Rafael J. Wysocki --- Documentation/netlink/specs/dev-energymodel.yaml | 25 +++++---- include/uapi/linux/dev_energymodel.h | 7 --- kernel/power/em_netlink.c | 68 ++++++++++++++++++------ kernel/power/em_netlink_autogen.c | 16 +++++- kernel/power/em_netlink_autogen.h | 2 + 5 files changed, 80 insertions(+), 38 deletions(-) (limited to 'include/uapi/linux') diff --git a/Documentation/netlink/specs/dev-energymodel.yaml b/Documentation/netlink/specs/dev-energymodel.yaml index af8b8f72f722..11faabfdfbe8 100644 --- a/Documentation/netlink/specs/dev-energymodel.yaml +++ b/Documentation/netlink/specs/dev-energymodel.yaml @@ -42,16 +42,6 @@ definitions: missing real power information. attribute-sets: - - - name: perf-domains - doc: >- - Information on all the performance domains. - attributes: - - - name: perf-domain - type: nest - nested-attributes: perf-domain - multi-attr: true - name: perf-domain doc: >- @@ -133,12 +123,21 @@ operations: list: - name: get-perf-domains - attribute-set: perf-domains + attribute-set: perf-domain doc: Get the list of information for all performance domains. do: - reply: + request: attributes: - - perf-domain + - perf-domain-id + reply: + attributes: &perf-domain-attrs + - pad + - perf-domain-id + - flags + - cpus + dump: + reply: + attributes: *perf-domain-attrs - name: get-perf-table attribute-set: perf-table diff --git a/include/uapi/linux/dev_energymodel.h b/include/uapi/linux/dev_energymodel.h index 3399967e1f93..355d8885c9a0 100644 --- a/include/uapi/linux/dev_energymodel.h +++ b/include/uapi/linux/dev_energymodel.h @@ -36,13 +36,6 @@ enum dev_energymodel_perf_domain_flags { DEV_ENERGYMODEL_PERF_DOMAIN_FLAGS_PERF_DOMAIN_ARTIFICIAL = 4, }; -enum { - DEV_ENERGYMODEL_A_PERF_DOMAINS_PERF_DOMAIN = 1, - - __DEV_ENERGYMODEL_A_PERF_DOMAINS_MAX, - DEV_ENERGYMODEL_A_PERF_DOMAINS_MAX = (__DEV_ENERGYMODEL_A_PERF_DOMAINS_MAX - 1) -}; - enum { DEV_ENERGYMODEL_A_PERF_DOMAIN_PAD = 1, DEV_ENERGYMODEL_A_PERF_DOMAIN_PERF_DOMAIN_ID, diff --git a/kernel/power/em_netlink.c b/kernel/power/em_netlink.c index b6edb018c65a..5a611d3950fd 100644 --- a/kernel/power/em_netlink.c +++ b/kernel/power/em_netlink.c @@ -18,6 +18,13 @@ #include "em_netlink_autogen.h" /*************************** Command encoding ********************************/ +struct dump_ctx { + int idx; + int start; + struct sk_buff *skb; + struct netlink_callback *cb; +}; + static int __em_nl_get_pd_size(struct em_perf_domain *pd, void *data) { int nr_cpus, msg_sz, cpus_sz; @@ -43,14 +50,8 @@ static int __em_nl_get_pd(struct em_perf_domain *pd, void *data) { struct sk_buff *msg = data; struct cpumask *cpumask; - struct nlattr *entry; int cpu; - entry = nla_nest_start(msg, - DEV_ENERGYMODEL_A_PERF_DOMAINS_PERF_DOMAIN); - if (!entry) - goto out_cancel_nest; - if (nla_put_u32(msg, DEV_ENERGYMODEL_A_PERF_DOMAIN_PERF_DOMAIN_ID, pd->id)) goto out_cancel_nest; @@ -66,26 +67,50 @@ static int __em_nl_get_pd(struct em_perf_domain *pd, void *data) goto out_cancel_nest; } - nla_nest_end(msg, entry); - return 0; out_cancel_nest: - nla_nest_cancel(msg, entry); - return -EMSGSIZE; } +static int __em_nl_get_pd_for_dump(struct em_perf_domain *pd, void *data) +{ + const struct genl_info *info; + struct dump_ctx *ctx = data; + void *hdr; + int ret; + + if (ctx->idx++ < ctx->start) + return 0; + + info = genl_info_dump(ctx->cb); + hdr = genlmsg_iput(ctx->skb, info); + if (!hdr) { + genlmsg_cancel(ctx->skb, hdr); + return -EMSGSIZE; + } + + ret = __em_nl_get_pd(pd, ctx->skb); + genlmsg_end(ctx->skb, hdr); + return ret; +} + int dev_energymodel_nl_get_perf_domains_doit(struct sk_buff *skb, struct genl_info *info) { + int id, ret = -EMSGSIZE, msg_sz = 0; + int cmd = info->genlhdr->cmd; + struct em_perf_domain *pd; struct sk_buff *msg; void *hdr; - int cmd = info->genlhdr->cmd; - int ret = -EMSGSIZE, msg_sz = 0; - for_each_em_perf_domain(__em_nl_get_pd_size, &msg_sz); + if (!info->attrs[DEV_ENERGYMODEL_A_PERF_DOMAIN_PERF_DOMAIN_ID]) + return -EINVAL; + id = nla_get_u32(info->attrs[DEV_ENERGYMODEL_A_PERF_DOMAIN_PERF_DOMAIN_ID]); + pd = em_perf_domain_get_by_id(id); + + __em_nl_get_pd_size(pd, &msg_sz); msg = genlmsg_new(msg_sz, GFP_KERNEL); if (!msg) return -ENOMEM; @@ -94,10 +119,9 @@ int dev_energymodel_nl_get_perf_domains_doit(struct sk_buff *skb, if (!hdr) goto out_free_msg; - ret = for_each_em_perf_domain(__em_nl_get_pd, msg); + ret = __em_nl_get_pd(pd, msg); if (ret) goto out_cancel_msg; - genlmsg_end(msg, hdr); return genlmsg_reply(msg, info); @@ -106,10 +130,22 @@ out_cancel_msg: genlmsg_cancel(msg, hdr); out_free_msg: nlmsg_free(msg); - return ret; } +int dev_energymodel_nl_get_perf_domains_dumpit(struct sk_buff *skb, + struct netlink_callback *cb) +{ + struct dump_ctx ctx = { + .idx = 0, + .start = cb->args[0], + .skb = skb, + .cb = cb, + }; + + return for_each_em_perf_domain(__em_nl_get_pd_for_dump, &ctx); +} + static struct em_perf_domain *__em_nl_get_pd_table_id(struct nlattr **attrs) { struct em_perf_domain *pd; diff --git a/kernel/power/em_netlink_autogen.c b/kernel/power/em_netlink_autogen.c index 44acef0e7df2..fedd473e4244 100644 --- a/kernel/power/em_netlink_autogen.c +++ b/kernel/power/em_netlink_autogen.c @@ -11,6 +11,11 @@ #include +/* DEV_ENERGYMODEL_CMD_GET_PERF_DOMAINS - do */ +static const struct nla_policy dev_energymodel_get_perf_domains_nl_policy[DEV_ENERGYMODEL_A_PERF_DOMAIN_PERF_DOMAIN_ID + 1] = { + [DEV_ENERGYMODEL_A_PERF_DOMAIN_PERF_DOMAIN_ID] = { .type = NLA_U32, }, +}; + /* DEV_ENERGYMODEL_CMD_GET_PERF_TABLE - do */ static const struct nla_policy dev_energymodel_get_perf_table_nl_policy[DEV_ENERGYMODEL_A_PERF_TABLE_PERF_DOMAIN_ID + 1] = { [DEV_ENERGYMODEL_A_PERF_TABLE_PERF_DOMAIN_ID] = { .type = NLA_U32, }, @@ -18,10 +23,17 @@ static const struct nla_policy dev_energymodel_get_perf_table_nl_policy[DEV_ENER /* Ops table for dev_energymodel */ static const struct genl_split_ops dev_energymodel_nl_ops[] = { + { + .cmd = DEV_ENERGYMODEL_CMD_GET_PERF_DOMAINS, + .doit = dev_energymodel_nl_get_perf_domains_doit, + .policy = dev_energymodel_get_perf_domains_nl_policy, + .maxattr = DEV_ENERGYMODEL_A_PERF_DOMAIN_PERF_DOMAIN_ID, + .flags = GENL_CMD_CAP_DO, + }, { .cmd = DEV_ENERGYMODEL_CMD_GET_PERF_DOMAINS, - .doit = dev_energymodel_nl_get_perf_domains_doit, - .flags = GENL_CMD_CAP_DO, + .dumpit = dev_energymodel_nl_get_perf_domains_dumpit, + .flags = GENL_CMD_CAP_DUMP, }, { .cmd = DEV_ENERGYMODEL_CMD_GET_PERF_TABLE, diff --git a/kernel/power/em_netlink_autogen.h b/kernel/power/em_netlink_autogen.h index f7e4bddcbd53..5caf2f7e18a5 100644 --- a/kernel/power/em_netlink_autogen.h +++ b/kernel/power/em_netlink_autogen.h @@ -14,6 +14,8 @@ int dev_energymodel_nl_get_perf_domains_doit(struct sk_buff *skb, struct genl_info *info); +int dev_energymodel_nl_get_perf_domains_dumpit(struct sk_buff *skb, + struct netlink_callback *cb); int dev_energymodel_nl_get_perf_table_doit(struct sk_buff *skb, struct genl_info *info); -- cgit v1.2.3 From c279e83953d937470f8a6e69b69f62608714f13f Mon Sep 17 00:00:00 2001 From: Nicolin Chen Date: Mon, 15 Dec 2025 13:42:19 -0800 Subject: iommu: Introduce pci_dev_reset_iommu_prepare/done() PCIe permits a device to ignore ATS invalidation TLPs while processing a reset. This creates a problem visible to the OS where an ATS invalidation command will time out. E.g. an SVA domain will have no coordination with a reset event and can racily issue ATS invalidations to a resetting device. The OS should do something to mitigate this as we do not want production systems to be reporting critical ATS failures, especially in a hypervisor environment. Broadly, OS could arrange to ignore the timeouts, block page table mutations to prevent invalidations, or disable and block ATS. The PCIe r6.0, sec 10.3.1 IMPLEMENTATION NOTE recommends SW to disable and block ATS before initiating a Function Level Reset. It also mentions that other reset methods could have the same vulnerability as well. Provide a callback from the PCI subsystem that will enclose the reset and have the iommu core temporarily change all the attached RID/PASID domains group->blocking_domain so that the IOMMU hardware would fence any incoming ATS queries. And IOMMU drivers should also synchronously stop issuing new ATS invalidations and wait for all ATS invalidations to complete. This can avoid any ATS invaliation timeouts. However, if there is a domain attachment/replacement happening during an ongoing reset, ATS routines may be re-activated between the two function calls. So, introduce a new resetting_domain in the iommu_group structure to reject any concurrent attach_dev/set_dev_pasid call during a reset for a concern of compatibility failure. Since this changes the behavior of an attach operation, update the uAPI accordingly. Note that there are two corner cases: 1. Devices in the same iommu_group Since an attachment is always per iommu_group, this means that any sibling devices in the iommu_group cannot change domain, to prevent race conditions. 2. An SR-IOV PF that is being reset while its VF is not In such case, the VF itself is already broken. So, there is no point in preventing PF from going through the iommu reset. Reviewed-by: Lu Baolu Reviewed-by: Kevin Tian Reviewed-by: Jason Gunthorpe Tested-by: Dheeraj Kumar Srivastava Signed-off-by: Nicolin Chen Signed-off-by: Joerg Roedel --- drivers/iommu/iommu.c | 173 ++++++++++++++++++++++++++++++++++++++++++++++ include/linux/iommu.h | 13 ++++ include/uapi/linux/vfio.h | 4 ++ 3 files changed, 190 insertions(+) (limited to 'include/uapi/linux') diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c index 672597100e9a..0665dedd91b2 100644 --- a/drivers/iommu/iommu.c +++ b/drivers/iommu/iommu.c @@ -61,6 +61,11 @@ struct iommu_group { int id; struct iommu_domain *default_domain; struct iommu_domain *blocking_domain; + /* + * During a group device reset, @resetting_domain points to the physical + * domain, while @domain points to the attached domain before the reset. + */ + struct iommu_domain *resetting_domain; struct iommu_domain *domain; struct list_head entry; unsigned int owner_cnt; @@ -2195,6 +2200,15 @@ int iommu_deferred_attach(struct device *dev, struct iommu_domain *domain) guard(mutex)(&dev->iommu_group->mutex); + /* + * This is a concurrent attach during a device reset. Reject it until + * pci_dev_reset_iommu_done() attaches the device to group->domain. + * + * Note that this might fail the iommu_dma_map(). But there's nothing + * more we can do here. + */ + if (dev->iommu_group->resetting_domain) + return -EBUSY; return __iommu_attach_device(domain, dev, NULL); } @@ -2253,6 +2267,17 @@ struct iommu_domain *iommu_driver_get_domain_for_dev(struct device *dev) lockdep_assert_held(&group->mutex); + /* + * Driver handles the low-level __iommu_attach_device(), including the + * one invoked by pci_dev_reset_iommu_done() re-attaching the device to + * the cached group->domain. In this case, the driver must get the old + * domain from group->resetting_domain rather than group->domain. This + * prevents it from re-attaching the device from group->domain (old) to + * group->domain (new). + */ + if (group->resetting_domain) + return group->resetting_domain; + return group->domain; } EXPORT_SYMBOL_GPL(iommu_driver_get_domain_for_dev); @@ -2409,6 +2434,13 @@ static int __iommu_group_set_domain_internal(struct iommu_group *group, if (WARN_ON(!new_domain)) return -EINVAL; + /* + * This is a concurrent attach during a device reset. Reject it until + * pci_dev_reset_iommu_done() attaches the device to group->domain. + */ + if (group->resetting_domain) + return -EBUSY; + /* * Changing the domain is done by calling attach_dev() on the new * domain. This switch does not have to be atomic and DMA can be @@ -3527,6 +3559,16 @@ int iommu_attach_device_pasid(struct iommu_domain *domain, return -EINVAL; mutex_lock(&group->mutex); + + /* + * This is a concurrent attach during a device reset. Reject it until + * pci_dev_reset_iommu_done() attaches the device to group->domain. + */ + if (group->resetting_domain) { + ret = -EBUSY; + goto out_unlock; + } + for_each_group_device(group, device) { /* * Skip PASID validation for devices without PASID support @@ -3610,6 +3652,16 @@ int iommu_replace_device_pasid(struct iommu_domain *domain, return -EINVAL; mutex_lock(&group->mutex); + + /* + * This is a concurrent attach during a device reset. Reject it until + * pci_dev_reset_iommu_done() attaches the device to group->domain. + */ + if (group->resetting_domain) { + ret = -EBUSY; + goto out_unlock; + } + entry = iommu_make_pasid_array_entry(domain, handle); curr = xa_cmpxchg(&group->pasid_array, pasid, NULL, XA_ZERO_ENTRY, GFP_KERNEL); @@ -3867,6 +3919,127 @@ err_unlock: } EXPORT_SYMBOL_NS_GPL(iommu_replace_group_handle, "IOMMUFD_INTERNAL"); +/** + * pci_dev_reset_iommu_prepare() - Block IOMMU to prepare for a PCI device reset + * @pdev: PCI device that is going to enter a reset routine + * + * The PCIe r6.0, sec 10.3.1 IMPLEMENTATION NOTE recommends to disable and block + * ATS before initiating a reset. This means that a PCIe device during the reset + * routine wants to block any IOMMU activity: translation and ATS invalidation. + * + * This function attaches the device's RID/PASID(s) the group->blocking_domain, + * setting the group->resetting_domain. This allows the IOMMU driver pausing any + * IOMMU activity while leaving the group->domain pointer intact. Later when the + * reset is finished, pci_dev_reset_iommu_done() can restore everything. + * + * Caller must use pci_dev_reset_iommu_prepare() with pci_dev_reset_iommu_done() + * before/after the core-level reset routine, to unset the resetting_domain. + * + * Return: 0 on success or negative error code if the preparation failed. + * + * These two functions are designed to be used by PCI reset functions that would + * not invoke any racy iommu_release_device(), since PCI sysfs node gets removed + * before it notifies with a BUS_NOTIFY_REMOVED_DEVICE. When using them in other + * case, callers must ensure there will be no racy iommu_release_device() call, + * which otherwise would UAF the dev->iommu_group pointer. + */ +int pci_dev_reset_iommu_prepare(struct pci_dev *pdev) +{ + struct iommu_group *group = pdev->dev.iommu_group; + unsigned long pasid; + void *entry; + int ret; + + if (!pci_ats_supported(pdev) || !dev_has_iommu(&pdev->dev)) + return 0; + + guard(mutex)(&group->mutex); + + /* Re-entry is not allowed */ + if (WARN_ON(group->resetting_domain)) + return -EBUSY; + + ret = __iommu_group_alloc_blocking_domain(group); + if (ret) + return ret; + + /* Stage RID domain at blocking_domain while retaining group->domain */ + if (group->domain != group->blocking_domain) { + ret = __iommu_attach_device(group->blocking_domain, &pdev->dev, + group->domain); + if (ret) + return ret; + } + + /* + * Stage PASID domains at blocking_domain while retaining pasid_array. + * + * The pasid_array is mostly fenced by group->mutex, except one reader + * in iommu_attach_handle_get(), so it's safe to read without xa_lock. + */ + xa_for_each_start(&group->pasid_array, pasid, entry, 1) + iommu_remove_dev_pasid(&pdev->dev, pasid, + pasid_array_entry_to_domain(entry)); + + group->resetting_domain = group->blocking_domain; + return ret; +} +EXPORT_SYMBOL_GPL(pci_dev_reset_iommu_prepare); + +/** + * pci_dev_reset_iommu_done() - Restore IOMMU after a PCI device reset is done + * @pdev: PCI device that has finished a reset routine + * + * After a PCIe device finishes a reset routine, it wants to restore its IOMMU + * IOMMU activity, including new translation as well as cache invalidation, by + * re-attaching all RID/PASID of the device's back to the domains retained in + * the core-level structure. + * + * Caller must pair it with a successful pci_dev_reset_iommu_prepare(). + * + * Note that, although unlikely, there is a risk that re-attaching domains might + * fail due to some unexpected happening like OOM. + */ +void pci_dev_reset_iommu_done(struct pci_dev *pdev) +{ + struct iommu_group *group = pdev->dev.iommu_group; + unsigned long pasid; + void *entry; + + if (!pci_ats_supported(pdev) || !dev_has_iommu(&pdev->dev)) + return; + + guard(mutex)(&group->mutex); + + /* pci_dev_reset_iommu_prepare() was bypassed for the device */ + if (!group->resetting_domain) + return; + + /* pci_dev_reset_iommu_prepare() was not successfully called */ + if (WARN_ON(!group->blocking_domain)) + return; + + /* Re-attach RID domain back to group->domain */ + if (group->domain != group->blocking_domain) { + WARN_ON(__iommu_attach_device(group->domain, &pdev->dev, + group->blocking_domain)); + } + + /* + * Re-attach PASID domains back to the domains retained in pasid_array. + * + * The pasid_array is mostly fenced by group->mutex, except one reader + * in iommu_attach_handle_get(), so it's safe to read without xa_lock. + */ + xa_for_each_start(&group->pasid_array, pasid, entry, 1) + WARN_ON(__iommu_set_group_pasid( + pasid_array_entry_to_domain(entry), group, pasid, + group->blocking_domain)); + + group->resetting_domain = NULL; +} +EXPORT_SYMBOL_GPL(pci_dev_reset_iommu_done); + #if IS_ENABLED(CONFIG_IRQ_MSI_IOMMU) /** * iommu_dma_prepare_msi() - Map the MSI page in the IOMMU domain diff --git a/include/linux/iommu.h b/include/linux/iommu.h index ff097df318b9..54b8b48c762e 100644 --- a/include/linux/iommu.h +++ b/include/linux/iommu.h @@ -1188,6 +1188,10 @@ void iommu_detach_device_pasid(struct iommu_domain *domain, struct device *dev, ioasid_t pasid); ioasid_t iommu_alloc_global_pasid(struct device *dev); void iommu_free_global_pasid(ioasid_t pasid); + +/* PCI device reset functions */ +int pci_dev_reset_iommu_prepare(struct pci_dev *pdev); +void pci_dev_reset_iommu_done(struct pci_dev *pdev); #else /* CONFIG_IOMMU_API */ struct iommu_ops {}; @@ -1511,6 +1515,15 @@ static inline ioasid_t iommu_alloc_global_pasid(struct device *dev) } static inline void iommu_free_global_pasid(ioasid_t pasid) {} + +static inline int pci_dev_reset_iommu_prepare(struct pci_dev *pdev) +{ + return 0; +} + +static inline void pci_dev_reset_iommu_done(struct pci_dev *pdev) +{ +} #endif /* CONFIG_IOMMU_API */ #ifdef CONFIG_IRQ_MSI_IOMMU diff --git a/include/uapi/linux/vfio.h b/include/uapi/linux/vfio.h index ac2329f24141..bb7b89330d35 100644 --- a/include/uapi/linux/vfio.h +++ b/include/uapi/linux/vfio.h @@ -964,6 +964,10 @@ struct vfio_device_bind_iommufd { * hwpt corresponding to the given pt_id. * * Return: 0 on success, -errno on failure. + * + * When a device is resetting, -EBUSY will be returned to reject any concurrent + * attachment to the resetting device itself or any sibling device in the IOMMU + * group having the resetting device. */ struct vfio_device_attach_iommufd_pt { __u32 argsz; -- cgit v1.2.3 From bc87b14594e30720a5c1546c24e0f5f08d34eb40 Mon Sep 17 00:00:00 2001 From: Michael Chan Date: Thu, 8 Jan 2026 10:35:21 -0800 Subject: bnxt_en: Implement ethtool_ops -> get_link_ext_state() Map the link_down_reason from the FW to the ethtool link_ext_state when it is available. Also log it to the link down dmesg when it is available. Add 2 new link_ext_state enums to the UAPI: ETHTOOL_LINK_EXT_STATE_OTP_SPEED_VIOLATION ETHTOOL_LINK_EXT_STATE_BMC_REQUEST_DOWN to cover OTP (one-time-programmable) speed restrictions and BMC (Baseboard management controller) forcing the link down. Reviewed-by: Andy Gospodarek Reviewed-by: Kalesh AP Reviewed-by: Pavan Chebbi Signed-off-by: Michael Chan Link: https://patch.msgid.link/20260108183521.215610-7-michael.chan@broadcom.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 25 +++++++++++++++- drivers/net/ethernet/broadcom/bnxt/bnxt.h | 1 + drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c | 35 +++++++++++++++++++++++ include/uapi/linux/ethtool.h | 2 ++ 4 files changed, 62 insertions(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index 9902babd82cb..cb78614d4108 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -11915,6 +11915,26 @@ static char *bnxt_report_fec(struct bnxt_link_info *link_info) } } +static char *bnxt_link_down_reason(struct bnxt_link_info *link_info) +{ + u8 reason = link_info->link_down_reason; + + /* Multiple bits can be set, we report 1 bit only in order of + * priority. + */ + if (reason & PORT_PHY_QCFG_RESP_LINK_DOWN_REASON_RF) + return "(Remote fault)"; + if (reason & PORT_PHY_QCFG_RESP_LINK_DOWN_REASON_OTP_SPEED_VIOLATION) + return "(OTP Speed limit violation)"; + if (reason & PORT_PHY_QCFG_RESP_LINK_DOWN_REASON_CABLE_REMOVED) + return "(Cable removed)"; + if (reason & PORT_PHY_QCFG_RESP_LINK_DOWN_REASON_MODULE_FAULT) + return "(Module fault)"; + if (reason & PORT_PHY_QCFG_RESP_LINK_DOWN_REASON_BMC_REQUEST) + return "(BMC request down)"; + return ""; +} + void bnxt_report_link(struct bnxt *bp) { if (BNXT_LINK_IS_UP(bp)) { @@ -11972,8 +11992,10 @@ void bnxt_report_link(struct bnxt *bp) (fec & BNXT_FEC_AUTONEG) ? "on" : "off", bnxt_report_fec(&bp->link_info)); } else { + char *str = bnxt_link_down_reason(&bp->link_info); + netif_carrier_off(bp->dev); - netdev_err(bp->dev, "NIC Link is Down\n"); + netdev_err(bp->dev, "NIC Link is Down %s\n", str); } } @@ -12173,6 +12195,7 @@ int bnxt_update_link(struct bnxt *bp, bool chng_link_state) link_info->phy_addr = resp->eee_config_phy_addr & PORT_PHY_QCFG_RESP_PHY_ADDR_MASK; link_info->module_status = resp->module_status; + link_info->link_down_reason = resp->link_down_reason; if (bp->phy_flags & BNXT_PHY_FL_EEE_CAP) { struct ethtool_keee *eee = &bp->eee; diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.h b/drivers/net/ethernet/broadcom/bnxt/bnxt.h index 3afd1d5e364a..e441a002ddef 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.h +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.h @@ -1553,6 +1553,7 @@ struct bnxt_link_info { #define BNXT_LINK_STATE_DOWN 1 #define BNXT_LINK_STATE_UP 2 #define BNXT_LINK_IS_UP(bp) ((bp)->link_info.link_state == BNXT_LINK_STATE_UP) + u8 link_down_reason; u8 active_lanes; u8 duplex; #define BNXT_LINK_DUPLEX_HALF PORT_PHY_QCFG_RESP_DUPLEX_STATE_HALF diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c b/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c index 4dfae7b61c76..6b15fedbb16f 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c @@ -3432,6 +3432,40 @@ static u32 bnxt_get_link(struct net_device *dev) return BNXT_LINK_IS_UP(bp); } +static int bnxt_get_link_ext_state(struct net_device *dev, + struct ethtool_link_ext_state_info *info) +{ + struct bnxt *bp = netdev_priv(dev); + u8 reason; + + if (BNXT_LINK_IS_UP(bp)) + return -ENODATA; + + reason = bp->link_info.link_down_reason; + if (reason & PORT_PHY_QCFG_RESP_LINK_DOWN_REASON_RF) { + info->link_ext_state = ETHTOOL_LINK_EXT_STATE_LINK_TRAINING_FAILURE; + info->link_training = ETHTOOL_LINK_EXT_SUBSTATE_LT_REMOTE_FAULT; + return 0; + } + if (reason & PORT_PHY_QCFG_RESP_LINK_DOWN_REASON_CABLE_REMOVED) { + info->link_ext_state = ETHTOOL_LINK_EXT_STATE_NO_CABLE; + return 0; + } + if (reason & PORT_PHY_QCFG_RESP_LINK_DOWN_REASON_OTP_SPEED_VIOLATION) { + info->link_ext_state = ETHTOOL_LINK_EXT_STATE_OTP_SPEED_VIOLATION; + return 0; + } + if (reason & PORT_PHY_QCFG_RESP_LINK_DOWN_REASON_MODULE_FAULT) { + info->link_ext_state = ETHTOOL_LINK_EXT_STATE_MODULE; + return 0; + } + if (reason & PORT_PHY_QCFG_RESP_LINK_DOWN_REASON_BMC_REQUEST) { + info->link_ext_state = ETHTOOL_LINK_EXT_STATE_BMC_REQUEST_DOWN; + return 0; + } + return -ENODATA; +} + int bnxt_hwrm_nvm_get_dev_info(struct bnxt *bp, struct hwrm_nvm_get_dev_info_output *nvm_dev_info) { @@ -5711,6 +5745,7 @@ const struct ethtool_ops bnxt_ethtool_ops = { .get_eeprom = bnxt_get_eeprom, .set_eeprom = bnxt_set_eeprom, .get_link = bnxt_get_link, + .get_link_ext_state = bnxt_get_link_ext_state, .get_link_ext_stats = bnxt_get_link_ext_stats, .get_eee = bnxt_get_eee, .set_eee = bnxt_set_eee, diff --git a/include/uapi/linux/ethtool.h b/include/uapi/linux/ethtool.h index eb7ff2602fbb..5daa8f225b67 100644 --- a/include/uapi/linux/ethtool.h +++ b/include/uapi/linux/ethtool.h @@ -603,6 +603,8 @@ enum ethtool_link_ext_state { ETHTOOL_LINK_EXT_STATE_POWER_BUDGET_EXCEEDED, ETHTOOL_LINK_EXT_STATE_OVERHEAT, ETHTOOL_LINK_EXT_STATE_MODULE, + ETHTOOL_LINK_EXT_STATE_OTP_SPEED_VIOLATION, + ETHTOOL_LINK_EXT_STATE_BMC_REQUEST_DOWN, }; /* More information in addition to ETHTOOL_LINK_EXT_STATE_AUTONEG. */ -- cgit v1.2.3 From 2e4b28c48f88ce9e263957b1d944cf5349952f88 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Sun, 11 Jan 2026 16:53:48 +0100 Subject: treewide: Update email address In a vain attempt to consolidate the email zoo switch everything to the kernel.org account. Signed-off-by: Thomas Gleixner Signed-off-by: Linus Torvalds --- .mailmap | 1 + CREDITS | 2 +- .../ABI/stable/sysfs-kernel-time-aux-clocks | 2 +- Documentation/arch/x86/topology.rst | 2 +- Documentation/core-api/cpu_hotplug.rst | 2 +- Documentation/core-api/genericirq.rst | 2 +- Documentation/core-api/librs.rst | 2 +- .../devicetree/bindings/timer/mrvl,mmp-timer.yaml | 2 +- Documentation/driver-api/mtdnand.rst | 4 +-- .../translations/zh_CN/core-api/cpu_hotplug.rst | 2 +- .../translations/zh_CN/core-api/genericirq.rst | 2 +- MAINTAINERS | 36 +++++++++++----------- arch/sh/kernel/perf_event.c | 2 +- arch/sparc/kernel/perf_event.c | 2 +- arch/x86/events/core.c | 2 +- arch/x86/events/perf_event.h | 2 +- arch/x86/kernel/x86_init.c | 2 +- arch/x86/mm/pti.c | 2 +- drivers/mtd/nand/ecc-sw-hamming.c | 2 +- drivers/mtd/nand/raw/diskonchip.c | 2 +- drivers/mtd/nand/raw/nand_base.c | 4 +-- drivers/mtd/nand/raw/nand_bbt.c | 2 +- drivers/mtd/nand/raw/nand_ids.c | 2 +- drivers/mtd/nand/raw/nand_jedec.c | 2 +- drivers/mtd/nand/raw/nand_legacy.c | 2 +- drivers/mtd/nand/raw/nand_onfi.c | 2 +- drivers/mtd/nand/raw/ndfc.c | 2 +- drivers/uio/uio.c | 2 +- fs/jffs2/wbuf.c | 4 +-- include/linux/hrtimer.h | 2 +- include/linux/ktime.h | 2 +- include/linux/mtd/jedec.h | 2 +- include/linux/mtd/nand-ecc-sw-hamming.h | 2 +- include/linux/mtd/ndfc.h | 2 +- include/linux/mtd/onfi.h | 2 +- include/linux/mtd/platnand.h | 2 +- include/linux/mtd/rawnand.h | 2 +- include/linux/perf_event.h | 2 +- include/linux/plist.h | 2 +- include/linux/rslib.h | 2 +- include/linux/uio_driver.h | 2 +- include/uapi/linux/perf_event.h | 2 +- kernel/events/callchain.c | 2 +- kernel/events/core.c | 2 +- kernel/events/ring_buffer.c | 2 +- kernel/irq/debugfs.c | 2 +- kernel/irq/matrix.c | 2 +- kernel/sched/fair.c | 2 +- kernel/sched/pelt.c | 2 +- kernel/time/clockevents.c | 2 +- kernel/time/hrtimer.c | 2 +- kernel/time/tick-broadcast.c | 2 +- kernel/time/tick-common.c | 2 +- kernel/time/tick-oneshot.c | 2 +- kernel/time/tick-sched.c | 2 +- lib/debugobjects.c | 2 +- lib/plist.c | 2 +- lib/reed_solomon/decode_rs.c | 2 +- lib/reed_solomon/encode_rs.c | 2 +- lib/reed_solomon/reed_solomon.c | 2 +- scripts/spdxcheck.py | 2 +- tools/include/uapi/linux/perf_event.h | 2 +- tools/perf/builtin-list.c | 2 +- 63 files changed, 83 insertions(+), 82 deletions(-) (limited to 'include/uapi/linux') diff --git a/.mailmap b/.mailmap index b23e0853d636..fa018b5bd533 100644 --- a/.mailmap +++ b/.mailmap @@ -801,6 +801,7 @@ Tanzir Hasan Tejun Heo Tomeu Vizoso Thomas Graf +Thomas Gleixner Thomas Körper Thomas Pedersen Thorsten Blum diff --git a/CREDITS b/CREDITS index ca75f110edb6..383809bc4b7a 100644 --- a/CREDITS +++ b/CREDITS @@ -1398,7 +1398,7 @@ D: SRM environment driver (for Alpha systems) P: 1024D/8399E1BB 250D 3BCF 7127 0D8C A444 A961 1DBD 5E75 8399 E1BB N: Thomas Gleixner -E: tglx@linutronix.de +E: tglx@kernel.org D: NAND flash hardware support, JFFS2 on NAND flash N: Jérôme Glisse diff --git a/Documentation/ABI/stable/sysfs-kernel-time-aux-clocks b/Documentation/ABI/stable/sysfs-kernel-time-aux-clocks index 825508f42af6..e1a894c8dd1b 100644 --- a/Documentation/ABI/stable/sysfs-kernel-time-aux-clocks +++ b/Documentation/ABI/stable/sysfs-kernel-time-aux-clocks @@ -1,5 +1,5 @@ What: /sys/kernel/time/aux_clocks//enable Date: May 2025 -Contact: Thomas Gleixner +Contact: Thomas Gleixner Description: Controls the enablement of auxiliary clock timekeepers. diff --git a/Documentation/arch/x86/topology.rst b/Documentation/arch/x86/topology.rst index 86bec8ac2c4d..f779a68875c5 100644 --- a/Documentation/arch/x86/topology.rst +++ b/Documentation/arch/x86/topology.rst @@ -17,7 +17,7 @@ with the generic one and look at this one in parallel for the x86 specifics. Needless to say, code should use the generic functions - this file is *only* here to *document* the inner workings of x86 topology. -Started by Thomas Gleixner and Borislav Petkov . +Started by Thomas Gleixner and Borislav Petkov . The main aim of the topology facilities is to present adequate interfaces to code which needs to know/query/use the structure of the running system wrt diff --git a/Documentation/core-api/cpu_hotplug.rst b/Documentation/core-api/cpu_hotplug.rst index e1b0eeabbb5e..9b4afca9fd09 100644 --- a/Documentation/core-api/cpu_hotplug.rst +++ b/Documentation/core-api/cpu_hotplug.rst @@ -8,7 +8,7 @@ CPU hotplug in the Kernel Srivatsa Vaddagiri , Ashok Raj , Joel Schopp , - Thomas Gleixner + Thomas Gleixner Introduction ============ diff --git a/Documentation/core-api/genericirq.rst b/Documentation/core-api/genericirq.rst index 582bde9bf5a9..b16d751d4b98 100644 --- a/Documentation/core-api/genericirq.rst +++ b/Documentation/core-api/genericirq.rst @@ -439,6 +439,6 @@ Credits The following people have contributed to this document: -1. Thomas Gleixner tglx@linutronix.de +1. Thomas Gleixner tglx@kernel.org 2. Ingo Molnar mingo@elte.hu diff --git a/Documentation/core-api/librs.rst b/Documentation/core-api/librs.rst index 6010f5bc5bf9..0d88893dbc03 100644 --- a/Documentation/core-api/librs.rst +++ b/Documentation/core-api/librs.rst @@ -209,4 +209,4 @@ testing. Thanks a lot. The following people have contributed to this document: -Thomas Gleixner\ tglx@linutronix.de +Thomas Gleixner\ tglx@kernel.org diff --git a/Documentation/devicetree/bindings/timer/mrvl,mmp-timer.yaml b/Documentation/devicetree/bindings/timer/mrvl,mmp-timer.yaml index fe6bc4173789..0643cfcc6bc7 100644 --- a/Documentation/devicetree/bindings/timer/mrvl,mmp-timer.yaml +++ b/Documentation/devicetree/bindings/timer/mrvl,mmp-timer.yaml @@ -8,7 +8,7 @@ title: Marvell MMP Timer maintainers: - Daniel Lezcano - - Thomas Gleixner + - Thomas Gleixner - Rob Herring properties: diff --git a/Documentation/driver-api/mtdnand.rst b/Documentation/driver-api/mtdnand.rst index ce77e024c4f1..adf03983f1ba 100644 --- a/Documentation/driver-api/mtdnand.rst +++ b/Documentation/driver-api/mtdnand.rst @@ -996,11 +996,11 @@ The following people have contributed to the NAND driver: 2. David Woodhouse\ dwmw2@infradead.org -3. Thomas Gleixner\ tglx@linutronix.de +3. Thomas Gleixner\ tglx@kernel.org A lot of users have provided bugfixes, improvements and helping hands for testing. Thanks a lot. The following people have contributed to this document: -1. Thomas Gleixner\ tglx@linutronix.de +1. Thomas Gleixner\ tglx@kernel.org diff --git a/Documentation/translations/zh_CN/core-api/cpu_hotplug.rst b/Documentation/translations/zh_CN/core-api/cpu_hotplug.rst index bc0d7ea6d834..3447fbf0e695 100644 --- a/Documentation/translations/zh_CN/core-api/cpu_hotplug.rst +++ b/Documentation/translations/zh_CN/core-api/cpu_hotplug.rst @@ -22,7 +22,7 @@ Srivatsa Vaddagiri , Ashok Raj , Joel Schopp , - Thomas Gleixner + Thomas Gleixner 简介 ==== diff --git a/Documentation/translations/zh_CN/core-api/genericirq.rst b/Documentation/translations/zh_CN/core-api/genericirq.rst index 05ccb954c18d..d2c1bd94bb97 100644 --- a/Documentation/translations/zh_CN/core-api/genericirq.rst +++ b/Documentation/translations/zh_CN/core-api/genericirq.rst @@ -404,6 +404,6 @@ kernel/irq/chip.c 感谢以下人士对本文档作出的贡献: -1. Thomas Gleixner tglx@linutronix.de +1. Thomas Gleixner tglx@kernel.org 2. Ingo Molnar mingo@elte.hu diff --git a/MAINTAINERS b/MAINTAINERS index 32b5e41d9849..ee036e0a3ef6 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6175,7 +6175,7 @@ F: include/linux/clk.h CLOCKSOURCE, CLOCKEVENT DRIVERS M: Daniel Lezcano -M: Thomas Gleixner +M: Thomas Gleixner L: linux-kernel@vger.kernel.org S: Supported T: git git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git timers/core @@ -6541,7 +6541,7 @@ S: Maintained F: drivers/cpufreq/virtual-cpufreq.c CPU HOTPLUG -M: Thomas Gleixner +M: Thomas Gleixner M: Peter Zijlstra L: linux-kernel@vger.kernel.org S: Maintained @@ -6968,7 +6968,7 @@ F: Documentation/scsi/dc395x.rst F: drivers/scsi/dc395x.* DEBUGOBJECTS: -M: Thomas Gleixner +M: Thomas Gleixner L: linux-kernel@vger.kernel.org S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git core/debugobjects @@ -10371,7 +10371,7 @@ F: include/uapi/linux/fuse.h F: tools/testing/selftests/filesystems/fuse/ FUTEX SUBSYSTEM -M: Thomas Gleixner +M: Thomas Gleixner M: Ingo Molnar R: Peter Zijlstra R: Darren Hart @@ -10515,7 +10515,7 @@ F: drivers/base/arch_topology.c F: include/linux/arch_topology.h GENERIC ENTRY CODE -M: Thomas Gleixner +M: Thomas Gleixner M: Peter Zijlstra M: Andy Lutomirski L: linux-kernel@vger.kernel.org @@ -10628,7 +10628,7 @@ F: drivers/uio/uio_pci_generic.c GENERIC VDSO LIBRARY M: Andy Lutomirski -M: Thomas Gleixner +M: Thomas Gleixner M: Vincenzo Frascino L: linux-kernel@vger.kernel.org S: Maintained @@ -11241,7 +11241,7 @@ F: drivers/hid/hid-logitech-hidpp.c HIGH-RESOLUTION TIMERS, TIMER WHEEL, CLOCKEVENTS M: Anna-Maria Behnsen M: Frederic Weisbecker -M: Thomas Gleixner +M: Thomas Gleixner L: linux-kernel@vger.kernel.org S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git timers/core @@ -11264,7 +11264,7 @@ R: Boqun Feng R: FUJITA Tomonori R: Frederic Weisbecker R: Lyude Paul -R: Thomas Gleixner +R: Thomas Gleixner R: Anna-Maria Behnsen R: John Stultz R: Stephen Boyd @@ -13334,7 +13334,7 @@ F: Documentation/devicetree/bindings/sound/irondevice,* F: sound/soc/codecs/sma* IRQ DOMAINS (IRQ NUMBER MAPPING LIBRARY) -M: Thomas Gleixner +M: Thomas Gleixner S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git irq/core F: Documentation/core-api/irq/irq-domain.rst @@ -13344,7 +13344,7 @@ F: kernel/irq/irqdomain.c F: kernel/irq/msi.c IRQ SUBSYSTEM -M: Thomas Gleixner +M: Thomas Gleixner L: linux-kernel@vger.kernel.org S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git irq/core @@ -13357,7 +13357,7 @@ F: kernel/irq/ F: lib/group_cpus.c IRQCHIP DRIVERS -M: Thomas Gleixner +M: Thomas Gleixner L: linux-kernel@vger.kernel.org S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git irq/core @@ -14451,7 +14451,7 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm.git mm-nonmm-unstab F: lib/* LICENSES and SPDX stuff -M: Thomas Gleixner +M: Thomas Gleixner M: Greg Kroah-Hartman L: linux-spdx@vger.kernel.org S: Maintained @@ -18576,7 +18576,7 @@ NOHZ, DYNTICKS SUPPORT M: Anna-Maria Behnsen M: Frederic Weisbecker M: Ingo Molnar -M: Thomas Gleixner +M: Thomas Gleixner L: linux-kernel@vger.kernel.org S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git timers/nohz @@ -20761,7 +20761,7 @@ F: drivers/platform/x86/portwell-ec.c POSIX CLOCKS and TIMERS M: Anna-Maria Behnsen M: Frederic Weisbecker -M: Thomas Gleixner +M: Thomas Gleixner L: linux-kernel@vger.kernel.org S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git timers/core @@ -26272,7 +26272,7 @@ F: drivers/net/wireless/ti/ TIMEKEEPING, CLOCKSOURCE CORE, NTP, ALARMTIMER M: John Stultz -M: Thomas Gleixner +M: Thomas Gleixner R: Stephen Boyd L: linux-kernel@vger.kernel.org S: Supported @@ -28203,7 +28203,7 @@ F: net/lapb/ F: net/x25/ X86 ARCHITECTURE (32-BIT AND 64-BIT) -M: Thomas Gleixner +M: Thomas Gleixner M: Ingo Molnar M: Borislav Petkov M: Dave Hansen @@ -28219,7 +28219,7 @@ F: tools/testing/selftests/x86 X86 CPUID DATABASE M: Borislav Petkov -M: Thomas Gleixner +M: Thomas Gleixner M: x86@kernel.org R: Ahmed S. Darwish L: x86-cpuid@lists.linux.dev @@ -28235,7 +28235,7 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git x86/asm F: arch/x86/entry/ X86 HARDWARE VULNERABILITIES -M: Thomas Gleixner +M: Thomas Gleixner M: Borislav Petkov M: Peter Zijlstra M: Josh Poimboeuf diff --git a/arch/sh/kernel/perf_event.c b/arch/sh/kernel/perf_event.c index 1d2507f22437..1fbb7d46e484 100644 --- a/arch/sh/kernel/perf_event.c +++ b/arch/sh/kernel/perf_event.c @@ -7,7 +7,7 @@ * Heavily based on the x86 and PowerPC implementations. * * x86: - * Copyright (C) 2008 Thomas Gleixner + * Copyright (C) 2008 Linutronix GmbH, Thomas Gleixner * Copyright (C) 2008-2009 Red Hat, Inc., Ingo Molnar * Copyright (C) 2009 Jaswinder Singh Rajput * Copyright (C) 2009 Advanced Micro Devices, Inc., Robert Richter diff --git a/arch/sparc/kernel/perf_event.c b/arch/sparc/kernel/perf_event.c index cae4d33002a5..0ce4ae343531 100644 --- a/arch/sparc/kernel/perf_event.c +++ b/arch/sparc/kernel/perf_event.c @@ -6,7 +6,7 @@ * This code is based almost entirely upon the x86 perf event * code, which is: * - * Copyright (C) 2008 Thomas Gleixner + * Copyright (C) 2008 Linutronix GmbH, Thomas Gleixner * Copyright (C) 2008-2009 Red Hat, Inc., Ingo Molnar * Copyright (C) 2009 Jaswinder Singh Rajput * Copyright (C) 2009 Advanced Micro Devices, Inc., Robert Richter diff --git a/arch/x86/events/core.c b/arch/x86/events/core.c index 0c38a31d5fc7..576baa9a52c5 100644 --- a/arch/x86/events/core.c +++ b/arch/x86/events/core.c @@ -1,7 +1,7 @@ /* * Performance events x86 architecture code * - * Copyright (C) 2008 Thomas Gleixner + * Copyright (C) 2008 Linutronix GmbH, Thomas Gleixner * Copyright (C) 2008-2009 Red Hat, Inc., Ingo Molnar * Copyright (C) 2009 Jaswinder Singh Rajput * Copyright (C) 2009 Advanced Micro Devices, Inc., Robert Richter diff --git a/arch/x86/events/perf_event.h b/arch/x86/events/perf_event.h index 3161ec0a3416..62963022b517 100644 --- a/arch/x86/events/perf_event.h +++ b/arch/x86/events/perf_event.h @@ -1,7 +1,7 @@ /* * Performance events x86 architecture header * - * Copyright (C) 2008 Thomas Gleixner + * Copyright (C) 2008 Linutronix GmbH, Thomas Gleixner * Copyright (C) 2008-2009 Red Hat, Inc., Ingo Molnar * Copyright (C) 2009 Jaswinder Singh Rajput * Copyright (C) 2009 Advanced Micro Devices, Inc., Robert Richter diff --git a/arch/x86/kernel/x86_init.c b/arch/x86/kernel/x86_init.c index 0a2bbd674a6d..ebefb77c37bb 100644 --- a/arch/x86/kernel/x86_init.c +++ b/arch/x86/kernel/x86_init.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009 Thomas Gleixner + * Copyright (C) 2009 Linutronix GmbH, Thomas Gleixner * * For licencing details see kernel-base/COPYING */ diff --git a/arch/x86/mm/pti.c b/arch/x86/mm/pti.c index b10d4d131dce..f7546e9e8e89 100644 --- a/arch/x86/mm/pti.c +++ b/arch/x86/mm/pti.c @@ -15,7 +15,7 @@ * Signed-off-by: Michael Schwarz * * Major changes to the original code by: Dave Hansen - * Mostly rewritten by Thomas Gleixner and + * Mostly rewritten by Thomas Gleixner and * Andy Lutomirsky */ #include diff --git a/drivers/mtd/nand/ecc-sw-hamming.c b/drivers/mtd/nand/ecc-sw-hamming.c index f2d0effad9d2..bc62a71f9fdd 100644 --- a/drivers/mtd/nand/ecc-sw-hamming.c +++ b/drivers/mtd/nand/ecc-sw-hamming.c @@ -8,7 +8,7 @@ * * Completely replaces the previous ECC implementation which was written by: * Steven J. Hill (sjhill@realitydiluted.com) - * Thomas Gleixner (tglx@linutronix.de) + * Thomas Gleixner (tglx@kernel.org) * * Information on how this algorithm works and how it was developed * can be found in Documentation/driver-api/mtd/nand_ecc.rst diff --git a/drivers/mtd/nand/raw/diskonchip.c b/drivers/mtd/nand/raw/diskonchip.c index 70d6c2250f32..540b6baf8bb1 100644 --- a/drivers/mtd/nand/raw/diskonchip.c +++ b/drivers/mtd/nand/raw/diskonchip.c @@ -11,7 +11,7 @@ * Error correction code lifted from the old docecc code * Author: Fabrice Bellard (fabrice.bellard@netgem.com) * Copyright (C) 2000 Netgem S.A. - * converted to the generic Reed-Solomon library by Thomas Gleixner + * converted to the generic Reed-Solomon library by Thomas Gleixner * * Interface to generic NAND code for M-Systems DiskOnChip devices */ diff --git a/drivers/mtd/nand/raw/nand_base.c b/drivers/mtd/nand/raw/nand_base.c index ad6d66309597..f2322de93ab4 100644 --- a/drivers/mtd/nand/raw/nand_base.c +++ b/drivers/mtd/nand/raw/nand_base.c @@ -8,7 +8,7 @@ * http://www.linux-mtd.infradead.org/doc/nand.html * * Copyright (C) 2000 Steven J. Hill (sjhill@realitydiluted.com) - * 2002-2006 Thomas Gleixner (tglx@linutronix.de) + * 2002-2006 Thomas Gleixner (tglx@kernel.org) * * Credits: * David Woodhouse for adding multichip support @@ -6594,5 +6594,5 @@ EXPORT_SYMBOL_GPL(nand_cleanup); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Steven J. Hill "); -MODULE_AUTHOR("Thomas Gleixner "); +MODULE_AUTHOR("Thomas Gleixner "); MODULE_DESCRIPTION("Generic NAND flash driver code"); diff --git a/drivers/mtd/nand/raw/nand_bbt.c b/drivers/mtd/nand/raw/nand_bbt.c index a8fba5f39f59..3050ab7e6eb6 100644 --- a/drivers/mtd/nand/raw/nand_bbt.c +++ b/drivers/mtd/nand/raw/nand_bbt.c @@ -3,7 +3,7 @@ * Overview: * Bad block table support for the NAND driver * - * Copyright © 2004 Thomas Gleixner (tglx@linutronix.de) + * Copyright © 2004 Thomas Gleixner (tglx@kernel.org) * * Description: * diff --git a/drivers/mtd/nand/raw/nand_ids.c b/drivers/mtd/nand/raw/nand_ids.c index 650351c62af6..62a8cf86d9e2 100644 --- a/drivers/mtd/nand/raw/nand_ids.c +++ b/drivers/mtd/nand/raw/nand_ids.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (C) 2002 Thomas Gleixner (tglx@linutronix.de) + * Copyright (C) 2002 Thomas Gleixner (tglx@kernel.org) */ #include diff --git a/drivers/mtd/nand/raw/nand_jedec.c b/drivers/mtd/nand/raw/nand_jedec.c index b3cc8f360529..89e6dd8ed1a8 100644 --- a/drivers/mtd/nand/raw/nand_jedec.c +++ b/drivers/mtd/nand/raw/nand_jedec.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2000 Steven J. Hill (sjhill@realitydiluted.com) - * 2002-2006 Thomas Gleixner (tglx@linutronix.de) + * 2002-2006 Thomas Gleixner (tglx@kernel.org) * * Credits: * David Woodhouse for adding multichip support diff --git a/drivers/mtd/nand/raw/nand_legacy.c b/drivers/mtd/nand/raw/nand_legacy.c index 743792edf98d..97700f80d5b8 100644 --- a/drivers/mtd/nand/raw/nand_legacy.c +++ b/drivers/mtd/nand/raw/nand_legacy.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2000 Steven J. Hill (sjhill@realitydiluted.com) - * 2002-2006 Thomas Gleixner (tglx@linutronix.de) + * 2002-2006 Thomas Gleixner (tglx@kernel.org) * * Credits: * David Woodhouse for adding multichip support diff --git a/drivers/mtd/nand/raw/nand_onfi.c b/drivers/mtd/nand/raw/nand_onfi.c index 861975e44b55..11954440e4de 100644 --- a/drivers/mtd/nand/raw/nand_onfi.c +++ b/drivers/mtd/nand/raw/nand_onfi.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2000 Steven J. Hill (sjhill@realitydiluted.com) - * 2002-2006 Thomas Gleixner (tglx@linutronix.de) + * 2002-2006 Thomas Gleixner (tglx@kernel.org) * * Credits: * David Woodhouse for adding multichip support diff --git a/drivers/mtd/nand/raw/ndfc.c b/drivers/mtd/nand/raw/ndfc.c index 13365128194d..7ad8bc04be1a 100644 --- a/drivers/mtd/nand/raw/ndfc.c +++ b/drivers/mtd/nand/raw/ndfc.c @@ -272,5 +272,5 @@ static struct platform_driver ndfc_driver = { module_platform_driver(ndfc_driver); MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Thomas Gleixner "); +MODULE_AUTHOR("Thomas Gleixner "); MODULE_DESCRIPTION("OF Platform driver for NDFC"); diff --git a/drivers/uio/uio.c b/drivers/uio/uio.c index d93ed4e86a17..fa0d4e6aee16 100644 --- a/drivers/uio/uio.c +++ b/drivers/uio/uio.c @@ -3,7 +3,7 @@ * drivers/uio/uio.c * * Copyright(C) 2005, Benedikt Spranger - * Copyright(C) 2005, Thomas Gleixner + * Copyright(C) 2005, Linutronix GmbH, Thomas Gleixner * Copyright(C) 2006, Hans J. Koch * Copyright(C) 2006, Greg Kroah-Hartman * diff --git a/fs/jffs2/wbuf.c b/fs/jffs2/wbuf.c index bb815a002984..3ab3f0ff7ebb 100644 --- a/fs/jffs2/wbuf.c +++ b/fs/jffs2/wbuf.c @@ -2,10 +2,10 @@ * JFFS2 -- Journalling Flash File System, Version 2. * * Copyright © 2001-2007 Red Hat, Inc. - * Copyright © 2004 Thomas Gleixner + * Copyright © 2004 Thomas Gleixner * * Created by David Woodhouse - * Modified debugged and enhanced by Thomas Gleixner + * Modified debugged and enhanced by Thomas Gleixner * * For licensing information, see the file 'LICENCE' in this directory. * diff --git a/include/linux/hrtimer.h b/include/linux/hrtimer.h index 2cf1bf65b225..0de12f14d6a4 100644 --- a/include/linux/hrtimer.h +++ b/include/linux/hrtimer.h @@ -2,7 +2,7 @@ /* * hrtimers - High-resolution kernel timers * - * Copyright(C) 2005, Thomas Gleixner + * Copyright(C) 2005, Linutronix GmbH, Thomas Gleixner * Copyright(C) 2005, Red Hat, Inc., Ingo Molnar * * data type definitions, declarations, prototypes diff --git a/include/linux/ktime.h b/include/linux/ktime.h index 383ed9985802..f247e564602f 100644 --- a/include/linux/ktime.h +++ b/include/linux/ktime.h @@ -3,7 +3,7 @@ * * ktime_t - nanosecond-resolution time format. * - * Copyright(C) 2005, Thomas Gleixner + * Copyright(C) 2005, Linutronix GmbH, Thomas Gleixner * Copyright(C) 2005, Red Hat, Inc., Ingo Molnar * * data type definitions, declarations, prototypes and macros. diff --git a/include/linux/mtd/jedec.h b/include/linux/mtd/jedec.h index 56047a4e54c9..255972f3d88d 100644 --- a/include/linux/mtd/jedec.h +++ b/include/linux/mtd/jedec.h @@ -2,7 +2,7 @@ /* * Copyright © 2000-2010 David Woodhouse * Steven J. Hill - * Thomas Gleixner + * Thomas Gleixner * * Contains all JEDEC related definitions */ diff --git a/include/linux/mtd/nand-ecc-sw-hamming.h b/include/linux/mtd/nand-ecc-sw-hamming.h index c6c71894c575..2aa2f8ef68d2 100644 --- a/include/linux/mtd/nand-ecc-sw-hamming.h +++ b/include/linux/mtd/nand-ecc-sw-hamming.h @@ -2,7 +2,7 @@ /* * Copyright (C) 2000-2010 Steven J. Hill * David Woodhouse - * Thomas Gleixner + * Thomas Gleixner * * This file is the header for the NAND Hamming ECC implementation. */ diff --git a/include/linux/mtd/ndfc.h b/include/linux/mtd/ndfc.h index 98f075b86931..622891191e9c 100644 --- a/include/linux/mtd/ndfc.h +++ b/include/linux/mtd/ndfc.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* - * Copyright (c) 2006 Thomas Gleixner + * Copyright (c) 2006 Linutronix GmbH, Thomas Gleixner * * Info: * Contains defines, datastructures for ndfc nand controller diff --git a/include/linux/mtd/onfi.h b/include/linux/mtd/onfi.h index 55ab2e4d62f9..09a5cbd8f232 100644 --- a/include/linux/mtd/onfi.h +++ b/include/linux/mtd/onfi.h @@ -2,7 +2,7 @@ /* * Copyright © 2000-2010 David Woodhouse * Steven J. Hill - * Thomas Gleixner + * Thomas Gleixner * * Contains all ONFI related definitions */ diff --git a/include/linux/mtd/platnand.h b/include/linux/mtd/platnand.h index bc11eb6b593b..2df6fba699f2 100644 --- a/include/linux/mtd/platnand.h +++ b/include/linux/mtd/platnand.h @@ -2,7 +2,7 @@ /* * Copyright © 2000-2010 David Woodhouse * Steven J. Hill - * Thomas Gleixner + * Thomas Gleixner * * Contains all platform NAND related definitions. */ diff --git a/include/linux/mtd/rawnand.h b/include/linux/mtd/rawnand.h index d30bdc3fcfd7..5c70e7bd3ed5 100644 --- a/include/linux/mtd/rawnand.h +++ b/include/linux/mtd/rawnand.h @@ -2,7 +2,7 @@ /* * Copyright © 2000-2010 David Woodhouse * Steven J. Hill - * Thomas Gleixner + * Thomas Gleixner * * Info: * Contains standard defines and IDs for NAND flash devices diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h index 9870d768db4c..9ded2e582c60 100644 --- a/include/linux/perf_event.h +++ b/include/linux/perf_event.h @@ -1,7 +1,7 @@ /* * Performance events: * - * Copyright (C) 2008-2009, Thomas Gleixner + * Copyright (C) 2008-2009, Linutronix GmbH, Thomas Gleixner * Copyright (C) 2008-2011, Red Hat, Inc., Ingo Molnar * Copyright (C) 2008-2011, Red Hat, Inc., Peter Zijlstra * diff --git a/include/linux/plist.h b/include/linux/plist.h index 8c1c8adf7fe9..16cf4355b5c1 100644 --- a/include/linux/plist.h +++ b/include/linux/plist.h @@ -8,7 +8,7 @@ * 2001-2005 (c) MontaVista Software, Inc. * Daniel Walker * - * (C) 2005 Thomas Gleixner + * (C) 2005 Linutronix GmbH, Thomas Gleixner * * Simplifications of the original code by * Oleg Nesterov diff --git a/include/linux/rslib.h b/include/linux/rslib.h index a04dacbdc8ae..a2848f6907e3 100644 --- a/include/linux/rslib.h +++ b/include/linux/rslib.h @@ -2,7 +2,7 @@ /* * Generic Reed Solomon encoder / decoder library * - * Copyright (C) 2004 Thomas Gleixner (tglx@linutronix.de) + * Copyright (C) 2004 Thomas Gleixner (tglx@kernel.org) * * RS code lifted from reed solomon library written by Phil Karn * Copyright 2002 Phil Karn, KA9Q diff --git a/include/linux/uio_driver.h b/include/linux/uio_driver.h index 18238dc8bfd3..334641e20fb1 100644 --- a/include/linux/uio_driver.h +++ b/include/linux/uio_driver.h @@ -3,7 +3,7 @@ * include/linux/uio_driver.h * * Copyright(C) 2005, Benedikt Spranger - * Copyright(C) 2005, Thomas Gleixner + * Copyright(C) 2005, Linutronix GmbH, Thomas Gleixner * Copyright(C) 2006, Hans J. Koch * Copyright(C) 2006, Greg Kroah-Hartman * diff --git a/include/uapi/linux/perf_event.h b/include/uapi/linux/perf_event.h index c44a8fb3e418..72f03153dd32 100644 --- a/include/uapi/linux/perf_event.h +++ b/include/uapi/linux/perf_event.h @@ -2,7 +2,7 @@ /* * Performance events: * - * Copyright (C) 2008-2009, Thomas Gleixner + * Copyright (C) 2008-2009, Linutronix GmbH, Thomas Gleixner * Copyright (C) 2008-2011, Red Hat, Inc., Ingo Molnar * Copyright (C) 2008-2011, Red Hat, Inc., Peter Zijlstra * diff --git a/kernel/events/callchain.c b/kernel/events/callchain.c index b9c7e00725d6..1f6589578703 100644 --- a/kernel/events/callchain.c +++ b/kernel/events/callchain.c @@ -2,7 +2,7 @@ /* * Performance events callchain code, extracted from core.c: * - * Copyright (C) 2008 Thomas Gleixner + * Copyright (C) 2008 Linutronix GmbH, Thomas Gleixner * Copyright (C) 2008-2011 Red Hat, Inc., Ingo Molnar * Copyright (C) 2008-2011 Red Hat, Inc., Peter Zijlstra * Copyright © 2009 Paul Mackerras, IBM Corp. diff --git a/kernel/events/core.c b/kernel/events/core.c index dad0d3d2e85f..f5e9d30e4fa9 100644 --- a/kernel/events/core.c +++ b/kernel/events/core.c @@ -2,7 +2,7 @@ /* * Performance events core code: * - * Copyright (C) 2008 Thomas Gleixner + * Copyright (C) 2008 Linutronix GmbH, Thomas Gleixner * Copyright (C) 2008-2011 Red Hat, Inc., Ingo Molnar * Copyright (C) 2008-2011 Red Hat, Inc., Peter Zijlstra * Copyright © 2009 Paul Mackerras, IBM Corp. diff --git a/kernel/events/ring_buffer.c b/kernel/events/ring_buffer.c index 20a905023736..3e7de2661417 100644 --- a/kernel/events/ring_buffer.c +++ b/kernel/events/ring_buffer.c @@ -2,7 +2,7 @@ /* * Performance events ring-buffer code: * - * Copyright (C) 2008 Thomas Gleixner + * Copyright (C) 2008 Linutronix GmbH, Thomas Gleixner * Copyright (C) 2008-2011 Red Hat, Inc., Ingo Molnar * Copyright (C) 2008-2011 Red Hat, Inc., Peter Zijlstra * Copyright © 2009 Paul Mackerras, IBM Corp. diff --git a/kernel/irq/debugfs.c b/kernel/irq/debugfs.c index 3527defd2890..5c5ebaee35f2 100644 --- a/kernel/irq/debugfs.c +++ b/kernel/irq/debugfs.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0 -// Copyright 2017 Thomas Gleixner +// Copyright 2017 Linutronix GmbH, Thomas Gleixner #include #include diff --git a/kernel/irq/matrix.c b/kernel/irq/matrix.c index 8f222d1cccec..a50f2305a8dc 100644 --- a/kernel/irq/matrix.c +++ b/kernel/irq/matrix.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0 -// Copyright (C) 2017 Thomas Gleixner +// Copyright (C) 2017 Linutronix GmbH, Thomas Gleixner #include #include diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index da46c3164537..e71302282671 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -15,7 +15,7 @@ * Author: Srivatsa Vaddagiri * * Scaled math optimizations by Thomas Gleixner - * Copyright (C) 2007, Thomas Gleixner + * Copyright (C) 2007, Linutronix GmbH, Thomas Gleixner * * Adaptive scheduling granularity, math enhancements by Peter Zijlstra * Copyright (C) 2007 Red Hat, Inc., Peter Zijlstra diff --git a/kernel/sched/pelt.c b/kernel/sched/pelt.c index fa83bbaf4f3e..897790889ba3 100644 --- a/kernel/sched/pelt.c +++ b/kernel/sched/pelt.c @@ -15,7 +15,7 @@ * Author: Srivatsa Vaddagiri * * Scaled math optimizations by Thomas Gleixner - * Copyright (C) 2007, Thomas Gleixner + * Copyright (C) 2007, Linutronix GmbH, Thomas Gleixner * * Adaptive scheduling granularity, math enhancements by Peter Zijlstra * Copyright (C) 2007 Red Hat, Inc., Peter Zijlstra diff --git a/kernel/time/clockevents.c b/kernel/time/clockevents.c index a59bc75ab7c5..eaae1ce9f060 100644 --- a/kernel/time/clockevents.c +++ b/kernel/time/clockevents.c @@ -2,7 +2,7 @@ /* * This file contains functions which manage clock event devices. * - * Copyright(C) 2005-2006, Thomas Gleixner + * Copyright(C) 2005-2006, Linutronix GmbH, Thomas Gleixner * Copyright(C) 2005-2007, Red Hat, Inc., Ingo Molnar * Copyright(C) 2006-2007, Timesys Corp., Thomas Gleixner */ diff --git a/kernel/time/hrtimer.c b/kernel/time/hrtimer.c index f8ea8c8fc895..bdb30cc5e873 100644 --- a/kernel/time/hrtimer.c +++ b/kernel/time/hrtimer.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Copyright(C) 2005-2006, Thomas Gleixner + * Copyright(C) 2005-2006, Linutronix GmbH, Thomas Gleixner * Copyright(C) 2005-2007, Red Hat, Inc., Ingo Molnar * Copyright(C) 2006-2007 Timesys Corp., Thomas Gleixner * diff --git a/kernel/time/tick-broadcast.c b/kernel/time/tick-broadcast.c index 0207868c8b4d..f63c65881364 100644 --- a/kernel/time/tick-broadcast.c +++ b/kernel/time/tick-broadcast.c @@ -3,7 +3,7 @@ * This file contains functions which emulate a local clock-event * device via a broadcast event source. * - * Copyright(C) 2005-2006, Thomas Gleixner + * Copyright(C) 2005-2006, Linutronix GmbH, Thomas Gleixner * Copyright(C) 2005-2007, Red Hat, Inc., Ingo Molnar * Copyright(C) 2006-2007, Timesys Corp., Thomas Gleixner */ diff --git a/kernel/time/tick-common.c b/kernel/time/tick-common.c index 7e33d3f2e889..d305d8521896 100644 --- a/kernel/time/tick-common.c +++ b/kernel/time/tick-common.c @@ -3,7 +3,7 @@ * This file contains the base functions to manage periodic tick * related events. * - * Copyright(C) 2005-2006, Thomas Gleixner + * Copyright(C) 2005-2006, Linutronix GmbH, Thomas Gleixner * Copyright(C) 2005-2007, Red Hat, Inc., Ingo Molnar * Copyright(C) 2006-2007, Timesys Corp., Thomas Gleixner */ diff --git a/kernel/time/tick-oneshot.c b/kernel/time/tick-oneshot.c index ffee943d796d..7472597f3225 100644 --- a/kernel/time/tick-oneshot.c +++ b/kernel/time/tick-oneshot.c @@ -3,7 +3,7 @@ * This file contains functions which manage high resolution tick * related events. * - * Copyright(C) 2005-2006, Thomas Gleixner + * Copyright(C) 2005-2006, Linutronix GmbH, Thomas Gleixner * Copyright(C) 2005-2007, Red Hat, Inc., Ingo Molnar * Copyright(C) 2006-2007, Timesys Corp., Thomas Gleixner */ diff --git a/kernel/time/tick-sched.c b/kernel/time/tick-sched.c index 8ddf74e705d3..2f8a7923fa27 100644 --- a/kernel/time/tick-sched.c +++ b/kernel/time/tick-sched.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Copyright(C) 2005-2006, Thomas Gleixner + * Copyright(C) 2005-2006, Linutronix GmbH, Thomas Gleixner * Copyright(C) 2005-2007, Red Hat, Inc., Ingo Molnar * Copyright(C) 2006-2007 Timesys Corp., Thomas Gleixner * diff --git a/lib/debugobjects.c b/lib/debugobjects.c index ecf8e7f978e3..89a1d6745dc2 100644 --- a/lib/debugobjects.c +++ b/lib/debugobjects.c @@ -2,7 +2,7 @@ /* * Generic infrastructure for lifetime debugging of objects. * - * Copyright (C) 2008, Thomas Gleixner + * Copyright (C) 2008, Linutronix GmbH, Thomas Gleixner */ #define pr_fmt(fmt) "ODEBUG: " fmt diff --git a/lib/plist.c b/lib/plist.c index ba677c31e8f3..a5bef38add43 100644 --- a/lib/plist.c +++ b/lib/plist.c @@ -10,7 +10,7 @@ * 2001-2005 (c) MontaVista Software, Inc. * Daniel Walker * - * (C) 2005 Thomas Gleixner + * (C) 2005 Linutronix GmbH, Thomas Gleixner * * Simplifications of the original code by * Oleg Nesterov diff --git a/lib/reed_solomon/decode_rs.c b/lib/reed_solomon/decode_rs.c index 805de84ae83d..ef86ee2aec58 100644 --- a/lib/reed_solomon/decode_rs.c +++ b/lib/reed_solomon/decode_rs.c @@ -5,7 +5,7 @@ * Copyright 2002, Phil Karn, KA9Q * May be used under the terms of the GNU General Public License (GPL) * - * Adaption to the kernel by Thomas Gleixner (tglx@linutronix.de) + * Adaption to the kernel by Thomas Gleixner (tglx@kernel.org) * * Generic data width independent code which is included by the wrappers. */ diff --git a/lib/reed_solomon/encode_rs.c b/lib/reed_solomon/encode_rs.c index 9112d46e869e..1d9e51dcc83d 100644 --- a/lib/reed_solomon/encode_rs.c +++ b/lib/reed_solomon/encode_rs.c @@ -5,7 +5,7 @@ * Copyright 2002, Phil Karn, KA9Q * May be used under the terms of the GNU General Public License (GPL) * - * Adaption to the kernel by Thomas Gleixner (tglx@linutronix.de) + * Adaption to the kernel by Thomas Gleixner (tglx@kernel.org) * * Generic data width independent code which is included by the wrappers. */ diff --git a/lib/reed_solomon/reed_solomon.c b/lib/reed_solomon/reed_solomon.c index bbc01bad3053..a9e2dcb6f2a7 100644 --- a/lib/reed_solomon/reed_solomon.c +++ b/lib/reed_solomon/reed_solomon.c @@ -2,7 +2,7 @@ /* * Generic Reed Solomon encoder / decoder library * - * Copyright (C) 2004 Thomas Gleixner (tglx@linutronix.de) + * Copyright (C) 2004 Thomas Gleixner (tglx@kernel.org) * * Reed Solomon code lifted from reed solomon library written by Phil Karn * Copyright 2002 Phil Karn, KA9Q diff --git a/scripts/spdxcheck.py b/scripts/spdxcheck.py index 8d608f61bf37..908029e45ca2 100755 --- a/scripts/spdxcheck.py +++ b/scripts/spdxcheck.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: GPL-2.0 -# Copyright Thomas Gleixner +# Copyright Linutronix GmbH, Thomas Gleixner from argparse import ArgumentParser from ply import lex, yacc diff --git a/tools/include/uapi/linux/perf_event.h b/tools/include/uapi/linux/perf_event.h index c44a8fb3e418..72f03153dd32 100644 --- a/tools/include/uapi/linux/perf_event.h +++ b/tools/include/uapi/linux/perf_event.h @@ -2,7 +2,7 @@ /* * Performance events: * - * Copyright (C) 2008-2009, Thomas Gleixner + * Copyright (C) 2008-2009, Linutronix GmbH, Thomas Gleixner * Copyright (C) 2008-2011, Red Hat, Inc., Ingo Molnar * Copyright (C) 2008-2011, Red Hat, Inc., Peter Zijlstra * diff --git a/tools/perf/builtin-list.c b/tools/perf/builtin-list.c index 5cbca0bacd35..87a5491048ac 100644 --- a/tools/perf/builtin-list.c +++ b/tools/perf/builtin-list.c @@ -4,7 +4,7 @@ * * Builtin list command: list all event types * - * Copyright (C) 2009, Thomas Gleixner + * Copyright (C) 2009, Linutronix GmbH, Thomas Gleixner * Copyright (C) 2008-2009, Red Hat Inc, Ingo Molnar * Copyright (C) 2011, Red Hat Inc, Arnaldo Carvalho de Melo */ -- cgit v1.2.3 From 576ee5dfd459abe8e29bee8b204cd259e60b4e18 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 12 Jan 2026 16:47:10 +0100 Subject: fs: add immutable rootfs Currently pivot_root() doesn't work on the real rootfs because it cannot be unmounted. Userspace has to do a recursive removal of the initramfs contents manually before continuing the boot. Really all we want from the real rootfs is to serve as the parent mount for anything that is actually useful such as the tmpfs or ramfs for initramfs unpacking or the rootfs itself. There's no need for the real rootfs to actually be anything meaningful or useful. Add a immutable rootfs called "nullfs" that can be selected via the "nullfs_rootfs" kernel command line option. The kernel will mount a tmpfs/ramfs on top of it, unpack the initramfs and fire up userspace which mounts the rootfs and can then just do: chdir(rootfs); pivot_root(".", "."); umount2(".", MNT_DETACH); and be done with it. (Ofc, userspace can also choose to retain the initramfs contents by using something like pivot_root(".", "/initramfs") without unmounting it.) Technically this also means that the rootfs mount in unprivileged namespaces doesn't need to become MNT_LOCKED anymore as it's guaranteed that the immutable rootfs remains permanently empty so there cannot be anything revealed by unmounting the covering mount. In the future this will also allow us to create completely empty mount namespaces without risking to leak anything. systemd already handles this all correctly as it tries to pivot_root() first and falls back to MS_MOVE only when that fails. This goes back to various discussion in previous years and a LPC 2024 presentation about this very topic. Link: https://patch.msgid.link/20260112-work-immutable-rootfs-v2-3-88dd1c34a204@kernel.org Signed-off-by: Christian Brauner --- fs/Makefile | 2 +- fs/mount.h | 1 + fs/namespace.c | 82 +++++++++++++++++++++++++++++++++++++++------- fs/nullfs.c | 70 +++++++++++++++++++++++++++++++++++++++ include/uapi/linux/magic.h | 1 + init/do_mounts.c | 14 ++++++++ init/do_mounts.h | 1 + 7 files changed, 159 insertions(+), 12 deletions(-) create mode 100644 fs/nullfs.c (limited to 'include/uapi/linux') diff --git a/fs/Makefile b/fs/Makefile index a04274a3c854..becf133e4791 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -16,7 +16,7 @@ obj-y := open.o read_write.o file_table.o super.o \ stack.o fs_struct.o statfs.o fs_pin.o nsfs.o \ fs_dirent.o fs_context.o fs_parser.o fsopen.o init.o \ kernel_read_file.o mnt_idmapping.o remap_range.o pidfs.o \ - file_attr.o + file_attr.o nullfs.o obj-$(CONFIG_BUFFER_HEAD) += buffer.o mpage.o obj-$(CONFIG_PROC_FS) += proc_namespace.o diff --git a/fs/mount.h b/fs/mount.h index 2d28ef2a3aed..e0816c11a198 100644 --- a/fs/mount.h +++ b/fs/mount.h @@ -5,6 +5,7 @@ #include #include +extern struct file_system_type nullfs_fs_type; extern struct list_head notify_list; struct mnt_namespace { diff --git a/fs/namespace.c b/fs/namespace.c index 9261f56ccc81..a44ebb2f1161 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -75,6 +75,17 @@ static int __init initramfs_options_setup(char *str) __setup("initramfs_options=", initramfs_options_setup); +bool nullfs_rootfs = false; + +static int __init nullfs_rootfs_setup(char *str) +{ + if (*str) + return 0; + nullfs_rootfs = true; + return 1; +} +__setup("nullfs_rootfs", nullfs_rootfs_setup); + static u64 event; static DEFINE_XARRAY_FLAGS(mnt_id_xa, XA_FLAGS_ALLOC); static DEFINE_IDA(mnt_group_ida); @@ -4582,8 +4593,9 @@ int path_pivot_root(struct path *new, struct path *old) * pointed to by put_old must yield the same directory as new_root. No other * file system may be mounted on put_old. After all, new_root is a mountpoint. * - * Also, the current root cannot be on the 'rootfs' (initial ramfs) filesystem. - * See Documentation/filesystems/ramfs-rootfs-initramfs.rst for alternatives + * Also, the current root cannot be on the 'rootfs' (initial ramfs) filesystem + * unless the kernel was booted with "nullfs_rootfs". See + * Documentation/filesystems/ramfs-rootfs-initramfs.rst for alternatives * in this situation. * * Notes: @@ -5976,24 +5988,72 @@ struct mnt_namespace init_mnt_ns = { static void __init init_mount_tree(void) { - struct vfsmount *mnt; - struct mount *m; + struct vfsmount *mnt, *nullfs_mnt; + struct mount *mnt_root; struct path root; + /* + * When nullfs is used, we create two mounts: + * + * (1) nullfs with mount id 1 + * (2) mutable rootfs with mount id 2 + * + * with (2) mounted on top of (1). + */ + if (nullfs_rootfs) { + nullfs_mnt = vfs_kern_mount(&nullfs_fs_type, 0, "nullfs", NULL); + if (IS_ERR(nullfs_mnt)) + panic("VFS: Failed to create nullfs"); + } + mnt = vfs_kern_mount(&rootfs_fs_type, 0, "rootfs", initramfs_options); if (IS_ERR(mnt)) panic("Can't create rootfs"); - m = real_mount(mnt); - init_mnt_ns.root = m; - init_mnt_ns.nr_mounts = 1; - mnt_add_to_ns(&init_mnt_ns, m); + if (nullfs_rootfs) { + VFS_WARN_ON_ONCE(real_mount(nullfs_mnt)->mnt_id != 1); + VFS_WARN_ON_ONCE(real_mount(mnt)->mnt_id != 2); + + /* The namespace root is the nullfs mnt. */ + mnt_root = real_mount(nullfs_mnt); + init_mnt_ns.root = mnt_root; + + /* Mount mutable rootfs on top of nullfs. */ + root.mnt = nullfs_mnt; + root.dentry = nullfs_mnt->mnt_root; + + LOCK_MOUNT_EXACT(mp, &root); + if (unlikely(IS_ERR(mp.parent))) + panic("VFS: Failed to mount rootfs on nullfs"); + scoped_guard(mount_writer) + attach_mnt(real_mount(mnt), mp.parent, mp.mp); + + pr_info("VFS: Finished mounting rootfs on nullfs\n"); + } else { + VFS_WARN_ON_ONCE(real_mount(mnt)->mnt_id != 1); + + /* The namespace root is the mutable rootfs. */ + mnt_root = real_mount(mnt); + init_mnt_ns.root = mnt_root; + } + + /* + * We've dropped all locks here but that's fine. Not just are we + * the only task that's running, there's no other mount + * namespace in existence and the initial mount namespace is + * completely empty until we add the mounts we just created. + */ + for (struct mount *p = mnt_root; p; p = next_mnt(p, mnt_root)) { + mnt_add_to_ns(&init_mnt_ns, p); + init_mnt_ns.nr_mounts++; + } + init_task.nsproxy->mnt_ns = &init_mnt_ns; get_mnt_ns(&init_mnt_ns); - root.mnt = mnt; - root.dentry = mnt->mnt_root; - + /* The root and pwd always point to the mutable rootfs. */ + root.mnt = mnt; + root.dentry = mnt->mnt_root; set_fs_pwd(current->fs, &root); set_fs_root(current->fs, &root); diff --git a/fs/nullfs.c b/fs/nullfs.c new file mode 100644 index 000000000000..fdbd3e5d3d71 --- /dev/null +++ b/fs/nullfs.c @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2026 Christian Brauner */ +#include +#include +#include + +static const struct super_operations nullfs_super_operations = { + .statfs = simple_statfs, +}; + +static int nullfs_fs_fill_super(struct super_block *s, struct fs_context *fc) +{ + struct inode *inode; + + s->s_maxbytes = MAX_LFS_FILESIZE; + s->s_blocksize = PAGE_SIZE; + s->s_blocksize_bits = PAGE_SHIFT; + s->s_magic = NULL_FS_MAGIC; + s->s_op = &nullfs_super_operations; + s->s_export_op = NULL; + s->s_xattr = NULL; + s->s_time_gran = 1; + s->s_d_flags = 0; + + inode = new_inode(s); + if (!inode) + return -ENOMEM; + + /* nullfs is permanently empty... */ + make_empty_dir_inode(inode); + simple_inode_init_ts(inode); + inode->i_ino = 1; + /* ... and immutable. */ + inode->i_flags |= S_IMMUTABLE; + + s->s_root = d_make_root(inode); + if (!s->s_root) + return -ENOMEM; + + return 0; +} + +/* + * For now this is a single global instance. If needed we can make it + * mountable by userspace at which point we will need to make it + * multi-instance. + */ +static int nullfs_fs_get_tree(struct fs_context *fc) +{ + return get_tree_single(fc, nullfs_fs_fill_super); +} + +static const struct fs_context_operations nullfs_fs_context_ops = { + .get_tree = nullfs_fs_get_tree, +}; + +static int nullfs_init_fs_context(struct fs_context *fc) +{ + fc->ops = &nullfs_fs_context_ops; + fc->global = true; + fc->sb_flags = SB_NOUSER; + fc->s_iflags = SB_I_NOEXEC | SB_I_NODEV; + return 0; +} + +struct file_system_type nullfs_fs_type = { + .name = "nullfs", + .init_fs_context = nullfs_init_fs_context, + .kill_sb = kill_anon_super, +}; diff --git a/include/uapi/linux/magic.h b/include/uapi/linux/magic.h index 638ca21b7a90..4f2da935a76c 100644 --- a/include/uapi/linux/magic.h +++ b/include/uapi/linux/magic.h @@ -104,5 +104,6 @@ #define SECRETMEM_MAGIC 0x5345434d /* "SECM" */ #define PID_FS_MAGIC 0x50494446 /* "PIDF" */ #define GUEST_MEMFD_MAGIC 0x474d454d /* "GMEM" */ +#define NULL_FS_MAGIC 0x4E554C4C /* "NULL" */ #endif /* __LINUX_MAGIC_H__ */ diff --git a/init/do_mounts.c b/init/do_mounts.c index defbbf1d55f7..675397c8a7a4 100644 --- a/init/do_mounts.c +++ b/init/do_mounts.c @@ -492,6 +492,20 @@ void __init prepare_namespace(void) mount_root(saved_root_name); out: devtmpfs_mount(); + + if (nullfs_rootfs) { + if (init_pivot_root(".", ".")) { + pr_err("VFS: Failed to pivot into new rootfs\n"); + return; + } + if (init_umount(".", MNT_DETACH)) { + pr_err("VFS: Failed to unmount old rootfs\n"); + return; + } + pr_info("VFS: Pivoted into new rootfs\n"); + return; + } + init_mount(".", "/", NULL, MS_MOVE, NULL); init_chroot("."); } diff --git a/init/do_mounts.h b/init/do_mounts.h index 6069ea3eb80d..fbfee810aa89 100644 --- a/init/do_mounts.h +++ b/init/do_mounts.h @@ -15,6 +15,7 @@ void mount_root_generic(char *name, char *pretty_name, int flags); void mount_root(char *root_device_name); extern int root_mountflags; +extern bool nullfs_rootfs; static inline __init int create_dev(char *name, dev_t dev) { -- cgit v1.2.3 From 6abbb8703aeeb645a681ab6ad155e0b450413787 Mon Sep 17 00:00:00 2001 From: Günther Noack Date: Sun, 11 Jan 2026 18:52:04 +0100 Subject: landlock: Clarify documentation for the IOCTL access right MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the description of the LANDLOCK_ACCESS_FS_IOCTL_DEV access right together with the file access rights. This group of access rights applies to files (in this case device files), and they can be added to file or directory inodes using landlock_add_rule(2). The check for that works the same for all file access rights, including LANDLOCK_ACCESS_FS_IOCTL_DEV. Invoking ioctl(2) on directory FDs can not currently be restricted with Landlock. Having it grouped separately in the documentation is a remnant from earlier revisions of the LANDLOCK_ACCESS_FS_IOCTL_DEV patch set. Link: https://lore.kernel.org/all/20260108.Thaex5ruach2@digikod.net/ Signed-off-by: Günther Noack Link: https://lore.kernel.org/r/20260111175203.6545-2-gnoack3000@gmail.com Signed-off-by: Mickaël Salaün --- include/uapi/linux/landlock.h | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h index f030adc462ee..75fd7f5e6cc3 100644 --- a/include/uapi/linux/landlock.h +++ b/include/uapi/linux/landlock.h @@ -216,6 +216,23 @@ struct landlock_net_port_attr { * :manpage:`ftruncate(2)`, :manpage:`creat(2)`, or :manpage:`open(2)` with * ``O_TRUNC``. This access right is available since the third version of the * Landlock ABI. + * - %LANDLOCK_ACCESS_FS_IOCTL_DEV: Invoke :manpage:`ioctl(2)` commands on an opened + * character or block device. + * + * This access right applies to all `ioctl(2)` commands implemented by device + * drivers. However, the following common IOCTL commands continue to be + * invokable independent of the %LANDLOCK_ACCESS_FS_IOCTL_DEV right: + * + * * IOCTL commands targeting file descriptors (``FIOCLEX``, ``FIONCLEX``), + * * IOCTL commands targeting file descriptions (``FIONBIO``, ``FIOASYNC``), + * * IOCTL commands targeting file systems (``FIFREEZE``, ``FITHAW``, + * ``FIGETBSZ``, ``FS_IOC_GETFSUUID``, ``FS_IOC_GETFSSYSFSPATH``) + * * Some IOCTL commands which do not make sense when used with devices, but + * whose implementations are safe and return the right error codes + * (``FS_IOC_FIEMAP``, ``FICLONE``, ``FICLONERANGE``, ``FIDEDUPERANGE``) + * + * This access right is available since the fifth version of the Landlock + * ABI. * * Whether an opened file can be truncated with :manpage:`ftruncate(2)` or used * with `ioctl(2)` is determined during :manpage:`open(2)`, in the same way as @@ -275,26 +292,6 @@ struct landlock_net_port_attr { * If multiple requirements are not met, the ``EACCES`` error code takes * precedence over ``EXDEV``. * - * The following access right applies both to files and directories: - * - * - %LANDLOCK_ACCESS_FS_IOCTL_DEV: Invoke :manpage:`ioctl(2)` commands on an opened - * character or block device. - * - * This access right applies to all `ioctl(2)` commands implemented by device - * drivers. However, the following common IOCTL commands continue to be - * invokable independent of the %LANDLOCK_ACCESS_FS_IOCTL_DEV right: - * - * * IOCTL commands targeting file descriptors (``FIOCLEX``, ``FIONCLEX``), - * * IOCTL commands targeting file descriptions (``FIONBIO``, ``FIOASYNC``), - * * IOCTL commands targeting file systems (``FIFREEZE``, ``FITHAW``, - * ``FIGETBSZ``, ``FS_IOC_GETFSUUID``, ``FS_IOC_GETFSSYSFSPATH``) - * * Some IOCTL commands which do not make sense when used with devices, but - * whose implementations are safe and return the right error codes - * (``FS_IOC_FIEMAP``, ``FICLONE``, ``FICLONERANGE``, ``FIDEDUPERANGE``) - * - * This access right is available since the fifth version of the Landlock - * ABI. - * * .. warning:: * * It is currently not possible to restrict some file-related actions -- cgit v1.2.3 From 98bf2256855eb682433a33e6a7c4bce35191ca99 Mon Sep 17 00:00:00 2001 From: Stanley Zhang Date: Thu, 8 Jan 2026 02:19:31 -0700 Subject: ublk: support UBLK_PARAM_TYPE_INTEGRITY in device creation Add a feature flag UBLK_F_INTEGRITY for a ublk server to request integrity/metadata support when creating a ublk device. The ublk server can also check for the feature flag on the created device or the result of UBLK_U_CMD_GET_FEATURES to tell if the ublk driver supports it. UBLK_F_INTEGRITY requires UBLK_F_USER_COPY, as user copy is the only data copy mode initially supported for integrity data. Add UBLK_PARAM_TYPE_INTEGRITY and struct ublk_param_integrity to struct ublk_params to specify the integrity params of a ublk device. UBLK_PARAM_TYPE_INTEGRITY requires UBLK_F_INTEGRITY and a nonzero metadata_size. The LBMD_PI_CAP_* and LBMD_PI_CSUM_* values from the linux/fs.h UAPI header are used for the flags and csum_type fields. If the UBLK_PARAM_TYPE_INTEGRITY flag is set, validate the integrity parameters and apply them to the blk_integrity limits. The struct ublk_param_integrity validations are based on the checks in blk_validate_integrity_limits(). Any invalid parameters should be rejected before being applied to struct blk_integrity. [csander: drop redundant pi_tuple_size field, use block metadata UAPI constants, add param validation] Signed-off-by: Stanley Zhang Signed-off-by: Caleb Sander Mateos Reviewed-by: Ming Lei Signed-off-by: Jens Axboe --- drivers/block/ublk_drv.c | 101 +++++++++++++++++++++++++++++++++++++++++- include/uapi/linux/ublk_cmd.h | 19 ++++++++ 2 files changed, 119 insertions(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c index 53df4bfa2c92..a4d62e8e4f6b 100644 --- a/drivers/block/ublk_drv.c +++ b/drivers/block/ublk_drv.c @@ -44,6 +44,8 @@ #include #include #include +#include +#include #include #define UBLK_MINORS (1U << MINORBITS) @@ -83,7 +85,8 @@ #define UBLK_PARAM_TYPE_ALL \ (UBLK_PARAM_TYPE_BASIC | UBLK_PARAM_TYPE_DISCARD | \ UBLK_PARAM_TYPE_DEVT | UBLK_PARAM_TYPE_ZONED | \ - UBLK_PARAM_TYPE_DMA_ALIGN | UBLK_PARAM_TYPE_SEGMENT) + UBLK_PARAM_TYPE_DMA_ALIGN | UBLK_PARAM_TYPE_SEGMENT | \ + UBLK_PARAM_TYPE_INTEGRITY) struct ublk_uring_cmd_pdu { /* @@ -301,6 +304,11 @@ static inline bool ublk_queue_is_zoned(const struct ublk_queue *ubq) return ubq->flags & UBLK_F_ZONED; } +static inline bool ublk_dev_support_integrity(const struct ublk_device *ub) +{ + return ub->dev_info.flags & UBLK_F_INTEGRITY; +} + #ifdef CONFIG_BLK_DEV_ZONED struct ublk_zoned_report_desc { @@ -616,6 +624,53 @@ static void ublk_dev_param_basic_apply(struct ublk_device *ub) set_capacity(ub->ub_disk, p->dev_sectors); } +static int ublk_integrity_flags(u32 flags) +{ + int ret_flags = 0; + + if (flags & LBMD_PI_CAP_INTEGRITY) { + flags &= ~LBMD_PI_CAP_INTEGRITY; + ret_flags |= BLK_INTEGRITY_DEVICE_CAPABLE; + } + if (flags & LBMD_PI_CAP_REFTAG) { + flags &= ~LBMD_PI_CAP_REFTAG; + ret_flags |= BLK_INTEGRITY_REF_TAG; + } + return flags ? -EINVAL : ret_flags; +} + +static int ublk_integrity_pi_tuple_size(u8 csum_type) +{ + switch (csum_type) { + case LBMD_PI_CSUM_NONE: + return 0; + case LBMD_PI_CSUM_IP: + case LBMD_PI_CSUM_CRC16_T10DIF: + return 8; + case LBMD_PI_CSUM_CRC64_NVME: + return 16; + default: + return -EINVAL; + } +} + +static enum blk_integrity_checksum ublk_integrity_csum_type(u8 csum_type) +{ + switch (csum_type) { + case LBMD_PI_CSUM_NONE: + return BLK_INTEGRITY_CSUM_NONE; + case LBMD_PI_CSUM_IP: + return BLK_INTEGRITY_CSUM_IP; + case LBMD_PI_CSUM_CRC16_T10DIF: + return BLK_INTEGRITY_CSUM_CRC; + case LBMD_PI_CSUM_CRC64_NVME: + return BLK_INTEGRITY_CSUM_CRC64; + default: + WARN_ON_ONCE(1); + return BLK_INTEGRITY_CSUM_NONE; + } +} + static int ublk_validate_params(const struct ublk_device *ub) { /* basic param is the only one which must be set */ @@ -678,6 +733,29 @@ static int ublk_validate_params(const struct ublk_device *ub) return -EINVAL; } + if (ub->params.types & UBLK_PARAM_TYPE_INTEGRITY) { + const struct ublk_param_integrity *p = &ub->params.integrity; + int pi_tuple_size = ublk_integrity_pi_tuple_size(p->csum_type); + int flags = ublk_integrity_flags(p->flags); + + if (!ublk_dev_support_integrity(ub)) + return -EINVAL; + if (flags < 0) + return flags; + if (pi_tuple_size < 0) + return pi_tuple_size; + if (!p->metadata_size) + return -EINVAL; + if (p->csum_type == LBMD_PI_CSUM_NONE && + p->flags & LBMD_PI_CAP_REFTAG) + return -EINVAL; + if (p->pi_offset + pi_tuple_size > p->metadata_size) + return -EINVAL; + if (p->interval_exp < SECTOR_SHIFT || + p->interval_exp > ub->params.basic.logical_bs_shift) + return -EINVAL; + } + return 0; } @@ -2950,6 +3028,23 @@ static int ublk_ctrl_start_dev(struct ublk_device *ub, lim.max_segments = ub->params.seg.max_segments; } + if (ub->params.types & UBLK_PARAM_TYPE_INTEGRITY) { + const struct ublk_param_integrity *p = &ub->params.integrity; + int pi_tuple_size = ublk_integrity_pi_tuple_size(p->csum_type); + + lim.max_integrity_segments = + p->max_integrity_segments ?: USHRT_MAX; + lim.integrity = (struct blk_integrity) { + .flags = ublk_integrity_flags(p->flags), + .csum_type = ublk_integrity_csum_type(p->csum_type), + .metadata_size = p->metadata_size, + .pi_offset = p->pi_offset, + .interval_exp = p->interval_exp, + .tag_size = p->tag_size, + .pi_tuple_size = pi_tuple_size, + }; + } + if (wait_for_completion_interruptible(&ub->completion) != 0) return -EINTR; @@ -3140,6 +3235,10 @@ static int ublk_ctrl_add_dev(const struct ublksrv_ctrl_cmd *header) return -EINVAL; } + /* User copy is required to access integrity buffer */ + if (info.flags & UBLK_F_INTEGRITY && !(info.flags & UBLK_F_USER_COPY)) + return -EINVAL; + /* the created device is always owned by current user */ ublk_store_owner_uid_gid(&info.owner_uid, &info.owner_gid); diff --git a/include/uapi/linux/ublk_cmd.h b/include/uapi/linux/ublk_cmd.h index ec77dabba45b..4c141d7e4710 100644 --- a/include/uapi/linux/ublk_cmd.h +++ b/include/uapi/linux/ublk_cmd.h @@ -311,6 +311,12 @@ */ #define UBLK_F_BUF_REG_OFF_DAEMON (1ULL << 14) +/* + * ublk device supports requests with integrity/metadata buffer. + * Requires UBLK_F_USER_COPY. + */ +#define UBLK_F_INTEGRITY (1ULL << 16) + /* device state */ #define UBLK_S_DEV_DEAD 0 #define UBLK_S_DEV_LIVE 1 @@ -600,6 +606,17 @@ struct ublk_param_segment { __u8 pad[2]; }; +struct ublk_param_integrity { + __u32 flags; /* LBMD_PI_CAP_* from linux/fs.h */ + __u16 max_integrity_segments; /* 0 means no limit */ + __u8 interval_exp; + __u8 metadata_size; /* UBLK_PARAM_TYPE_INTEGRITY requires nonzero */ + __u8 pi_offset; + __u8 csum_type; /* LBMD_PI_CSUM_* from linux/fs.h */ + __u8 tag_size; + __u8 pad[5]; +}; + struct ublk_params { /* * Total length of parameters, userspace has to set 'len' for both @@ -614,6 +631,7 @@ struct ublk_params { #define UBLK_PARAM_TYPE_ZONED (1 << 3) #define UBLK_PARAM_TYPE_DMA_ALIGN (1 << 4) #define UBLK_PARAM_TYPE_SEGMENT (1 << 5) +#define UBLK_PARAM_TYPE_INTEGRITY (1 << 6) /* requires UBLK_F_INTEGRITY */ __u32 types; /* types of parameter included */ struct ublk_param_basic basic; @@ -622,6 +640,7 @@ struct ublk_params { struct ublk_param_zoned zoned; struct ublk_param_dma_align dma; struct ublk_param_segment seg; + struct ublk_param_integrity integrity; }; #endif -- cgit v1.2.3 From f82f0a16a8270b17211254beeb123d11a0f279cd Mon Sep 17 00:00:00 2001 From: Caleb Sander Mateos Date: Thu, 8 Jan 2026 02:19:32 -0700 Subject: ublk: set UBLK_IO_F_INTEGRITY in ublksrv_io_desc Indicate to the ublk server when an incoming request has integrity data by setting UBLK_IO_F_INTEGRITY in the ublksrv_io_desc's op_flags field. Signed-off-by: Caleb Sander Mateos Reviewed-by: Ming Lei Signed-off-by: Jens Axboe --- drivers/block/ublk_drv.c | 3 +++ include/uapi/linux/ublk_cmd.h | 2 ++ 2 files changed, 5 insertions(+) (limited to 'include/uapi/linux') diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c index a4d62e8e4f6b..fc7de2985a20 100644 --- a/drivers/block/ublk_drv.c +++ b/drivers/block/ublk_drv.c @@ -1113,6 +1113,9 @@ static inline unsigned int ublk_req_build_flags(struct request *req) if (req->cmd_flags & REQ_SWAP) flags |= UBLK_IO_F_SWAP; + if (blk_integrity_rq(req)) + flags |= UBLK_IO_F_INTEGRITY; + return flags; } diff --git a/include/uapi/linux/ublk_cmd.h b/include/uapi/linux/ublk_cmd.h index 4c141d7e4710..dfde4aee39eb 100644 --- a/include/uapi/linux/ublk_cmd.h +++ b/include/uapi/linux/ublk_cmd.h @@ -414,6 +414,8 @@ struct ublksrv_ctrl_dev_info { * passed in. */ #define UBLK_IO_F_NEED_REG_BUF (1U << 17) +/* Request has an integrity data buffer */ +#define UBLK_IO_F_INTEGRITY (1UL << 18) /* * io cmd is described by this structure, and stored in share memory, indexed -- cgit v1.2.3 From be82a89066d595da334f6e153ababcedc3f92ad6 Mon Sep 17 00:00:00 2001 From: Stanley Zhang Date: Thu, 8 Jan 2026 02:19:37 -0700 Subject: ublk: implement integrity user copy Add a function ublk_copy_user_integrity() to copy integrity information between a request and a user iov_iter. This mirrors the existing ublk_copy_user_pages() but operates on request integrity data instead of regular data. Check UBLKSRV_IO_INTEGRITY_FLAG in iocb->ki_pos in ublk_user_copy() to choose between copying data or integrity data. [csander: change offset units from data bytes to integrity data bytes, fix CONFIG_BLK_DEV_INTEGRITY=n build, rebase on user copy refactor] Signed-off-by: Stanley Zhang Signed-off-by: Caleb Sander Mateos Reviewed-by: Ming Lei Signed-off-by: Jens Axboe --- drivers/block/ublk_drv.c | 53 +++++++++++++++++++++++++++++++++++++++++-- include/uapi/linux/ublk_cmd.h | 4 ++++ 2 files changed, 55 insertions(+), 2 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c index d428a25121db..5c441f507c43 100644 --- a/drivers/block/ublk_drv.c +++ b/drivers/block/ublk_drv.c @@ -1040,6 +1040,33 @@ static size_t ublk_copy_user_pages(const struct request *req, return done; } +#ifdef CONFIG_BLK_DEV_INTEGRITY +static size_t ublk_copy_user_integrity(const struct request *req, + unsigned offset, struct iov_iter *uiter, int dir) +{ + size_t done = 0; + struct bio *bio = req->bio; + struct bvec_iter iter; + struct bio_vec iv; + + if (!blk_integrity_rq(req)) + return 0; + + bio_for_each_integrity_vec(iv, bio, iter) { + if (!ublk_copy_user_bvec(&iv, &offset, uiter, dir, &done)) + break; + } + + return done; +} +#else /* #ifdef CONFIG_BLK_DEV_INTEGRITY */ +static size_t ublk_copy_user_integrity(const struct request *req, + unsigned offset, struct iov_iter *uiter, int dir) +{ + return 0; +} +#endif /* #ifdef CONFIG_BLK_DEV_INTEGRITY */ + static inline bool ublk_need_map_req(const struct request *req) { return ublk_rq_has_data(req) && req_op(req) == REQ_OP_WRITE; @@ -2668,6 +2695,8 @@ ublk_user_copy(struct kiocb *iocb, struct iov_iter *iter, int dir) struct ublk_queue *ubq; struct request *req; struct ublk_io *io; + unsigned data_len; + bool is_integrity; size_t buf_off; u16 tag, q_id; ssize_t ret; @@ -2681,6 +2710,10 @@ ublk_user_copy(struct kiocb *iocb, struct iov_iter *iter, int dir) tag = ublk_pos_to_tag(iocb->ki_pos); q_id = ublk_pos_to_hwq(iocb->ki_pos); buf_off = ublk_pos_to_buf_off(iocb->ki_pos); + is_integrity = !!(iocb->ki_pos & UBLKSRV_IO_INTEGRITY_FLAG); + + if (unlikely(!ublk_dev_support_integrity(ub) && is_integrity)) + return -EINVAL; if (q_id >= ub->dev_info.nr_hw_queues) return -EINVAL; @@ -2697,7 +2730,14 @@ ublk_user_copy(struct kiocb *iocb, struct iov_iter *iter, int dir) if (!req) return -EINVAL; - if (buf_off > blk_rq_bytes(req)) { + if (is_integrity) { + struct blk_integrity *bi = &req->q->limits.integrity; + + data_len = bio_integrity_bytes(bi, blk_rq_sectors(req)); + } else { + data_len = blk_rq_bytes(req); + } + if (buf_off > data_len) { ret = -EINVAL; goto out; } @@ -2707,7 +2747,10 @@ ublk_user_copy(struct kiocb *iocb, struct iov_iter *iter, int dir) goto out; } - ret = ublk_copy_user_pages(req, buf_off, iter, dir); + if (is_integrity) + ret = ublk_copy_user_integrity(req, buf_off, iter, dir); + else + ret = ublk_copy_user_pages(req, buf_off, iter, dir); out: ublk_put_req_ref(io, req); @@ -3948,6 +3991,12 @@ static int __init ublk_init(void) BUILD_BUG_ON((u64)UBLKSRV_IO_BUF_OFFSET + UBLKSRV_IO_BUF_TOTAL_SIZE < UBLKSRV_IO_BUF_OFFSET); + /* + * Ensure UBLKSRV_IO_BUF_OFFSET + UBLKSRV_IO_BUF_TOTAL_SIZE + * doesn't overflow into UBLKSRV_IO_INTEGRITY_FLAG + */ + BUILD_BUG_ON(UBLKSRV_IO_BUF_OFFSET + UBLKSRV_IO_BUF_TOTAL_SIZE >= + UBLKSRV_IO_INTEGRITY_FLAG); BUILD_BUG_ON(sizeof(struct ublk_auto_buf_reg) != 8); init_waitqueue_head(&ublk_idr_wq); diff --git a/include/uapi/linux/ublk_cmd.h b/include/uapi/linux/ublk_cmd.h index dfde4aee39eb..61ac5d8e1078 100644 --- a/include/uapi/linux/ublk_cmd.h +++ b/include/uapi/linux/ublk_cmd.h @@ -134,6 +134,10 @@ #define UBLKSRV_IO_BUF_TOTAL_BITS (UBLK_QID_OFF + UBLK_QID_BITS) #define UBLKSRV_IO_BUF_TOTAL_SIZE (1ULL << UBLKSRV_IO_BUF_TOTAL_BITS) +/* Copy to/from request integrity buffer instead of data buffer */ +#define UBLK_INTEGRITY_FLAG_OFF 62 +#define UBLKSRV_IO_INTEGRITY_FLAG (1ULL << UBLK_INTEGRITY_FLAG_OFF) + /* * ublk server can register data buffers for incoming I/O requests with a sparse * io_uring buffer table. The request buffer can then be used as the data buffer -- cgit v1.2.3 From e6ce36ccc86f6d447808a6e620f56d440d74aa19 Mon Sep 17 00:00:00 2001 From: Askar Safin Date: Wed, 19 Nov 2025 22:24:07 +0000 Subject: init: remove /proc/sys/kernel/real-root-dev It is not used anymore. Signed-off-by: Askar Safin Link: https://patch.msgid.link/20251119222407.3333257-4-safinaskar@gmail.com Reviewed-by: Christoph Hellwig Signed-off-by: Christian Brauner --- Documentation/admin-guide/sysctl/kernel.rst | 6 ------ include/uapi/linux/sysctl.h | 1 - init/do_mounts_initrd.c | 20 -------------------- 3 files changed, 27 deletions(-) (limited to 'include/uapi/linux') diff --git a/Documentation/admin-guide/sysctl/kernel.rst b/Documentation/admin-guide/sysctl/kernel.rst index 239da22c4e28..bb577fac76a0 100644 --- a/Documentation/admin-guide/sysctl/kernel.rst +++ b/Documentation/admin-guide/sysctl/kernel.rst @@ -1235,12 +1235,6 @@ that support this feature. == =========================================================================== -real-root-dev -============= - -See Documentation/admin-guide/initrd.rst. - - reboot-cmd (SPARC only) ======================= diff --git a/include/uapi/linux/sysctl.h b/include/uapi/linux/sysctl.h index 63d1464cb71c..1c7fe0f4dca4 100644 --- a/include/uapi/linux/sysctl.h +++ b/include/uapi/linux/sysctl.h @@ -92,7 +92,6 @@ enum KERN_DOMAINNAME=8, /* string: domainname */ KERN_PANIC=15, /* int: panic timeout */ - KERN_REALROOTDEV=16, /* real root device to mount after initrd */ KERN_SPARC_REBOOT=21, /* reboot command on Sparc */ KERN_CTLALTDEL=22, /* int: allow ctl-alt-del to reboot */ diff --git a/init/do_mounts_initrd.c b/init/do_mounts_initrd.c index fe335dbc95e0..892e69ab41c4 100644 --- a/init/do_mounts_initrd.c +++ b/init/do_mounts_initrd.c @@ -8,31 +8,11 @@ unsigned long initrd_start, initrd_end; int initrd_below_start_ok; -static unsigned int real_root_dev; /* do_proc_dointvec cannot handle kdev_t */ static int __initdata mount_initrd = 1; phys_addr_t phys_initrd_start __initdata; unsigned long phys_initrd_size __initdata; -#ifdef CONFIG_SYSCTL -static const struct ctl_table kern_do_mounts_initrd_table[] = { - { - .procname = "real-root-dev", - .data = &real_root_dev, - .maxlen = sizeof(int), - .mode = 0644, - .proc_handler = proc_dointvec, - }, -}; - -static __init int kernel_do_mounts_initrd_sysctls_init(void) -{ - register_sysctl_init("kernel", kern_do_mounts_initrd_table); - return 0; -} -late_initcall(kernel_do_mounts_initrd_sysctls_init); -#endif /* CONFIG_SYSCTL */ - static int __init no_initrd(char *str) { pr_warn("noinitrd option is deprecated and will be removed soon\n"); -- cgit v1.2.3 From e1cbdf78f60c35a1a320ca401852fd6a73624a4a Mon Sep 17 00:00:00 2001 From: Lachlan Hodges Date: Fri, 9 Jan 2026 19:14:39 +1100 Subject: wifi: cfg80211: include S1G_NO_PRIMARY flag when sending channel When sending a channel ensure we include the IEEE80211_CHAN_S1G_NO_PRIMARY flag. Signed-off-by: Lachlan Hodges Link: https://patch.msgid.link/20260109081439.3168-1-lachlan.hodges@morsemicro.com Signed-off-by: Johannes Berg --- include/uapi/linux/nl80211.h | 4 ++++ net/wireless/nl80211.c | 3 +++ 2 files changed, 7 insertions(+) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index 8134f10e4e6c..964e1c779cdd 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -4444,6 +4444,9 @@ enum nl80211_wmm_rule { * channel in current regulatory domain. * @NL80211_FREQUENCY_ATTR_NO_16MHZ: 16 MHz operation is not allowed on this * channel in current regulatory domain. + * @NL80211_FREQUENCY_ATTR_S1G_NO_PRIMARY: Channel is not permitted for use + * as a primary channel. Does not prevent the channel from existing + * as a non-primary subchannel. Only applicable to S1G channels. * @NL80211_FREQUENCY_ATTR_MAX: highest frequency attribute number * currently defined * @__NL80211_FREQUENCY_ATTR_AFTER_LAST: internal use @@ -4492,6 +4495,7 @@ enum nl80211_frequency_attr { NL80211_FREQUENCY_ATTR_NO_4MHZ, NL80211_FREQUENCY_ATTR_NO_8MHZ, NL80211_FREQUENCY_ATTR_NO_16MHZ, + NL80211_FREQUENCY_ATTR_S1G_NO_PRIMARY, /* keep last */ __NL80211_FREQUENCY_ATTR_AFTER_LAST, diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index c961cd42a832..225580507a4b 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -1314,6 +1314,9 @@ static int nl80211_msg_put_channel(struct sk_buff *msg, struct wiphy *wiphy, if ((chan->flags & IEEE80211_CHAN_NO_16MHZ) && nla_put_flag(msg, NL80211_FREQUENCY_ATTR_NO_16MHZ)) goto nla_put_failure; + if ((chan->flags & IEEE80211_CHAN_S1G_NO_PRIMARY) && + nla_put_flag(msg, NL80211_FREQUENCY_ATTR_S1G_NO_PRIMARY)) + goto nla_put_failure; } if (nla_put_u32(msg, NL80211_FREQUENCY_ATTR_MAX_TX_POWER, -- cgit v1.2.3 From 93ada1b3da398b492c45429cef1a1c9651d5c7ba Mon Sep 17 00:00:00 2001 From: Yoav Cohen Date: Tue, 13 Jan 2026 00:05:01 +0200 Subject: ublk: add UBLK_CMD_TRY_STOP_DEV command Add a best-effort stop command, UBLK_CMD_TRY_STOP_DEV, which only stops a ublk device when it has no active openers. Unlike UBLK_CMD_STOP_DEV, this command does not disrupt existing users. New opens are blocked only after disk_openers has reached zero; if the device is busy, the command returns -EBUSY and leaves it running. The ub->block_open flag is used only to close a race with an in-progress open and does not otherwise change open behavior. Advertise support via the UBLK_F_SAFE_STOP_DEV feature flag. Signed-off-by: Yoav Cohen Reviewed-by: Ming Lei Signed-off-by: Jens Axboe --- drivers/block/ublk_drv.c | 44 +++++++++++++++++++++++++++++++++++++++++-- include/uapi/linux/ublk_cmd.h | 9 ++++++++- 2 files changed, 50 insertions(+), 3 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c index 73490890242b..aaf94d2fb789 100644 --- a/drivers/block/ublk_drv.c +++ b/drivers/block/ublk_drv.c @@ -56,6 +56,7 @@ #define UBLK_CMD_DEL_DEV_ASYNC _IOC_NR(UBLK_U_CMD_DEL_DEV_ASYNC) #define UBLK_CMD_UPDATE_SIZE _IOC_NR(UBLK_U_CMD_UPDATE_SIZE) #define UBLK_CMD_QUIESCE_DEV _IOC_NR(UBLK_U_CMD_QUIESCE_DEV) +#define UBLK_CMD_TRY_STOP_DEV _IOC_NR(UBLK_U_CMD_TRY_STOP_DEV) #define UBLK_IO_REGISTER_IO_BUF _IOC_NR(UBLK_U_IO_REGISTER_IO_BUF) #define UBLK_IO_UNREGISTER_IO_BUF _IOC_NR(UBLK_U_IO_UNREGISTER_IO_BUF) @@ -76,7 +77,8 @@ | UBLK_F_QUIESCE \ | UBLK_F_PER_IO_DAEMON \ | UBLK_F_BUF_REG_OFF_DAEMON \ - | (IS_ENABLED(CONFIG_BLK_DEV_INTEGRITY) ? UBLK_F_INTEGRITY : 0)) + | (IS_ENABLED(CONFIG_BLK_DEV_INTEGRITY) ? UBLK_F_INTEGRITY : 0) \ + | UBLK_F_SAFE_STOP_DEV) #define UBLK_F_ALL_RECOVERY_FLAGS (UBLK_F_USER_RECOVERY \ | UBLK_F_USER_RECOVERY_REISSUE \ @@ -243,6 +245,8 @@ struct ublk_device { struct delayed_work exit_work; struct work_struct partition_scan_work; + bool block_open; /* protected by open_mutex */ + struct ublk_queue *queues[]; }; @@ -984,6 +988,9 @@ static int ublk_open(struct gendisk *disk, blk_mode_t mode) return -EPERM; } + if (ub->block_open) + return -ENXIO; + return 0; } @@ -3343,7 +3350,8 @@ static int ublk_ctrl_add_dev(const struct ublksrv_ctrl_cmd *header) ub->dev_info.flags |= UBLK_F_CMD_IOCTL_ENCODE | UBLK_F_URING_CMD_COMP_IN_TASK | UBLK_F_PER_IO_DAEMON | - UBLK_F_BUF_REG_OFF_DAEMON; + UBLK_F_BUF_REG_OFF_DAEMON | + UBLK_F_SAFE_STOP_DEV; /* GET_DATA isn't needed any more with USER_COPY or ZERO COPY */ if (ub->dev_info.flags & (UBLK_F_USER_COPY | UBLK_F_SUPPORT_ZERO_COPY | @@ -3464,6 +3472,34 @@ static void ublk_ctrl_stop_dev(struct ublk_device *ub) ublk_stop_dev(ub); } +static int ublk_ctrl_try_stop_dev(struct ublk_device *ub) +{ + struct gendisk *disk; + int ret = 0; + + disk = ublk_get_disk(ub); + if (!disk) + return -ENODEV; + + mutex_lock(&disk->open_mutex); + if (disk_openers(disk) > 0) { + ret = -EBUSY; + goto unlock; + } + ub->block_open = true; + /* release open_mutex as del_gendisk() will reacquire it */ + mutex_unlock(&disk->open_mutex); + + ublk_ctrl_stop_dev(ub); + goto out; + +unlock: + mutex_unlock(&disk->open_mutex); +out: + ublk_put_disk(disk); + return ret; +} + static int ublk_ctrl_get_dev_info(struct ublk_device *ub, const struct ublksrv_ctrl_cmd *header) { @@ -3859,6 +3895,7 @@ static int ublk_ctrl_uring_cmd_permission(struct ublk_device *ub, case UBLK_CMD_END_USER_RECOVERY: case UBLK_CMD_UPDATE_SIZE: case UBLK_CMD_QUIESCE_DEV: + case UBLK_CMD_TRY_STOP_DEV: mask = MAY_READ | MAY_WRITE; break; default: @@ -3972,6 +4009,9 @@ static int ublk_ctrl_uring_cmd(struct io_uring_cmd *cmd, case UBLK_CMD_QUIESCE_DEV: ret = ublk_ctrl_quiesce_dev(ub, header); break; + case UBLK_CMD_TRY_STOP_DEV: + ret = ublk_ctrl_try_stop_dev(ub); + break; default: ret = -EOPNOTSUPP; break; diff --git a/include/uapi/linux/ublk_cmd.h b/include/uapi/linux/ublk_cmd.h index 61ac5d8e1078..90f47da4f435 100644 --- a/include/uapi/linux/ublk_cmd.h +++ b/include/uapi/linux/ublk_cmd.h @@ -55,7 +55,8 @@ _IOWR('u', 0x15, struct ublksrv_ctrl_cmd) #define UBLK_U_CMD_QUIESCE_DEV \ _IOWR('u', 0x16, struct ublksrv_ctrl_cmd) - +#define UBLK_U_CMD_TRY_STOP_DEV \ + _IOWR('u', 0x17, struct ublksrv_ctrl_cmd) /* * 64bits are enough now, and it should be easy to extend in case of * running out of feature flags @@ -321,6 +322,12 @@ */ #define UBLK_F_INTEGRITY (1ULL << 16) +/* + * The device supports the UBLK_CMD_TRY_STOP_DEV command, which + * allows stopping the device only if there are no openers. + */ +#define UBLK_F_SAFE_STOP_DEV (1ULL << 17) + /* device state */ #define UBLK_S_DEV_DEAD 0 #define UBLK_S_DEV_LIVE 1 -- cgit v1.2.3 From 406fc2e9ca65e0df345ebf4ce95aa87cb6416f35 Mon Sep 17 00:00:00 2001 From: Deepa Guthyappa Madivalara Date: Wed, 10 Dec 2025 10:59:04 -0800 Subject: media: uapi: videodev2: Add support for AV1 stateful decoder Introduce a new pixel format, V4L2_PIX_FMT_AV1, to the Video4Linux2(V4L2) API. This format is intended for AV1 bitstreams in stateful decoding/encoding workflows. The fourcc code 'AV10' is used to distinguish this format from the existing V4L2_PIX_FMT_AV1_FRAME, which is used for stateless AV1 decoder implementation. Reviewed-by: Bryan O'Donoghue Reviewed-by: Nicolas Dufresne Reviewed-by: Hans Verkuil Signed-off-by: Deepa Guthyappa Madivalara Tested-by: Val Packett Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- Documentation/userspace-api/media/v4l/pixfmt-compressed.rst | 8 ++++++++ include/uapi/linux/videodev2.h | 1 + 2 files changed, 9 insertions(+) (limited to 'include/uapi/linux') diff --git a/Documentation/userspace-api/media/v4l/pixfmt-compressed.rst b/Documentation/userspace-api/media/v4l/pixfmt-compressed.rst index c7efb0465db6..235f955d3cd5 100644 --- a/Documentation/userspace-api/media/v4l/pixfmt-compressed.rst +++ b/Documentation/userspace-api/media/v4l/pixfmt-compressed.rst @@ -275,6 +275,14 @@ Compressed Formats of macroblocks to decode a full corresponding frame to the matching capture buffer. + * .. _V4L2-PIX-FMT-AV1: + + - ``V4L2_PIX_FMT_AV1`` + - 'AV01' + - AV1 compressed video frame. This format is adapted for implementing AV1 + pipeline. The decoder implements stateful video decoder and expects one + temporal unit per buffer in OBU stream format. + The encoder generates one Temporal Unit per buffer. .. raw:: latex \normalsize diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h index add08188f068..848e86617d5c 100644 --- a/include/uapi/linux/videodev2.h +++ b/include/uapi/linux/videodev2.h @@ -775,6 +775,7 @@ struct v4l2_pix_format { #define V4L2_PIX_FMT_H264_SLICE v4l2_fourcc('S', '2', '6', '4') /* H264 parsed slices */ #define V4L2_PIX_FMT_HEVC_SLICE v4l2_fourcc('S', '2', '6', '5') /* HEVC parsed slices */ #define V4L2_PIX_FMT_AV1_FRAME v4l2_fourcc('A', 'V', '1', 'F') /* AV1 parsed frame */ +#define V4L2_PIX_FMT_AV1 v4l2_fourcc('A', 'V', '0', '1') /* AV1 */ #define V4L2_PIX_FMT_SPK v4l2_fourcc('S', 'P', 'K', '0') /* Sorenson Spark */ #define V4L2_PIX_FMT_RV30 v4l2_fourcc('R', 'V', '3', '0') /* RealVideo 8 */ #define V4L2_PIX_FMT_RV40 v4l2_fourcc('R', 'V', '4', '0') /* RealVideo 9 & 10 */ -- cgit v1.2.3 From 1bddd758bac21fbbd8a06af746ec7b6d878a9d2c Mon Sep 17 00:00:00 2001 From: Jonas Köppeler Date: Fri, 9 Jan 2026 14:15:34 +0100 Subject: net/sched: sch_cake: share shaper state across sub-instances of cake_mq MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds shared shaper state across the cake instances beneath a cake_mq qdisc. It works by periodically tracking the number of active instances, and scaling the configured rate by the number of active queues. The scan is lockless and simply reads the qlen and the last_active state variable of each of the instances configured beneath the parent cake_mq instance. Locking is not required since the values are only updated by the owning instance, and eventual consistency is sufficient for the purpose of estimating the number of active queues. The interval for scanning the number of active queues is set to 200 us. We found this to be a good tradeoff between overhead and response time. For a detailed analysis of this aspect see the Netdevconf talk: https://netdevconf.info/0x19/docs/netdev-0x19-paper16-talk-paper.pdf Reviewed-by: Jamal Hadi Salim Signed-off-by: Jonas Köppeler Signed-off-by: Toke Høiland-Jørgensen Link: https://patch.msgid.link/20260109-mq-cake-sub-qdisc-v8-5-8d613fece5d8@redhat.com Signed-off-by: Paolo Abeni --- Documentation/netlink/specs/tc.yaml | 3 +++ include/uapi/linux/pkt_sched.h | 1 + net/sched/sch_cake.c | 51 +++++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+) (limited to 'include/uapi/linux') diff --git a/Documentation/netlink/specs/tc.yaml b/Documentation/netlink/specs/tc.yaml index b398f7a46dae..2e663333a279 100644 --- a/Documentation/netlink/specs/tc.yaml +++ b/Documentation/netlink/specs/tc.yaml @@ -2207,6 +2207,9 @@ attribute-sets: - name: blue-timer-us type: s32 + - + name: active-queues + type: u32 - name: cake-tin-stats-attrs name-prefix: tca-cake-tin-stats- diff --git a/include/uapi/linux/pkt_sched.h b/include/uapi/linux/pkt_sched.h index c2da76e78bad..66e8072f44df 100644 --- a/include/uapi/linux/pkt_sched.h +++ b/include/uapi/linux/pkt_sched.h @@ -1036,6 +1036,7 @@ enum { TCA_CAKE_STATS_DROP_NEXT_US, TCA_CAKE_STATS_P_DROP, TCA_CAKE_STATS_BLUE_TIMER_US, + TCA_CAKE_STATS_ACTIVE_QUEUES, __TCA_CAKE_STATS_MAX }; #define TCA_CAKE_STATS_MAX (__TCA_CAKE_STATS_MAX - 1) diff --git a/net/sched/sch_cake.c b/net/sched/sch_cake.c index 2e60e7980558..e30ef7f8ee68 100644 --- a/net/sched/sch_cake.c +++ b/net/sched/sch_cake.c @@ -202,6 +202,7 @@ struct cake_sched_config { u64 rate_bps; u64 interval; u64 target; + u64 sync_time; u32 buffer_config_limit; u32 fwmark_mask; u16 fwmark_shft; @@ -258,6 +259,11 @@ struct cake_sched_data { u16 max_adjlen; u16 min_netlen; u16 min_adjlen; + + /* mq sync state */ + u64 last_checked_active; + u64 last_active; + u32 active_queues; }; enum { @@ -384,6 +390,8 @@ static const u32 inv_sqrt_cache[REC_INV_SQRT_CACHE] = { 1239850263, 1191209601, 1147878294, 1108955788 }; +static void cake_set_rate(struct cake_tin_data *b, u64 rate, u32 mtu, + u64 target_ns, u64 rtt_est_ns); /* http://en.wikipedia.org/wiki/Methods_of_computing_square_roots * new_invsqrt = (invsqrt / 2) * (3 - count * invsqrt^2) * @@ -2004,6 +2012,40 @@ static struct sk_buff *cake_dequeue(struct Qdisc *sch) u64 delay; u32 len; + if (q->config->is_shared && now - q->last_checked_active >= q->config->sync_time) { + struct net_device *dev = qdisc_dev(sch); + struct cake_sched_data *other_priv; + u64 new_rate = q->config->rate_bps; + u64 other_qlen, other_last_active; + struct Qdisc *other_sch; + u32 num_active_qs = 1; + unsigned int ntx; + + for (ntx = 0; ntx < dev->num_tx_queues; ntx++) { + other_sch = rcu_dereference(netdev_get_tx_queue(dev, ntx)->qdisc_sleeping); + other_priv = qdisc_priv(other_sch); + + if (other_priv == q) + continue; + + other_qlen = READ_ONCE(other_sch->q.qlen); + other_last_active = READ_ONCE(other_priv->last_active); + + if (other_qlen || other_last_active > q->last_checked_active) + num_active_qs++; + } + + if (num_active_qs > 1) + new_rate = div64_u64(q->config->rate_bps, num_active_qs); + + /* mtu = 0 is used to only update the rate and not mess with cobalt params */ + cake_set_rate(b, new_rate, 0, 0, 0); + q->last_checked_active = now; + q->active_queues = num_active_qs; + q->rate_ns = b->tin_rate_ns; + q->rate_shft = b->tin_rate_shft; + } + begin: if (!sch->q.qlen) return NULL; @@ -2203,6 +2245,7 @@ retry: b->tin_ecn_mark += !!flow->cvars.ecn_marked; qdisc_bstats_update(sch, skb); + WRITE_ONCE(q->last_active, now); /* collect delay stats */ delay = ktime_to_ns(ktime_sub(now, cobalt_get_enqueue_time(skb))); @@ -2303,6 +2346,9 @@ static void cake_set_rate(struct cake_tin_data *b, u64 rate, u32 mtu, b->tin_rate_ns = rate_ns; b->tin_rate_shft = rate_shft; + if (mtu == 0) + return; + byte_target_ns = (byte_target * rate_ns) >> rate_shft; b->cparams.target = max((byte_target_ns * 3) / 2, target_ns); @@ -2769,6 +2815,7 @@ static void cake_config_init(struct cake_sched_config *q, bool is_shared) */ q->rate_flags |= CAKE_FLAG_SPLIT_GSO; q->is_shared = is_shared; + q->sync_time = 200 * NSEC_PER_USEC; } static int cake_init(struct Qdisc *sch, struct nlattr *opt, @@ -2842,6 +2889,9 @@ static int cake_init(struct Qdisc *sch, struct nlattr *opt, qd->avg_peak_bandwidth = q->rate_bps; qd->min_netlen = ~0; qd->min_adjlen = ~0; + qd->active_queues = 0; + qd->last_checked_active = 0; + return 0; err: kvfree(qd->config); @@ -2974,6 +3024,7 @@ static int cake_dump_stats(struct Qdisc *sch, struct gnet_dump *d) PUT_STAT_U32(MAX_ADJLEN, q->max_adjlen); PUT_STAT_U32(MIN_NETLEN, q->min_netlen); PUT_STAT_U32(MIN_ADJLEN, q->min_adjlen); + PUT_STAT_U32(ACTIVE_QUEUES, q->active_queues); #undef PUT_STAT_U32 #undef PUT_STAT_U64 -- cgit v1.2.3 From f29c852149f94dc1975c64fa919b3dd62db04d23 Mon Sep 17 00:00:00 2001 From: Ainy Kumari Date: Wed, 14 Jan 2026 16:48:52 +0530 Subject: wifi: cfg80211: add support for EPPKE Authentication Protocol Add an extended feature flag NL80211_EXT_FEATURE_EPPKE to allow a driver to indicate support for the Enhanced Privacy Protection Key Exchange (EPPKE) authentication protocol in non-AP STA mode, as defined in "IEEE P802.11bi/D3.0, 12.16.9". In case of SME in userspace, the Authentication frame body is prepared in userspace while the driver finalizes the Authentication frame once it receives the required fields and elements. The driver indicates support for EPPKE using the extended feature flag so that userspace can initiate EPPKE authentication. When the feature flag is set, process EPPKE Authentication frames from userspace in non-AP STA mode. If the flag is not set, reject EPPKE Authentication frames. Define a new authentication type NL80211_AUTHTYPE_EPPKE for EPPKE. Signed-off-by: Ainy Kumari Co-developed-by: Kavita Kavita Signed-off-by: Kavita Kavita Link: https://patch.msgid.link/20260114111900.2196941-2-kavita.kavita@oss.qualcomm.com Signed-off-by: Johannes Berg --- include/linux/ieee80211.h | 1 + include/uapi/linux/nl80211.h | 7 +++++++ net/wireless/nl80211.c | 14 ++++++++++++-- 3 files changed, 20 insertions(+), 2 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/linux/ieee80211.h b/include/linux/ieee80211.h index 96439de55f07..fbde215c25aa 100644 --- a/include/linux/ieee80211.h +++ b/include/linux/ieee80211.h @@ -1351,6 +1351,7 @@ struct ieee80211_tdls_data { #define WLAN_AUTH_FILS_SK 4 #define WLAN_AUTH_FILS_SK_PFS 5 #define WLAN_AUTH_FILS_PK 6 +#define WLAN_AUTH_EPPKE 9 #define WLAN_AUTH_LEAP 128 #define WLAN_AUTH_CHALLENGE_LEN 128 diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index 964e1c779cdd..351d4d176f87 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -5429,6 +5429,7 @@ enum nl80211_bss_status { * @NL80211_AUTHTYPE_FILS_SK: Fast Initial Link Setup shared key * @NL80211_AUTHTYPE_FILS_SK_PFS: Fast Initial Link Setup shared key with PFS * @NL80211_AUTHTYPE_FILS_PK: Fast Initial Link Setup public key + * @NL80211_AUTHTYPE_EPPKE: Enhanced Privacy Protection Key Exchange * @__NL80211_AUTHTYPE_NUM: internal * @NL80211_AUTHTYPE_MAX: maximum valid auth algorithm * @NL80211_AUTHTYPE_AUTOMATIC: determine automatically (if necessary by @@ -5444,6 +5445,7 @@ enum nl80211_auth_type { NL80211_AUTHTYPE_FILS_SK, NL80211_AUTHTYPE_FILS_SK_PFS, NL80211_AUTHTYPE_FILS_PK, + NL80211_AUTHTYPE_EPPKE, /* keep last */ __NL80211_AUTHTYPE_NUM, @@ -6748,6 +6750,10 @@ enum nl80211_feature_flags { * @NL80211_EXT_FEATURE_BEACON_RATE_EHT: Driver supports beacon rate * configuration (AP/mesh) with EHT rates. * + * @NL80211_EXT_FEATURE_EPPKE: Driver supports Enhanced Privacy Protection + * Key Exchange (EPPKE) with user space SME (NL80211_CMD_AUTHENTICATE) + * in non-AP STA mode. + * * @NUM_NL80211_EXT_FEATURES: number of extended features. * @MAX_NL80211_EXT_FEATURES: highest extended feature index. */ @@ -6824,6 +6830,7 @@ enum nl80211_ext_feature_index { NL80211_EXT_FEATURE_DFS_CONCURRENT, NL80211_EXT_FEATURE_SPP_AMSDU_SUPPORT, NL80211_EXT_FEATURE_BEACON_RATE_EHT, + NL80211_EXT_FEATURE_EPPKE, /* add new features before the definition below */ NUM_NL80211_EXT_FEATURES, diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 225580507a4b..8f3a27b7d4fd 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -6473,6 +6473,10 @@ static bool nl80211_valid_auth_type(struct cfg80211_registered_device *rdev, auth_type == NL80211_AUTHTYPE_FILS_SK_PFS || auth_type == NL80211_AUTHTYPE_FILS_PK)) return false; + if (!wiphy_ext_feature_isset(&rdev->wiphy, + NL80211_EXT_FEATURE_EPPKE) && + auth_type == NL80211_AUTHTYPE_EPPKE) + return false; return true; case NL80211_CMD_CONNECT: if (!(rdev->wiphy.features & NL80211_FEATURE_SAE) && @@ -6490,6 +6494,10 @@ static bool nl80211_valid_auth_type(struct cfg80211_registered_device *rdev, NL80211_EXT_FEATURE_FILS_SK_OFFLOAD) && auth_type == NL80211_AUTHTYPE_FILS_SK) return false; + if (!wiphy_ext_feature_isset(&rdev->wiphy, + NL80211_EXT_FEATURE_EPPKE) && + auth_type == NL80211_AUTHTYPE_EPPKE) + return false; return true; case NL80211_CMD_START_AP: if (!wiphy_ext_feature_isset(&rdev->wiphy, @@ -11956,7 +11964,8 @@ static int nl80211_authenticate(struct sk_buff *skb, struct genl_info *info) if ((auth_type == NL80211_AUTHTYPE_SAE || auth_type == NL80211_AUTHTYPE_FILS_SK || auth_type == NL80211_AUTHTYPE_FILS_SK_PFS || - auth_type == NL80211_AUTHTYPE_FILS_PK) && + auth_type == NL80211_AUTHTYPE_FILS_PK || + auth_type == NL80211_AUTHTYPE_EPPKE) && !info->attrs[NL80211_ATTR_AUTH_DATA]) return -EINVAL; @@ -11964,7 +11973,8 @@ static int nl80211_authenticate(struct sk_buff *skb, struct genl_info *info) if (auth_type != NL80211_AUTHTYPE_SAE && auth_type != NL80211_AUTHTYPE_FILS_SK && auth_type != NL80211_AUTHTYPE_FILS_SK_PFS && - auth_type != NL80211_AUTHTYPE_FILS_PK) + auth_type != NL80211_AUTHTYPE_FILS_PK && + auth_type != NL80211_AUTHTYPE_EPPKE) return -EINVAL; req.auth_data = nla_data(info->attrs[NL80211_ATTR_AUTH_DATA]); req.auth_data_len = nla_len(info->attrs[NL80211_ATTR_AUTH_DATA]); -- cgit v1.2.3 From 9d17a040c15d4b99484f13cf08dd45a9e308beeb Mon Sep 17 00:00:00 2001 From: Ainy Kumari Date: Wed, 14 Jan 2026 16:48:53 +0530 Subject: wifi: cfg80211: add feature flag for (re)association frame encryption Introduce an extended feature flag that allows drivers to signal support for encryption of (Re)Association Request and Response frames in both non-AP STA and AP mode, as specified in specification "IEEE P802.11bi/D3.0, 12.16.6". Signed-off-by: Ainy Kumari Signed-off-by: Kavita Kavita Link: https://patch.msgid.link/20260114111900.2196941-3-kavita.kavita@oss.qualcomm.com Signed-off-by: Johannes Berg --- include/uapi/linux/nl80211.h | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index 351d4d176f87..60573334e086 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -6754,6 +6754,11 @@ enum nl80211_feature_flags { * Key Exchange (EPPKE) with user space SME (NL80211_CMD_AUTHENTICATE) * in non-AP STA mode. * + * @NL80211_EXT_FEATURE_ASSOC_FRAME_ENCRYPTION: This specifies that the + * driver supports encryption of (Re)Association Request and Response + * frames in both non‑AP STA and AP mode as specified in + * "IEEE P802.11bi/D3.0, 12.16.6". + * * @NUM_NL80211_EXT_FEATURES: number of extended features. * @MAX_NL80211_EXT_FEATURES: highest extended feature index. */ @@ -6831,6 +6836,7 @@ enum nl80211_ext_feature_index { NL80211_EXT_FEATURE_SPP_AMSDU_SUPPORT, NL80211_EXT_FEATURE_BEACON_RATE_EHT, NL80211_EXT_FEATURE_EPPKE, + NL80211_EXT_FEATURE_ASSOC_FRAME_ENCRYPTION, /* add new features before the definition below */ NUM_NL80211_EXT_FEATURES, -- cgit v1.2.3 From 6ee3a22c61cdf57d71592ec9f3b9439cd5d0c75f Mon Sep 17 00:00:00 2001 From: Sai Pratyusha Magam Date: Wed, 14 Jan 2026 16:48:55 +0530 Subject: wifi: nl80211: Add support for EPP peer indication Introduce a new netlink attribute NL80211_ATTR_EPP_PEER to be used with NL80211_CMD_NEW_STA and NL80211_CMD_ADD_LINK_STA for the userspace to indicate that a non-AP STA is an Enhanced Privacy Protection (EPP) peer. Co-developed-by: Rohan Dutta Signed-off-by: Rohan Dutta Signed-off-by: Sai Pratyusha Magam Signed-off-by: Kavita Kavita Link: https://patch.msgid.link/20260114111900.2196941-5-kavita.kavita@oss.qualcomm.com Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 2 ++ include/uapi/linux/nl80211.h | 5 +++++ net/wireless/nl80211.c | 5 +++++ 3 files changed, 12 insertions(+) (limited to 'include/uapi/linux') diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index cbccedf32228..6d8e35a0dde4 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -1785,6 +1785,7 @@ struct cfg80211_ttlm_params { * present/updated * @eml_cap: EML capabilities of this station * @link_sta_params: link related params. + * @epp_peer: EPP peer indication */ struct station_parameters { struct net_device *vlan; @@ -1811,6 +1812,7 @@ struct station_parameters { bool eml_cap_present; u16 eml_cap; struct link_station_parameters link_sta_params; + bool epp_peer; }; /** diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index 60573334e086..eb92296457c9 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -2973,6 +2973,9 @@ enum nl80211_commands { * primary channel is 2 MHz wide, and the control channel designates * the 1 MHz primary subchannel within that 2 MHz primary. * + * @NL80211_ATTR_EPP_PEER: A flag attribute to indicate if the peer is an EPP + * STA. Used with %NL80211_CMD_NEW_STA and %NL80211_CMD_ADD_LINK_STA + * * @NUM_NL80211_ATTR: total number of nl80211_attrs available * @NL80211_ATTR_MAX: highest attribute number currently defined * @__NL80211_ATTR_AFTER_LAST: internal use @@ -3541,6 +3544,8 @@ enum nl80211_attrs { NL80211_ATTR_S1G_PRIMARY_2MHZ, + NL80211_ATTR_EPP_PEER, + /* add attributes here, update the policy in nl80211.c */ __NL80211_ATTR_AFTER_LAST, diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index df159a5f1816..3d74bea09ba3 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -932,6 +932,7 @@ static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = { NLA_POLICY_NESTED(nl80211_s1g_short_beacon), [NL80211_ATTR_BSS_PARAM] = { .type = NLA_FLAG }, [NL80211_ATTR_S1G_PRIMARY_2MHZ] = { .type = NLA_FLAG }, + [NL80211_ATTR_EPP_PEER] = { .type = NLA_FLAG }, }; /* policy for the key attributes */ @@ -8792,6 +8793,10 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info) goto out; } } + + params.epp_peer = + nla_get_flag(info->attrs[NL80211_ATTR_EPP_PEER]); + err = rdev_add_station(rdev, dev, mac_addr, ¶ms); out: dev_put(params.vlan); -- cgit v1.2.3 From dacbfc16780837aa3e00c684d89492d211fd809f Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Mon, 5 Jan 2026 13:24:03 +0100 Subject: crypto: af_alg - Annotate struct af_alg_iv with __counted_by Add the __counted_by() compiler attribute to the flexible array member 'iv' to improve access bounds-checking via CONFIG_UBSAN_BOUNDS and CONFIG_FORTIFY_SOURCE. Reviewed-by: Simon Horman Signed-off-by: Thorsten Blum Link: https://patch.msgid.link/20260105122402.2685-2-thorsten.blum@linux.dev Signed-off-by: Kees Cook --- include/uapi/linux/if_alg.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/if_alg.h b/include/uapi/linux/if_alg.h index b35871cbeed7..4f51e198ac2e 100644 --- a/include/uapi/linux/if_alg.h +++ b/include/uapi/linux/if_alg.h @@ -42,7 +42,7 @@ struct sockaddr_alg_new { struct af_alg_iv { __u32 ivlen; - __u8 iv[]; + __u8 iv[] __counted_by(ivlen); }; /* Socket options */ -- cgit v1.2.3 From d2bdcde9626cbea0c44a6aaa33b440c8adf81e09 Mon Sep 17 00:00:00 2001 From: Dapeng Mi Date: Wed, 14 Jan 2026 09:17:45 +0800 Subject: perf/x86/intel: Add support for PEBS memory auxiliary info field in DMR With the introduction of the OMR feature, the PEBS memory auxiliary info field for load and store latency events has been restructured for DMR. The memory auxiliary info field's bit[8] indicates whether a L2 cache miss occurred for a memory load or store instruction. If bit[8] is 0, it signifies no L2 cache miss, and bits[7:0] specify the exact cache data source (up to the L2 cache level). If bit[8] is 1, bits[7:0] represent the OMR encoding, indicating the specific L3 cache or memory region involved in the memory access. A significant enhancement is OMR encoding provides up to 8 fine-grained memory regions besides the cache region. A significant enhancement for OMR encoding is the ability to provide up to 8 fine-grained memory regions in addition to the cache region, offering more detailed insights into memory access regions. For detailed information on the memory auxiliary info encoding, please refer to section 16.2 "PEBS LOAD LATENCY AND STORE LATENCY FACILITY" in the ISE documentation. This patch ensures that the PEBS memory auxiliary info field is correctly interpreted and utilized in DMR. Signed-off-by: Dapeng Mi Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260114011750.350569-3-dapeng1.mi@linux.intel.com --- arch/x86/events/intel/ds.c | 140 ++++++++++++++++++++++++++++++++++ arch/x86/events/perf_event.h | 2 + include/uapi/linux/perf_event.h | 27 ++++++- tools/include/uapi/linux/perf_event.h | 27 ++++++- 4 files changed, 190 insertions(+), 6 deletions(-) (limited to 'include/uapi/linux') diff --git a/arch/x86/events/intel/ds.c b/arch/x86/events/intel/ds.c index feb1c3cf63e4..272e652f25fc 100644 --- a/arch/x86/events/intel/ds.c +++ b/arch/x86/events/intel/ds.c @@ -34,6 +34,17 @@ struct pebs_record_32 { */ +union omr_encoding { + struct { + u8 omr_source : 4; + u8 omr_remote : 1; + u8 omr_hitm : 1; + u8 omr_snoop : 1; + u8 omr_promoted : 1; + }; + u8 omr_full; +}; + union intel_x86_pebs_dse { u64 val; struct { @@ -73,6 +84,18 @@ union intel_x86_pebs_dse { unsigned int lnc_addr_blk:1; unsigned int ld_reserved6:18; }; + struct { + unsigned int pnc_dse: 8; + unsigned int pnc_l2_miss:1; + unsigned int pnc_stlb_clean_hit:1; + unsigned int pnc_stlb_any_hit:1; + unsigned int pnc_stlb_miss:1; + unsigned int pnc_locked:1; + unsigned int pnc_data_blk:1; + unsigned int pnc_addr_blk:1; + unsigned int pnc_fb_full:1; + unsigned int ld_reserved8:16; + }; }; @@ -228,6 +251,85 @@ void __init intel_pmu_pebs_data_source_lnl(void) __intel_pmu_pebs_data_source_cmt(data_source); } +/* Version for Panthercove and later */ + +/* L2 hit */ +#define PNC_PEBS_DATA_SOURCE_MAX 16 +static u64 pnc_pebs_l2_hit_data_source[PNC_PEBS_DATA_SOURCE_MAX] = { + P(OP, LOAD) | P(LVL, NA) | LEVEL(NA) | P(SNOOP, NA), /* 0x00: non-cache access */ + OP_LH | LEVEL(L0) | P(SNOOP, NONE), /* 0x01: L0 hit */ + OP_LH | P(LVL, L1) | LEVEL(L1) | P(SNOOP, NONE), /* 0x02: L1 hit */ + OP_LH | P(LVL, LFB) | LEVEL(LFB) | P(SNOOP, NONE), /* 0x03: L1 Miss Handling Buffer hit */ + OP_LH | P(LVL, L2) | LEVEL(L2) | P(SNOOP, NONE), /* 0x04: L2 Hit Clean */ + 0, /* 0x05: Reserved */ + 0, /* 0x06: Reserved */ + OP_LH | P(LVL, L2) | LEVEL(L2) | P(SNOOP, HIT), /* 0x07: L2 Hit Snoop HIT */ + OP_LH | P(LVL, L2) | LEVEL(L2) | P(SNOOP, HITM), /* 0x08: L2 Hit Snoop Hit Modified */ + OP_LH | P(LVL, L2) | LEVEL(L2) | P(SNOOP, MISS), /* 0x09: Prefetch Promotion */ + OP_LH | P(LVL, L2) | LEVEL(L2) | P(SNOOP, MISS), /* 0x0a: Cross Core Prefetch Promotion */ + 0, /* 0x0b: Reserved */ + 0, /* 0x0c: Reserved */ + 0, /* 0x0d: Reserved */ + 0, /* 0x0e: Reserved */ + OP_LH | P(LVL, UNC) | LEVEL(NA) | P(SNOOP, NONE), /* 0x0f: uncached */ +}; + +/* L2 miss */ +#define OMR_DATA_SOURCE_MAX 16 +static u64 omr_data_source[OMR_DATA_SOURCE_MAX] = { + P(OP, LOAD) | P(LVL, NA) | LEVEL(NA) | P(SNOOP, NA), /* 0x00: invalid */ + 0, /* 0x01: Reserved */ + OP_LH | P(LVL, L3) | LEVEL(L3) | P(REGION, L_SHARE), /* 0x02: local CA shared cache */ + OP_LH | P(LVL, L3) | LEVEL(L3) | P(REGION, L_NON_SHARE),/* 0x03: local CA non-shared cache */ + OP_LH | P(LVL, L3) | LEVEL(L3) | P(REGION, O_IO), /* 0x04: other CA IO agent */ + OP_LH | P(LVL, L3) | LEVEL(L3) | P(REGION, O_SHARE), /* 0x05: other CA shared cache */ + OP_LH | P(LVL, L3) | LEVEL(L3) | P(REGION, O_NON_SHARE),/* 0x06: other CA non-shared cache */ + OP_LH | LEVEL(RAM) | P(REGION, MMIO), /* 0x07: MMIO */ + OP_LH | LEVEL(RAM) | P(REGION, MEM0), /* 0x08: Memory region 0 */ + OP_LH | LEVEL(RAM) | P(REGION, MEM1), /* 0x09: Memory region 1 */ + OP_LH | LEVEL(RAM) | P(REGION, MEM2), /* 0x0a: Memory region 2 */ + OP_LH | LEVEL(RAM) | P(REGION, MEM3), /* 0x0b: Memory region 3 */ + OP_LH | LEVEL(RAM) | P(REGION, MEM4), /* 0x0c: Memory region 4 */ + OP_LH | LEVEL(RAM) | P(REGION, MEM5), /* 0x0d: Memory region 5 */ + OP_LH | LEVEL(RAM) | P(REGION, MEM6), /* 0x0e: Memory region 6 */ + OP_LH | LEVEL(RAM) | P(REGION, MEM7), /* 0x0f: Memory region 7 */ +}; + +static u64 parse_omr_data_source(u8 dse) +{ + union omr_encoding omr; + u64 val = 0; + + omr.omr_full = dse; + val = omr_data_source[omr.omr_source]; + if (omr.omr_source > 0x1 && omr.omr_source < 0x7) + val |= omr.omr_remote ? P(LVL, REM_CCE1) : 0; + else if (omr.omr_source > 0x7) + val |= omr.omr_remote ? P(LVL, REM_RAM1) : P(LVL, LOC_RAM); + + if (omr.omr_remote) + val |= REM; + + val |= omr.omr_hitm ? P(SNOOP, HITM) : P(SNOOP, HIT); + + if (omr.omr_source == 0x2) { + u8 snoop = omr.omr_snoop | omr.omr_promoted; + + if (snoop == 0x0) + val |= P(SNOOP, NA); + else if (snoop == 0x1) + val |= P(SNOOP, MISS); + else if (snoop == 0x2) + val |= P(SNOOP, HIT); + else if (snoop == 0x3) + val |= P(SNOOP, NONE); + } else if (omr.omr_source > 0x2 && omr.omr_source < 0x7) { + val |= omr.omr_snoop ? P(SNOOPX, FWD) : 0; + } + + return val; +} + static u64 precise_store_data(u64 status) { union intel_x86_pebs_dse dse; @@ -411,6 +513,44 @@ u64 arl_h_latency_data(struct perf_event *event, u64 status) return lnl_latency_data(event, status); } +u64 pnc_latency_data(struct perf_event *event, u64 status) +{ + union intel_x86_pebs_dse dse; + union perf_mem_data_src src; + u64 val; + + dse.val = status; + + if (!dse.pnc_l2_miss) + val = pnc_pebs_l2_hit_data_source[dse.pnc_dse & 0xf]; + else + val = parse_omr_data_source(dse.pnc_dse); + + if (!val) + val = P(OP, LOAD) | LEVEL(NA) | P(SNOOP, NA); + + if (dse.pnc_stlb_miss) + val |= P(TLB, MISS) | P(TLB, L2); + else + val |= P(TLB, HIT) | P(TLB, L1) | P(TLB, L2); + + if (dse.pnc_locked) + val |= P(LOCK, LOCKED); + + if (dse.pnc_data_blk) + val |= P(BLK, DATA); + if (dse.pnc_addr_blk) + val |= P(BLK, ADDR); + if (!dse.pnc_data_blk && !dse.pnc_addr_blk) + val |= P(BLK, NA); + + src.val = val; + if (event->hw.flags & PERF_X86_EVENT_PEBS_ST_HSW) + src.mem_op = P(OP, STORE); + + return src.val; +} + static u64 load_latency_data(struct perf_event *event, u64 status) { union intel_x86_pebs_dse dse; diff --git a/arch/x86/events/perf_event.h b/arch/x86/events/perf_event.h index 586e3fdfe6d8..bd501c2a0f73 100644 --- a/arch/x86/events/perf_event.h +++ b/arch/x86/events/perf_event.h @@ -1664,6 +1664,8 @@ u64 lnl_latency_data(struct perf_event *event, u64 status); u64 arl_h_latency_data(struct perf_event *event, u64 status); +u64 pnc_latency_data(struct perf_event *event, u64 status); + extern struct event_constraint intel_core2_pebs_event_constraints[]; extern struct event_constraint intel_atom_pebs_event_constraints[]; diff --git a/include/uapi/linux/perf_event.h b/include/uapi/linux/perf_event.h index c44a8fb3e418..533393ec94d0 100644 --- a/include/uapi/linux/perf_event.h +++ b/include/uapi/linux/perf_event.h @@ -1330,14 +1330,16 @@ union perf_mem_data_src { mem_snoopx : 2, /* Snoop mode, ext */ mem_blk : 3, /* Access blocked */ mem_hops : 3, /* Hop level */ - mem_rsvd : 18; + mem_region : 5, /* cache/memory regions */ + mem_rsvd : 13; }; }; #elif defined(__BIG_ENDIAN_BITFIELD) union perf_mem_data_src { __u64 val; struct { - __u64 mem_rsvd : 18, + __u64 mem_rsvd : 13, + mem_region : 5, /* cache/memory regions */ mem_hops : 3, /* Hop level */ mem_blk : 3, /* Access blocked */ mem_snoopx : 2, /* Snoop mode, ext */ @@ -1394,7 +1396,7 @@ union perf_mem_data_src { #define PERF_MEM_LVLNUM_L4 0x0004 /* L4 */ #define PERF_MEM_LVLNUM_L2_MHB 0x0005 /* L2 Miss Handling Buffer */ #define PERF_MEM_LVLNUM_MSC 0x0006 /* Memory-side Cache */ -/* 0x007 available */ +#define PERF_MEM_LVLNUM_L0 0x0007 /* L0 */ #define PERF_MEM_LVLNUM_UNC 0x0008 /* Uncached */ #define PERF_MEM_LVLNUM_CXL 0x0009 /* CXL */ #define PERF_MEM_LVLNUM_IO 0x000a /* I/O */ @@ -1447,6 +1449,25 @@ union perf_mem_data_src { /* 5-7 available */ #define PERF_MEM_HOPS_SHIFT 43 +/* Cache/Memory region */ +#define PERF_MEM_REGION_NA 0x0 /* Invalid */ +#define PERF_MEM_REGION_RSVD 0x01 /* Reserved */ +#define PERF_MEM_REGION_L_SHARE 0x02 /* Local CA shared cache */ +#define PERF_MEM_REGION_L_NON_SHARE 0x03 /* Local CA non-shared cache */ +#define PERF_MEM_REGION_O_IO 0x04 /* Other CA IO agent */ +#define PERF_MEM_REGION_O_SHARE 0x05 /* Other CA shared cache */ +#define PERF_MEM_REGION_O_NON_SHARE 0x06 /* Other CA non-shared cache */ +#define PERF_MEM_REGION_MMIO 0x07 /* MMIO */ +#define PERF_MEM_REGION_MEM0 0x08 /* Memory region 0 */ +#define PERF_MEM_REGION_MEM1 0x09 /* Memory region 1 */ +#define PERF_MEM_REGION_MEM2 0x0a /* Memory region 2 */ +#define PERF_MEM_REGION_MEM3 0x0b /* Memory region 3 */ +#define PERF_MEM_REGION_MEM4 0x0c /* Memory region 4 */ +#define PERF_MEM_REGION_MEM5 0x0d /* Memory region 5 */ +#define PERF_MEM_REGION_MEM6 0x0e /* Memory region 6 */ +#define PERF_MEM_REGION_MEM7 0x0f /* Memory region 7 */ +#define PERF_MEM_REGION_SHIFT 46 + #define PERF_MEM_S(a, s) \ (((__u64)PERF_MEM_##a##_##s) << PERF_MEM_##a##_SHIFT) diff --git a/tools/include/uapi/linux/perf_event.h b/tools/include/uapi/linux/perf_event.h index c44a8fb3e418..d4b99610a3b0 100644 --- a/tools/include/uapi/linux/perf_event.h +++ b/tools/include/uapi/linux/perf_event.h @@ -1330,14 +1330,16 @@ union perf_mem_data_src { mem_snoopx : 2, /* Snoop mode, ext */ mem_blk : 3, /* Access blocked */ mem_hops : 3, /* Hop level */ - mem_rsvd : 18; + mem_region : 5, /* cache/memory regions */ + mem_rsvd : 13; }; }; #elif defined(__BIG_ENDIAN_BITFIELD) union perf_mem_data_src { __u64 val; struct { - __u64 mem_rsvd : 18, + __u64 mem_rsvd : 13, + mem_region : 5, /* cache/memory regions */ mem_hops : 3, /* Hop level */ mem_blk : 3, /* Access blocked */ mem_snoopx : 2, /* Snoop mode, ext */ @@ -1394,7 +1396,7 @@ union perf_mem_data_src { #define PERF_MEM_LVLNUM_L4 0x0004 /* L4 */ #define PERF_MEM_LVLNUM_L2_MHB 0x0005 /* L2 Miss Handling Buffer */ #define PERF_MEM_LVLNUM_MSC 0x0006 /* Memory-side Cache */ -/* 0x007 available */ +#define PERF_MEM_LVLNUM_L0 0x0007 /* L0 */ #define PERF_MEM_LVLNUM_UNC 0x0008 /* Uncached */ #define PERF_MEM_LVLNUM_CXL 0x0009 /* CXL */ #define PERF_MEM_LVLNUM_IO 0x000a /* I/O */ @@ -1447,6 +1449,25 @@ union perf_mem_data_src { /* 5-7 available */ #define PERF_MEM_HOPS_SHIFT 43 +/* Cache/Memory region */ +#define PERF_MEM_REGION_NA 0x0 /* Invalid */ +#define PERF_MEM_REGION_RSVD 0x01 /* Reserved */ +#define PERF_MEM_REGION_L_SHARE 0x02 /* Local CA shared cache */ +#define PERF_MEM_REGION_L_NON_SHARE 0x03 /* Local CA non-shared cache */ +#define PERF_MEM_REGION_O_IO 0x04 /* Other CA IO agent */ +#define PERF_MEM_REGION_O_SHARE 0x05 /* Other CA shared cache */ +#define PERF_MEM_REGION_O_NON_SHARE 0x06 /* Other CA non-shared cache */ +#define PERF_MEM_REGION_MMIO 0x07 /* MMIO */ +#define PERF_MEM_REGION_MEM0 0x08 /* Memory region 0 */ +#define PERF_MEM_REGION_MEM1 0x09 /* Memory region 1 */ +#define PERF_MEM_REGION_MEM2 0x0a /* Memory region 2 */ +#define PERF_MEM_REGION_MEM3 0x0b /* Memory region 3 */ +#define PERF_MEM_REGION_MEM4 0x0c /* Memory region 4 */ +#define PERF_MEM_REGION_MEM5 0x0d /* Memory region 5 */ +#define PERF_MEM_REGION_MEM6 0x0e /* Memory region 6 */ +#define PERF_MEM_REGION_MEM7 0x0f /* Memory region 7 */ +#define PERF_MEM_REGION_SHIFT 46 + #define PERF_MEM_S(a, s) \ (((__u64)PERF_MEM_##a##_##s) << PERF_MEM_##a##_SHIFT) -- cgit v1.2.3 From 567873005dca1be0a3b3e2e309a8f0de14d2b827 Mon Sep 17 00:00:00 2001 From: Gal Pressman Date: Thu, 15 Jan 2026 08:05:44 +0200 Subject: ethtool: Clarify len/n_stats fields in/out semantics Document that the 'len' field in ethtool_gstrings and 'n_stats' field in ethtool_stats optionally serve dual purposes: on entry they specify the number of items requested, and on return they indicate the number actually returned (which is not necessarily the same). Signed-off-by: Gal Pressman Reviewed-by: Dragos Tatulea Link: https://patch.msgid.link/20260115060544.481550-1-gal@nvidia.com Signed-off-by: Jakub Kicinski --- include/uapi/linux/ethtool.h | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/ethtool.h b/include/uapi/linux/ethtool.h index 5daa8f225b67..bbfe6e1cf01b 100644 --- a/include/uapi/linux/ethtool.h +++ b/include/uapi/linux/ethtool.h @@ -1096,13 +1096,20 @@ enum ethtool_module_fw_flash_status { * struct ethtool_gstrings - string set for data tagging * @cmd: Command number = %ETHTOOL_GSTRINGS * @string_set: String set ID; one of &enum ethtool_stringset - * @len: On return, the number of strings in the string set + * @len: Number of strings in the string set * @data: Buffer for strings. Each string is null-padded to a size of * %ETH_GSTRING_LEN. * * Users must use %ETHTOOL_GSSET_INFO to find the number of strings in * the string set. They must allocate a buffer of the appropriate * size immediately following this structure. + * + * Setting @len on input is optional (though preferred), but must be zeroed + * otherwise. + * When set, @len will return the requested count if it matches the actual + * count; otherwise, it will be zero. + * This prevents issues when the number of strings is different than the + * userspace allocation. */ struct ethtool_gstrings { __u32 cmd; @@ -1179,13 +1186,20 @@ struct ethtool_test { /** * struct ethtool_stats - device-specific statistics * @cmd: Command number = %ETHTOOL_GSTATS - * @n_stats: On return, the number of statistics + * @n_stats: Number of statistics * @data: Array of statistics * * Users must use %ETHTOOL_GSSET_INFO or %ETHTOOL_GDRVINFO to find the * number of statistics that will be returned. They must allocate a * buffer of the appropriate size (8 * number of statistics) * immediately following this structure. + * + * Setting @n_stats on input is optional (though preferred), but must be zeroed + * otherwise. + * When set, @n_stats will return the requested count if it matches the actual + * count; otherwise, it will be zero. + * This prevents issues when the number of stats is different than the + * userspace allocation. */ struct ethtool_stats { __u32 cmd; -- cgit v1.2.3 From d89ccbf3dde727d91a242a5a3f3b70a90579b057 Mon Sep 17 00:00:00 2001 From: Richard Leitner Date: Tue, 9 Dec 2025 23:44:36 +0100 Subject: media: v4l: ctrls: add a control for flash/strobe duration Add a V4L2_CID_FLASH_DURATION control to set the duration of a flash/strobe pulse. This controls the length of the flash/strobe pulse output by device (typically a camera sensor) and connected to the flash controller. This is different to the V4L2_CID_FLASH_TIMEOUT control, which is implemented by the flash controller and defines a limit after which the flash is "forcefully" turned off again. Reviewed-by: Laurent Pinchart Signed-off-by: Richard Leitner Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/v4l2-core/v4l2-ctrls-defs.c | 1 + include/uapi/linux/v4l2-controls.h | 1 + 2 files changed, 2 insertions(+) (limited to 'include/uapi/linux') diff --git a/drivers/media/v4l2-core/v4l2-ctrls-defs.c b/drivers/media/v4l2-core/v4l2-ctrls-defs.c index ad41f65374e2..4848423205ff 100644 --- a/drivers/media/v4l2-core/v4l2-ctrls-defs.c +++ b/drivers/media/v4l2-core/v4l2-ctrls-defs.c @@ -1135,6 +1135,7 @@ const char *v4l2_ctrl_get_name(u32 id) case V4L2_CID_FLASH_FAULT: return "Faults"; case V4L2_CID_FLASH_CHARGE: return "Charge"; case V4L2_CID_FLASH_READY: return "Ready to Strobe"; + case V4L2_CID_FLASH_DURATION: return "Strobe Duration"; /* JPEG encoder controls */ /* Keep the order of the 'case's the same as in v4l2-controls.h! */ diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h index f84ed133a6c9..357845830fe9 100644 --- a/include/uapi/linux/v4l2-controls.h +++ b/include/uapi/linux/v4l2-controls.h @@ -1192,6 +1192,7 @@ enum v4l2_flash_strobe_source { #define V4L2_CID_FLASH_CHARGE (V4L2_CID_FLASH_CLASS_BASE + 11) #define V4L2_CID_FLASH_READY (V4L2_CID_FLASH_CLASS_BASE + 12) +#define V4L2_CID_FLASH_DURATION (V4L2_CID_FLASH_CLASS_BASE + 13) /* JPEG-class control IDs */ -- cgit v1.2.3 From 5be4154f6255d92d9d2ad5da658d7d33a655386f Mon Sep 17 00:00:00 2001 From: Richard Leitner Date: Tue, 9 Dec 2025 23:44:37 +0100 Subject: media: v4l: ctrls: add a control for enabling strobe output Add a control V4L2_CID_FLASH_STROBE_OE to en- or disable the strobe output of v4l2 devices (most likely sensors). Signed-off-by: Richard Leitner Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/v4l2-core/v4l2-ctrls-defs.c | 2 ++ include/uapi/linux/v4l2-controls.h | 1 + 2 files changed, 3 insertions(+) (limited to 'include/uapi/linux') diff --git a/drivers/media/v4l2-core/v4l2-ctrls-defs.c b/drivers/media/v4l2-core/v4l2-ctrls-defs.c index 4848423205ff..765aeeec84fe 100644 --- a/drivers/media/v4l2-core/v4l2-ctrls-defs.c +++ b/drivers/media/v4l2-core/v4l2-ctrls-defs.c @@ -1136,6 +1136,7 @@ const char *v4l2_ctrl_get_name(u32 id) case V4L2_CID_FLASH_CHARGE: return "Charge"; case V4L2_CID_FLASH_READY: return "Ready to Strobe"; case V4L2_CID_FLASH_DURATION: return "Strobe Duration"; + case V4L2_CID_FLASH_STROBE_OE: return "Strobe Output Enable"; /* JPEG encoder controls */ /* Keep the order of the 'case's the same as in v4l2-controls.h! */ @@ -1282,6 +1283,7 @@ void v4l2_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type, case V4L2_CID_FLASH_STROBE_STATUS: case V4L2_CID_FLASH_CHARGE: case V4L2_CID_FLASH_READY: + case V4L2_CID_FLASH_STROBE_OE: case V4L2_CID_MPEG_VIDEO_DECODER_MPEG4_DEBLOCK_FILTER: case V4L2_CID_MPEG_VIDEO_DECODER_SLICE_INTERFACE: case V4L2_CID_MPEG_VIDEO_DEC_DISPLAY_DELAY_ENABLE: diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h index 357845830fe9..572622e4535e 100644 --- a/include/uapi/linux/v4l2-controls.h +++ b/include/uapi/linux/v4l2-controls.h @@ -1193,6 +1193,7 @@ enum v4l2_flash_strobe_source { #define V4L2_CID_FLASH_CHARGE (V4L2_CID_FLASH_CLASS_BASE + 11) #define V4L2_CID_FLASH_READY (V4L2_CID_FLASH_CLASS_BASE + 12) #define V4L2_CID_FLASH_DURATION (V4L2_CID_FLASH_CLASS_BASE + 13) +#define V4L2_CID_FLASH_STROBE_OE (V4L2_CID_FLASH_CLASS_BASE + 14) /* JPEG-class control IDs */ -- cgit v1.2.3 From 10d28cffb3f6ec7ad67f0a4cd32c2afa92909452 Mon Sep 17 00:00:00 2001 From: Ian Abbott Date: Wed, 3 Dec 2025 16:24:38 +0000 Subject: comedi: Fix getting range information for subdevices 16 to 255 The `COMEDI_RANGEINFO` ioctl does not work properly for subdevice indices above 15. Currently, the only in-tree COMEDI drivers that support more than 16 subdevices are the "8255" driver and the "comedi_bond" driver. Making the ioctl work for subdevice indices up to 255 is achievable. It needs minor changes to the handling of the `COMEDI_RANGEINFO` and `COMEDI_CHANINFO` ioctls that should be mostly harmless to user-space, apart from making them less broken. Details follow... The `COMEDI_RANGEINFO` ioctl command gets the list of supported ranges (usually with units of volts or milliamps) for a COMEDI subdevice or channel. (Only some subdevices have per-channel range tables, indicated by the `SDF_RANGETYPE` flag in the subdevice information.) It uses a `range_type` value and a user-space pointer, both supplied by user-space, but the `range_type` value should match what was obtained using the `COMEDI_CHANINFO` ioctl (if the subdevice has per-channel range tables) or `COMEDI_SUBDINFO` ioctl (if the subdevice uses a single range table for all channels). Bits 15 to 0 of the `range_type` value contain the length of the range table, which is the only part that user-space should care about (so it can use a suitably sized buffer to fetch the range table). Bits 23 to 16 store the channel index, which is assumed to be no more than 255 if the subdevice has per-channel range tables, and is set to 0 if the subdevice has a single range table. For `range_type` values produced by the `COMEDI_SUBDINFO` ioctl, bits 31 to 24 contain the subdevice index, which is assumed to be no more than 255. But for `range_type` values produced by the `COMEDI_CHANINFO` ioctl, bits 27 to 24 contain the subdevice index, which is assumed to be no more than 15, and bits 31 to 28 contain the COMEDI device's minor device number for some unknown reason lost in the mists of time. The `COMEDI_RANGEINFO` ioctl extract the length from bits 15 to 0 of the user-supplied `range_type` value, extracts the channel index from bits 23 to 16 (only used if the subdevice has per-channel range tables), extracts the subdevice index from bits 27 to 24, and ignores bits 31 to 28. So for subdevice indices 16 to 255, the `COMEDI_SUBDINFO` or `COMEDI_CHANINFO` ioctl will report a `range_type` value that doesn't work with the `COMEDI_RANGEINFO` ioctl. It will either get the range table for the subdevice index modulo 16, or will fail with `-EINVAL`. To fix this, always use bits 31 to 24 of the `range_type` value to hold the subdevice index (assumed to be no more than 255). This affects the `COMEDI_CHANINFO` and `COMEDI_RANGEINFO` ioctls. There should not be anything in user-space that depends on the old, broken usage, although it may now see different values in bits 31 to 28 of the `range_type` values reported by the `COMEDI_CHANINFO` ioctl for subdevices that have per-channel subdevices. User-space should not be trying to decode bits 31 to 16 of the `range_type` values anyway. Fixes: ed9eccbe8970 ("Staging: add comedi core") Cc: stable@vger.kernel.org #5.17+ Signed-off-by: Ian Abbott Link: https://patch.msgid.link/20251203162438.176841-1-abbotti@mev.co.uk Signed-off-by: Greg Kroah-Hartman --- drivers/comedi/comedi_fops.c | 2 +- drivers/comedi/range.c | 2 +- include/uapi/linux/comedi.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/comedi/comedi_fops.c b/drivers/comedi/comedi_fops.c index 657c98cd723e..2c3eb9e89571 100644 --- a/drivers/comedi/comedi_fops.c +++ b/drivers/comedi/comedi_fops.c @@ -1155,7 +1155,7 @@ static int do_chaninfo_ioctl(struct comedi_device *dev, for (i = 0; i < s->n_chan; i++) { int x; - x = (dev->minor << 28) | (it->subdev << 24) | (i << 16) | + x = (it->subdev << 24) | (i << 16) | (s->range_table_list[i]->length); if (put_user(x, it->rangelist + i)) return -EFAULT; diff --git a/drivers/comedi/range.c b/drivers/comedi/range.c index 8f43cf88d784..5b8f662365e3 100644 --- a/drivers/comedi/range.c +++ b/drivers/comedi/range.c @@ -52,7 +52,7 @@ int do_rangeinfo_ioctl(struct comedi_device *dev, const struct comedi_lrange *lr; struct comedi_subdevice *s; - subd = (it->range_type >> 24) & 0xf; + subd = (it->range_type >> 24) & 0xff; chan = (it->range_type >> 16) & 0xff; if (!dev->attached) diff --git a/include/uapi/linux/comedi.h b/include/uapi/linux/comedi.h index 7314e5ee0a1e..798ec9a39e12 100644 --- a/include/uapi/linux/comedi.h +++ b/include/uapi/linux/comedi.h @@ -640,7 +640,7 @@ struct comedi_chaninfo { /** * struct comedi_rangeinfo - used to retrieve the range table for a channel - * @range_type: Encodes subdevice index (bits 27:24), channel index + * @range_type: Encodes subdevice index (bits 31:24), channel index * (bits 23:16) and range table length (bits 15:0). * @range_ptr: Pointer to array of @struct comedi_krange to be filled * in with the range table for the channel or subdevice. -- cgit v1.2.3 From 9b8a0ba68246a61d903ce62c35c303b1501df28b Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 29 Dec 2025 14:03:24 +0100 Subject: mount: add OPEN_TREE_NAMESPACE When creating containers the setup usually involves using CLONE_NEWNS via clone3() or unshare(). This copies the caller's complete mount namespace. The runtime will also assemble a new rootfs and then use pivot_root() to switch the old mount tree with the new rootfs. Afterward it will recursively umount the old mount tree thereby getting rid of all mounts. On a basic system here where the mount table isn't particularly large this still copies about 30 mounts. Copying all of these mounts only to get rid of them later is pretty wasteful. This is exacerbated if intermediary mount namespaces are used that only exist for a very short amount of time and are immediately destroyed again causing a ton of mounts to be copied and destroyed needlessly. With a large mount table and a system where thousands or ten-thousands of containers are spawned in parallel this quickly becomes a bottleneck increasing contention on the semaphore. Extend open_tree() with a new OPEN_TREE_NAMESPACE flag. Similar to OPEN_TREE_CLONE only the indicated mount tree is copied. Instead of returning a file descriptor referring to that mount tree OPEN_TREE_NAMESPACE will cause open_tree() to return a file descriptor to a new mount namespace. In that new mount namespace the copied mount tree has been mounted on top of a copy of the real rootfs. The caller can setns() into that mount namespace and perform any additionally required setup such as move_mount() detached mounts in there. This allows OPEN_TREE_NAMESPACE to function as a combined unshare(CLONE_NEWNS) and pivot_root(). A caller may for example choose to create an extremely minimal rootfs: fd_mntns = open_tree(-EBADF, "/var/lib/containers/wootwoot", OPEN_TREE_NAMESPACE); This will create a mount namespace where "wootwoot" has become the rootfs mounted on top of the real rootfs. The caller can now setns() into this new mount namespace and assemble additional mounts. This also works with user namespaces: unshare(CLONE_NEWUSER); fd_mntns = open_tree(-EBADF, "/var/lib/containers/wootwoot", OPEN_TREE_NAMESPACE); which creates a new mount namespace owned by the earlier created user namespace with "wootwoot" as the rootfs mounted on top of the real rootfs. Link: https://patch.msgid.link/20251229-work-empty-namespace-v1-1-bfb24c7b061f@kernel.org Tested-by: Jeff Layton Reviewed-by: Aleksa Sarai Reviewed-by: Jeff Layton Suggested-by: Christian Brauner Suggested-by: Aleksa Sarai Signed-off-by: Christian Brauner --- fs/internal.h | 1 + fs/namespace.c | 163 ++++++++++++++++++++++++++++++++++++++++----- fs/nsfs.c | 13 ++++ include/uapi/linux/mount.h | 3 +- 4 files changed, 163 insertions(+), 17 deletions(-) (limited to 'include/uapi/linux') diff --git a/fs/internal.h b/fs/internal.h index e333b105337a..f6932e639f36 100644 --- a/fs/internal.h +++ b/fs/internal.h @@ -246,6 +246,7 @@ extern void mnt_pin_kill(struct mount *m); */ extern const struct dentry_operations ns_dentry_operations; int open_namespace(struct ns_common *ns); +struct file *open_namespace_file(struct ns_common *ns); /* * fs/stat.c: diff --git a/fs/namespace.c b/fs/namespace.c index ec3b16fedd9f..59557019e422 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -2796,6 +2796,9 @@ static inline void unlock_mount(struct pinned_mountpoint *m) __unlock_mount(m); } +static void lock_mount_exact(const struct path *path, + struct pinned_mountpoint *mp); + #define LOCK_MOUNT_MAYBE_BENEATH(mp, path, beneath) \ struct pinned_mountpoint mp __cleanup(unlock_mount) = {}; \ do_lock_mount((path), &mp, (beneath)) @@ -2946,10 +2949,11 @@ static inline bool may_copy_tree(const struct path *path) return check_anonymous_mnt(mnt); } - -static struct mount *__do_loopback(const struct path *old_path, int recurse) +static struct mount *__do_loopback(const struct path *old_path, + unsigned int flags, unsigned int copy_flags) { struct mount *old = real_mount(old_path->mnt); + bool recurse = flags & AT_RECURSIVE; if (IS_MNT_UNBINDABLE(old)) return ERR_PTR(-EINVAL); @@ -2960,10 +2964,22 @@ static struct mount *__do_loopback(const struct path *old_path, int recurse) if (!recurse && __has_locked_children(old, old_path->dentry)) return ERR_PTR(-EINVAL); + /* + * When creating a new mount namespace we don't want to copy over + * mounts of mount namespaces to avoid the risk of cycles and also to + * minimize the default complex interdependencies between mount + * namespaces. + * + * We could ofc just check whether all mount namespace files aren't + * creating cycles but really let's keep this simple. + */ + if (!(flags & OPEN_TREE_NAMESPACE)) + copy_flags |= CL_COPY_MNT_NS_FILE; + if (recurse) - return copy_tree(old, old_path->dentry, CL_COPY_MNT_NS_FILE); - else - return clone_mnt(old, old_path->dentry, 0); + return copy_tree(old, old_path->dentry, copy_flags); + + return clone_mnt(old, old_path->dentry, copy_flags); } /* @@ -2974,7 +2990,9 @@ static int do_loopback(const struct path *path, const char *old_name, { struct path old_path __free(path_put) = {}; struct mount *mnt = NULL; + unsigned int flags = recurse ? AT_RECURSIVE : 0; int err; + if (!old_name || !*old_name) return -EINVAL; err = kern_path(old_name, LOOKUP_FOLLOW|LOOKUP_AUTOMOUNT, &old_path); @@ -2991,7 +3009,7 @@ static int do_loopback(const struct path *path, const char *old_name, if (!check_mnt(mp.parent)) return -EINVAL; - mnt = __do_loopback(&old_path, recurse); + mnt = __do_loopback(&old_path, flags, 0); if (IS_ERR(mnt)) return PTR_ERR(mnt); @@ -3004,7 +3022,7 @@ static int do_loopback(const struct path *path, const char *old_name, return err; } -static struct mnt_namespace *get_detached_copy(const struct path *path, bool recursive) +static struct mnt_namespace *get_detached_copy(const struct path *path, unsigned int flags) { struct mnt_namespace *ns, *mnt_ns = current->nsproxy->mnt_ns, *src_mnt_ns; struct user_namespace *user_ns = mnt_ns->user_ns; @@ -3029,7 +3047,7 @@ static struct mnt_namespace *get_detached_copy(const struct path *path, bool rec ns->seq_origin = src_mnt_ns->ns.ns_id; } - mnt = __do_loopback(path, recursive); + mnt = __do_loopback(path, flags, 0); if (IS_ERR(mnt)) { emptied_ns = ns; return ERR_CAST(mnt); @@ -3043,9 +3061,9 @@ static struct mnt_namespace *get_detached_copy(const struct path *path, bool rec return ns; } -static struct file *open_detached_copy(struct path *path, bool recursive) +static struct file *open_detached_copy(struct path *path, unsigned int flags) { - struct mnt_namespace *ns = get_detached_copy(path, recursive); + struct mnt_namespace *ns = get_detached_copy(path, flags); struct file *file; if (IS_ERR(ns)) @@ -3061,21 +3079,122 @@ static struct file *open_detached_copy(struct path *path, bool recursive) return file; } +DEFINE_FREE(put_empty_mnt_ns, struct mnt_namespace *, + if (!IS_ERR_OR_NULL(_T)) free_mnt_ns(_T)) + +static struct mnt_namespace *create_new_namespace(struct path *path, unsigned int flags) +{ + struct mnt_namespace *new_ns __free(put_empty_mnt_ns) = NULL; + struct path to_path __free(path_put) = {}; + struct mnt_namespace *ns = current->nsproxy->mnt_ns; + struct user_namespace *user_ns = current_user_ns(); + struct mount *new_ns_root; + struct mount *mnt; + unsigned int copy_flags = 0; + bool locked = false; + + if (user_ns != ns->user_ns) + copy_flags |= CL_SLAVE; + + new_ns = alloc_mnt_ns(user_ns, false); + if (IS_ERR(new_ns)) + return ERR_CAST(new_ns); + + scoped_guard(namespace_excl) { + new_ns_root = clone_mnt(ns->root, ns->root->mnt.mnt_root, copy_flags); + if (IS_ERR(new_ns_root)) + return ERR_CAST(new_ns_root); + + /* + * If the real rootfs had a locked mount on top of it somewhere + * in the stack, lock the new mount tree as well so it can't be + * exposed. + */ + mnt = ns->root; + while (mnt->overmount) { + mnt = mnt->overmount; + if (mnt->mnt.mnt_flags & MNT_LOCKED) + locked = true; + } + } + + /* + * We dropped the namespace semaphore so we can actually lock + * the copy for mounting. The copied mount isn't attached to any + * mount namespace and it is thus excluded from any propagation. + * So realistically we're isolated and the mount can't be + * overmounted. + */ + + /* Borrow the reference from clone_mnt(). */ + to_path.mnt = &new_ns_root->mnt; + to_path.dentry = dget(new_ns_root->mnt.mnt_root); + + /* Now lock for actual mounting. */ + LOCK_MOUNT_EXACT(mp, &to_path); + if (unlikely(IS_ERR(mp.parent))) + return ERR_CAST(mp.parent); + + /* + * We don't emulate unshare()ing a mount namespace. We stick to the + * restrictions of creating detached bind-mounts. It has a lot + * saner and simpler semantics. + */ + mnt = __do_loopback(path, flags, copy_flags); + if (IS_ERR(mnt)) + return ERR_CAST(mnt); + + scoped_guard(mount_writer) { + if (locked) + mnt->mnt.mnt_flags |= MNT_LOCKED; + /* + * Now mount the detached tree on top of the copy of the + * real rootfs we created. + */ + attach_mnt(mnt, new_ns_root, mp.mp); + if (user_ns != ns->user_ns) + lock_mnt_tree(new_ns_root); + } + + /* Add all mounts to the new namespace. */ + for (struct mount *p = new_ns_root; p; p = next_mnt(p, new_ns_root)) { + mnt_add_to_ns(new_ns, p); + new_ns->nr_mounts++; + } + + new_ns->root = real_mount(no_free_ptr(to_path.mnt)); + ns_tree_add_raw(new_ns); + return no_free_ptr(new_ns); +} + +static struct file *open_new_namespace(struct path *path, unsigned int flags) +{ + struct mnt_namespace *new_ns; + + new_ns = create_new_namespace(path, flags); + if (IS_ERR(new_ns)) + return ERR_CAST(new_ns); + return open_namespace_file(to_ns_common(new_ns)); +} + static struct file *vfs_open_tree(int dfd, const char __user *filename, unsigned int flags) { int ret; struct path path __free(path_put) = {}; int lookup_flags = LOOKUP_AUTOMOUNT | LOOKUP_FOLLOW; - bool detached = flags & OPEN_TREE_CLONE; BUILD_BUG_ON(OPEN_TREE_CLOEXEC != O_CLOEXEC); if (flags & ~(AT_EMPTY_PATH | AT_NO_AUTOMOUNT | AT_RECURSIVE | AT_SYMLINK_NOFOLLOW | OPEN_TREE_CLONE | - OPEN_TREE_CLOEXEC)) + OPEN_TREE_CLOEXEC | OPEN_TREE_NAMESPACE)) return ERR_PTR(-EINVAL); - if ((flags & (AT_RECURSIVE | OPEN_TREE_CLONE)) == AT_RECURSIVE) + if ((flags & (AT_RECURSIVE | OPEN_TREE_CLONE | OPEN_TREE_NAMESPACE)) == + AT_RECURSIVE) + return ERR_PTR(-EINVAL); + + if (hweight32(flags & (OPEN_TREE_CLONE | OPEN_TREE_NAMESPACE)) > 1) return ERR_PTR(-EINVAL); if (flags & AT_NO_AUTOMOUNT) @@ -3085,15 +3204,27 @@ static struct file *vfs_open_tree(int dfd, const char __user *filename, unsigned if (flags & AT_EMPTY_PATH) lookup_flags |= LOOKUP_EMPTY; - if (detached && !may_mount()) + /* + * If we create a new mount namespace with the cloned mount tree we + * just care about being privileged over our current user namespace. + * The new mount namespace will be owned by it. + */ + if ((flags & OPEN_TREE_NAMESPACE) && + !ns_capable(current_user_ns(), CAP_SYS_ADMIN)) + return ERR_PTR(-EPERM); + + if ((flags & OPEN_TREE_CLONE) && !may_mount()) return ERR_PTR(-EPERM); ret = user_path_at(dfd, filename, lookup_flags, &path); if (unlikely(ret)) return ERR_PTR(ret); - if (detached) - return open_detached_copy(&path, flags & AT_RECURSIVE); + if (flags & OPEN_TREE_NAMESPACE) + return open_new_namespace(&path, flags); + + if (flags & OPEN_TREE_CLONE) + return open_detached_copy(&path, flags); return dentry_open(&path, O_PATH, current_cred()); } diff --git a/fs/nsfs.c b/fs/nsfs.c index bf27d5da91f1..db91de208645 100644 --- a/fs/nsfs.c +++ b/fs/nsfs.c @@ -99,6 +99,19 @@ int ns_get_path(struct path *path, struct task_struct *task, return ns_get_path_cb(path, ns_get_path_task, &args); } +struct file *open_namespace_file(struct ns_common *ns) +{ + struct path path __free(path_put) = {}; + int err; + + /* call first to consume reference */ + err = path_from_stashed(&ns->stashed, nsfs_mnt, ns, &path); + if (err < 0) + return ERR_PTR(err); + + return dentry_open(&path, O_RDONLY, current_cred()); +} + /** * open_namespace - open a namespace * @ns: the namespace to open diff --git a/include/uapi/linux/mount.h b/include/uapi/linux/mount.h index 18c624405268..d9d86598d100 100644 --- a/include/uapi/linux/mount.h +++ b/include/uapi/linux/mount.h @@ -61,7 +61,8 @@ /* * open_tree() flags. */ -#define OPEN_TREE_CLONE 1 /* Clone the target tree and attach the clone */ +#define OPEN_TREE_CLONE (1 << 0) /* Clone the target tree and attach the clone */ +#define OPEN_TREE_NAMESPACE (1 << 1) /* Clone the target tree into a new mount namespace */ #define OPEN_TREE_CLOEXEC O_CLOEXEC /* Close the file on execve() */ /* -- cgit v1.2.3 From 1e5271393d777f6159d896943b4c44c4f3ecff52 Mon Sep 17 00:00:00 2001 From: Thomas Weißschuh Date: Thu, 15 Jan 2026 08:35:44 +0100 Subject: hyper-v: Mark inner union in hv_kvp_exchg_msg_value as packed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The unpacked union within a packed struct generates alignment warnings on clang for 32-bit ARM: ./usr/include/linux/hyperv.h:361:2: error: field within 'struct hv_kvp_exchg_msg_value' is less aligned than 'union hv_kvp_exchg_msg_value::(anonymous at ./usr/include/linux/hyperv.h:361:2)' and is usually due to 'struct hv_kvp_exchg_msg_value' being packed, which can lead to unaligned accesses [-Werror,-Wunaligned-access] 361 | union { | ^ With the recent changes to compile-test the UAPI headers in more cases, this warning in combination with CONFIG_WERROR breaks the build. Fix the warning. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202512140314.DzDxpIVn-lkp@intel.com/ Reported-by: Nathan Chancellor Closes: https://lore.kernel.org/linux-kbuild/20260110-uapi-test-disable-headers-arm-clang-unaligned-access-v1-1-b7b0fa541daa@kernel.org/ Suggested-by: Arnd Bergmann Link: https://lore.kernel.org/linux-kbuild/29b2e736-d462-45b7-a0a9-85f8d8a3de56@app.fastmail.com/ Signed-off-by: Thomas Weißschuh Acked-by: Wei Liu (Microsoft) Tested-by: Nicolas Schier Reviewed-by: Nicolas Schier Acked-by: Greg Kroah-Hartman Link: https://patch.msgid.link/20260115-kbuild-alignment-vbox-v1-1-076aed1623ff@linutronix.de Signed-off-by: Nathan Chancellor --- include/uapi/linux/hyperv.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/hyperv.h b/include/uapi/linux/hyperv.h index aaa502a7bff4..1749b35ab2c2 100644 --- a/include/uapi/linux/hyperv.h +++ b/include/uapi/linux/hyperv.h @@ -362,7 +362,7 @@ struct hv_kvp_exchg_msg_value { __u8 value[HV_KVP_EXCHANGE_MAX_VALUE_SIZE]; __u32 value_u32; __u64 value_u64; - }; + } __attribute__((packed)); } __attribute__((packed)); struct hv_kvp_msg_enumerate { -- cgit v1.2.3 From c25d01e1c4f2d43f47af87c00e223f5ca7c71792 Mon Sep 17 00:00:00 2001 From: Thomas Weißschuh Date: Thu, 15 Jan 2026 08:35:45 +0100 Subject: virt: vbox: uapi: Mark inner unions in packed structs as packed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The unpacked unions within a packed struct generates alignment warnings on clang for 32-bit ARM: ./usr/include/linux/vbox_vmmdev_types.h:239:4: error: field u within 'struct vmmdev_hgcm_function_parameter32' is less aligned than 'union (unnamed union at ./usr/include/linux/vbox_vmmdev_types.h:223:2)' and is usually due to 'struct vmmdev_hgcm_function_parameter32' being packed, which can lead to unaligned accesses [-Werror,-Wunaligned-access] 239 | } u; | ^ ./usr/include/linux/vbox_vmmdev_types.h:254:6: error: field u within 'struct vmmdev_hgcm_function_parameter64::(anonymous union)::(unnamed at ./usr/include/linux/vbox_vmmdev_types.h:249:3)' is less aligned than 'union (unnamed union at ./usr/include/linux/vbox_vmmdev_types.h:251:4)' and is usually due to 'struct vmmdev_hgcm_function_parameter64::(anonymous union)::(unnamed at ./usr/include/linux/vbox_vmmdev_types.h:249:3)' being packed, which can lead to unaligned accesses [-Werror,-Wunaligned-access] With the recent changes to compile-test the UAPI headers in more cases, these warning in combination with CONFIG_WERROR breaks the build. Fix the warnings. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202512140314.DzDxpIVn-lkp@intel.com/ Reported-by: Nathan Chancellor Closes: https://lore.kernel.org/linux-kbuild/20260110-uapi-test-disable-headers-arm-clang-unaligned-access-v1-1-b7b0fa541daa@kernel.org/ Suggested-by: Arnd Bergmann Link: https://lore.kernel.org/linux-kbuild/29b2e736-d462-45b7-a0a9-85f8d8a3de56@app.fastmail.com/ Signed-off-by: Thomas Weißschuh Tested-by: Nicolas Schier Reviewed-by: Nicolas Schier Acked-by: Greg Kroah-Hartman Link: https://patch.msgid.link/20260115-kbuild-alignment-vbox-v1-2-076aed1623ff@linutronix.de Signed-off-by: Nathan Chancellor --- include/uapi/linux/vbox_vmmdev_types.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/vbox_vmmdev_types.h b/include/uapi/linux/vbox_vmmdev_types.h index 6073858d52a2..11f3627c3729 100644 --- a/include/uapi/linux/vbox_vmmdev_types.h +++ b/include/uapi/linux/vbox_vmmdev_types.h @@ -236,7 +236,7 @@ struct vmmdev_hgcm_function_parameter32 { /** Relative to the request header. */ __u32 offset; } page_list; - } u; + } __packed u; } __packed; VMMDEV_ASSERT_SIZE(vmmdev_hgcm_function_parameter32, 4 + 8); @@ -251,7 +251,7 @@ struct vmmdev_hgcm_function_parameter64 { union { __u64 phys_addr; __u64 linear_addr; - } u; + } __packed u; } __packed pointer; struct { /** Size of the buffer described by the page list. */ -- cgit v1.2.3 From 150a04d817d8f5be5a4f92799827cdc8d7e45989 Mon Sep 17 00:00:00 2001 From: Bill Wendling Date: Fri, 16 Jan 2026 00:57:57 +0000 Subject: compiler_types.h: Attributes: Add __counted_by_ptr macro Introduce __counted_by_ptr(), which works like __counted_by(), but for pointer struct members. struct foo { int a, b, c; char *buffer __counted_by_ptr(bytes); short nr_bars; struct bar *bars __counted_by_ptr(nr_bars); size_t bytes; }; Because "counted_by" can only be applied to pointer members in very recent compiler versions, its application ends up needing to be distinct from flexibe array "counted_by" annotations, hence a separate macro. This is a reworking of Kees' previous patch [1]. Link: https://lore.kernel.org/all/20251020220118.1226740-1-kees@kernel.org/ [1] Co-developed-by: Kees Cook Signed-off-by: Bill Wendling Link: https://patch.msgid.link/20260116005838.2419118-1-morbo@google.com Signed-off-by: Kees Cook --- Makefile | 6 ++++++ include/linux/compiler_types.h | 18 +++++++++++++++++- include/uapi/linux/stddef.h | 4 ++++ init/Kconfig | 7 +++++++ 4 files changed, 34 insertions(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/Makefile b/Makefile index 3cd00b62cde9..c0751976cdee 100644 --- a/Makefile +++ b/Makefile @@ -952,6 +952,12 @@ KBUILD_CFLAGS += $(CC_AUTO_VAR_INIT_ZERO_ENABLER) endif endif +ifdef CONFIG_CC_IS_CLANG +ifdef CONFIG_CC_HAS_COUNTED_BY_PTR +KBUILD_CFLAGS += -fexperimental-late-parse-attributes +endif +endif + # Explicitly clear padding bits during variable initialization KBUILD_CFLAGS += $(call cc-option,-fzero-init-padding-bits=all) diff --git a/include/linux/compiler_types.h b/include/linux/compiler_types.h index d3318a3c2577..d095beb904ea 100644 --- a/include/linux/compiler_types.h +++ b/include/linux/compiler_types.h @@ -369,7 +369,7 @@ struct ftrace_likely_data { * Optional: only supported since clang >= 18 * * gcc: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108896 - * clang: https://github.com/llvm/llvm-project/pull/76348 + * clang: https://clang.llvm.org/docs/AttributeReference.html#counted-by-counted-by-or-null-sized-by-sized-by-or-null * * __bdos on clang < 19.1.2 can erroneously return 0: * https://github.com/llvm/llvm-project/pull/110497 @@ -383,6 +383,22 @@ struct ftrace_likely_data { # define __counted_by(member) #endif +/* + * Runtime track number of objects pointed to by a pointer member for use by + * CONFIG_FORTIFY_SOURCE and CONFIG_UBSAN_BOUNDS. + * + * Optional: only supported since gcc >= 16 + * Optional: only supported since clang >= 22 + * + * gcc: https://gcc.gnu.org/pipermail/gcc-patches/2025-April/681727.html + * clang: https://clang.llvm.org/docs/AttributeReference.html#counted-by-counted-by-or-null-sized-by-sized-by-or-null + */ +#ifdef CONFIG_CC_HAS_COUNTED_BY_PTR +#define __counted_by_ptr(member) __attribute__((__counted_by__(member))) +#else +#define __counted_by_ptr(member) +#endif + /* * Optional: only supported since gcc >= 15 * Optional: not supported by Clang diff --git a/include/uapi/linux/stddef.h b/include/uapi/linux/stddef.h index 9a28f7d9a334..111b097ec00b 100644 --- a/include/uapi/linux/stddef.h +++ b/include/uapi/linux/stddef.h @@ -72,6 +72,10 @@ #define __counted_by_be(m) #endif +#ifndef __counted_by_ptr +#define __counted_by_ptr(m) +#endif + #ifdef __KERNEL__ #define __kernel_nonstring __nonstring #else diff --git a/init/Kconfig b/init/Kconfig index fa79feb8fe57..96b7cd481eaa 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -143,6 +143,13 @@ config CC_HAS_COUNTED_BY # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108896 default y if CC_IS_GCC && GCC_VERSION >= 150100 +config CC_HAS_COUNTED_BY_PTR + bool + # supported since clang 22 + default y if CC_IS_CLANG && CLANG_VERSION >= 220000 + # supported since gcc 16.0.0 + default y if CC_IS_GCC && GCC_VERSION >= 160000 + config CC_HAS_MULTIDIMENSIONAL_NONSTRING def_bool $(success,echo 'char tag[][4] __attribute__((__nonstring__)) = { };' | $(CC) $(CLANG_FLAGS) -x c - -c -o /dev/null -Werror) -- cgit v1.2.3 From ca9d74eb5f6aea6eee746aae648382332dbcf24e Mon Sep 17 00:00:00 2001 From: Thomas Weißschuh Date: Tue, 13 Jan 2026 08:44:17 +0100 Subject: uapi: add INT_MAX and INT_MIN constants MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some UAPI headers use INT_MAX and INT_MIN. Currently they include for their definitions, which introduces a problematic dependency on libc. Add custom, namespaced definitions of INT_MAX and INT_MIN using the same values as the regular kernel code. These definitions are not added to uapi/linux/limits.h, as that header will conflict with libc definitions on some platforms. Signed-off-by: Thomas Weißschuh Link: https://patch.msgid.link/20260113-uapi-limits-v2-1-93c20f4b2c1a@linutronix.de Signed-off-by: Jakub Kicinski --- include/uapi/linux/typelimits.h | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 include/uapi/linux/typelimits.h (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/typelimits.h b/include/uapi/linux/typelimits.h new file mode 100644 index 000000000000..8166c639b518 --- /dev/null +++ b/include/uapi/linux/typelimits.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _UAPI_LINUX_TYPELIMITS_H +#define _UAPI_LINUX_TYPELIMITS_H + +#define __KERNEL_INT_MAX ((int)(~0U >> 1)) +#define __KERNEL_INT_MIN (-__KERNEL_INT_MAX - 1) + +#endif /* _UAPI_LINUX_TYPELIMITS_H */ -- cgit v1.2.3 From a8a11e5237aed71b7f5f9d33c554ef06fe974311 Mon Sep 17 00:00:00 2001 From: Thomas Weißschuh Date: Tue, 13 Jan 2026 08:44:18 +0100 Subject: ethtool: uapi: Use UAPI definition of INT_MAX MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Using to gain access to INT_MAX introduces a dependency on a libc, which UAPI headers should not do. Use the equivalent UAPI constant. Signed-off-by: Thomas Weißschuh Link: https://patch.msgid.link/20260113-uapi-limits-v2-2-93c20f4b2c1a@linutronix.de Signed-off-by: Jakub Kicinski --- include/uapi/linux/ethtool.h | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/ethtool.h b/include/uapi/linux/ethtool.h index bbfe6e1cf01b..ce9aeb65a8e1 100644 --- a/include/uapi/linux/ethtool.h +++ b/include/uapi/linux/ethtool.h @@ -15,13 +15,10 @@ #define _UAPI_LINUX_ETHTOOL_H #include +#include #include #include -#ifndef __KERNEL__ -#include /* for INT_MAX */ -#endif - /* All structures exposed to userland should be defined such that they * have the same layout for 32-bit and 64-bit userland. */ @@ -2216,7 +2213,7 @@ enum ethtool_link_mode_bit_indices { static inline int ethtool_validate_speed(__u32 speed) { - return speed <= INT_MAX || speed == (__u32)SPEED_UNKNOWN; + return speed <= __KERNEL_INT_MAX || speed == (__u32)SPEED_UNKNOWN; } /* Duplex, half or full. */ -- cgit v1.2.3 From 0b3877bec78b0f26a280078a15f8992426de1db7 Mon Sep 17 00:00:00 2001 From: Thomas Weißschuh Date: Tue, 13 Jan 2026 08:44:19 +0100 Subject: netfilter: uapi: Use UAPI definition of INT_MAX and INT_MIN MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Using to gain access to INT_MAX and INT_MIN introduces a dependency on a libc, which UAPI headers should not do. Use the equivalent UAPI constants. Signed-off-by: Thomas Weißschuh Link: https://patch.msgid.link/20260113-uapi-limits-v2-3-93c20f4b2c1a@linutronix.de Signed-off-by: Jakub Kicinski --- include/uapi/linux/netfilter_bridge.h | 9 +++------ include/uapi/linux/netfilter_ipv4.h | 9 ++++----- include/uapi/linux/netfilter_ipv6.h | 7 +++---- 3 files changed, 10 insertions(+), 15 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/netfilter_bridge.h b/include/uapi/linux/netfilter_bridge.h index 1610fdbab98d..f6e8d1e05c97 100644 --- a/include/uapi/linux/netfilter_bridge.h +++ b/include/uapi/linux/netfilter_bridge.h @@ -10,10 +10,7 @@ #include #include #include - -#ifndef __KERNEL__ -#include /* for INT_MIN, INT_MAX */ -#endif +#include /* Bridge Hooks */ /* After promisc drops, checksum checks. */ @@ -31,14 +28,14 @@ #define NF_BR_NUMHOOKS 6 enum nf_br_hook_priorities { - NF_BR_PRI_FIRST = INT_MIN, + NF_BR_PRI_FIRST = __KERNEL_INT_MIN, NF_BR_PRI_NAT_DST_BRIDGED = -300, NF_BR_PRI_FILTER_BRIDGED = -200, NF_BR_PRI_BRNF = 0, NF_BR_PRI_NAT_DST_OTHER = 100, NF_BR_PRI_FILTER_OTHER = 200, NF_BR_PRI_NAT_SRC = 300, - NF_BR_PRI_LAST = INT_MAX, + NF_BR_PRI_LAST = __KERNEL_INT_MAX, }; #endif /* _UAPI__LINUX_BRIDGE_NETFILTER_H */ diff --git a/include/uapi/linux/netfilter_ipv4.h b/include/uapi/linux/netfilter_ipv4.h index 155e77d6a42d..439d3c59862b 100644 --- a/include/uapi/linux/netfilter_ipv4.h +++ b/include/uapi/linux/netfilter_ipv4.h @@ -7,12 +7,11 @@ #include +#include /* only for userspace compatibility */ #ifndef __KERNEL__ -#include /* for INT_MIN, INT_MAX */ - /* IP Hooks */ /* After promisc drops, checksum checks. */ #define NF_IP_PRE_ROUTING 0 @@ -28,7 +27,7 @@ #endif /* ! __KERNEL__ */ enum nf_ip_hook_priorities { - NF_IP_PRI_FIRST = INT_MIN, + NF_IP_PRI_FIRST = __KERNEL_INT_MIN, NF_IP_PRI_RAW_BEFORE_DEFRAG = -450, NF_IP_PRI_CONNTRACK_DEFRAG = -400, NF_IP_PRI_RAW = -300, @@ -41,8 +40,8 @@ enum nf_ip_hook_priorities { NF_IP_PRI_NAT_SRC = 100, NF_IP_PRI_SELINUX_LAST = 225, NF_IP_PRI_CONNTRACK_HELPER = 300, - NF_IP_PRI_CONNTRACK_CONFIRM = INT_MAX, - NF_IP_PRI_LAST = INT_MAX, + NF_IP_PRI_CONNTRACK_CONFIRM = __KERNEL_INT_MAX, + NF_IP_PRI_LAST = __KERNEL_INT_MAX, }; /* Arguments for setsockopt SOL_IP: */ diff --git a/include/uapi/linux/netfilter_ipv6.h b/include/uapi/linux/netfilter_ipv6.h index 80aa9b0799af..0e40d00b37fa 100644 --- a/include/uapi/linux/netfilter_ipv6.h +++ b/include/uapi/linux/netfilter_ipv6.h @@ -10,12 +10,11 @@ #include +#include /* only for userspace compatibility */ #ifndef __KERNEL__ -#include /* for INT_MIN, INT_MAX */ - /* IP6 Hooks */ /* After promisc drops, checksum checks. */ #define NF_IP6_PRE_ROUTING 0 @@ -32,7 +31,7 @@ enum nf_ip6_hook_priorities { - NF_IP6_PRI_FIRST = INT_MIN, + NF_IP6_PRI_FIRST = __KERNEL_INT_MIN, NF_IP6_PRI_RAW_BEFORE_DEFRAG = -450, NF_IP6_PRI_CONNTRACK_DEFRAG = -400, NF_IP6_PRI_RAW = -300, @@ -45,7 +44,7 @@ enum nf_ip6_hook_priorities { NF_IP6_PRI_NAT_SRC = 100, NF_IP6_PRI_SELINUX_LAST = 225, NF_IP6_PRI_CONNTRACK_HELPER = 300, - NF_IP6_PRI_LAST = INT_MAX, + NF_IP6_PRI_LAST = __KERNEL_INT_MAX, }; -- cgit v1.2.3 From 7d8b06ecc45bd679dec58d2cc2bd86223d4e076d Mon Sep 17 00:00:00 2001 From: Suravee Suthikulpanit Date: Thu, 15 Jan 2026 06:08:02 +0000 Subject: iommu/amd: Add support for hw_info for iommu capability query AMD IOMMU Extended Feature (EFR) and Extended Feature 2 (EFR2) registers specify features supported by each IOMMU hardware instance. The IOMMU driver checks each feature-specific bits before enabling each feature at run time. For IOMMUFD, the hypervisor passes the raw value of amd_iommu_efr and amd_iommu_efr2 to VMM via iommufd IOMMU_DEVICE_GET_HW_INFO ioctl. Reviewed-by: Nicolin Chen Reviewed-by: Vasant Hegde Reviewed-by: Jason Gunthorpe Signed-off-by: Suravee Suthikulpanit Signed-off-by: Joerg Roedel --- drivers/iommu/amd/Kconfig | 10 ++++++++++ drivers/iommu/amd/Makefile | 1 + drivers/iommu/amd/iommu.c | 2 ++ drivers/iommu/amd/iommufd.c | 31 +++++++++++++++++++++++++++++++ drivers/iommu/amd/iommufd.h | 15 +++++++++++++++ include/uapi/linux/iommufd.h | 28 ++++++++++++++++++++++++++++ 6 files changed, 87 insertions(+) create mode 100644 drivers/iommu/amd/iommufd.c create mode 100644 drivers/iommu/amd/iommufd.h (limited to 'include/uapi/linux') diff --git a/drivers/iommu/amd/Kconfig b/drivers/iommu/amd/Kconfig index f2acf471cb5d..588355ff7eb7 100644 --- a/drivers/iommu/amd/Kconfig +++ b/drivers/iommu/amd/Kconfig @@ -30,6 +30,16 @@ config AMD_IOMMU your BIOS for an option to enable it or if you have an IVRS ACPI table. +config AMD_IOMMU_IOMMUFD + bool "Enable IOMMUFD features for AMD IOMMU (EXPERIMENTAL)" + depends on IOMMUFD + depends on AMD_IOMMU + help + Support for IOMMUFD features intended to support virtual machines + with accelerated virtual IOMMUs. + + Say Y here if you are doing development and testing on this feature. + config AMD_IOMMU_DEBUGFS bool "Enable AMD IOMMU internals in DebugFS" depends on AMD_IOMMU && IOMMU_DEBUGFS diff --git a/drivers/iommu/amd/Makefile b/drivers/iommu/amd/Makefile index 5412a563c697..41f053b49dce 100644 --- a/drivers/iommu/amd/Makefile +++ b/drivers/iommu/amd/Makefile @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only obj-y += iommu.o init.o quirks.o ppr.o pasid.o +obj-$(CONFIG_AMD_IOMMU_IOMMUFD) += iommufd.o obj-$(CONFIG_AMD_IOMMU_DEBUGFS) += debugfs.o diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c index d7f457338de7..d550a7e431ac 100644 --- a/drivers/iommu/amd/iommu.c +++ b/drivers/iommu/amd/iommu.c @@ -43,6 +43,7 @@ #include #include "amd_iommu.h" +#include "iommufd.h" #include "../irq_remapping.h" #include "../iommu-pages.h" @@ -3083,6 +3084,7 @@ static bool amd_iommu_enforce_cache_coherency(struct iommu_domain *domain) const struct iommu_ops amd_iommu_ops = { .capable = amd_iommu_capable, + .hw_info = amd_iommufd_hw_info, .blocked_domain = &blocked_domain, .release_domain = &blocked_domain, .identity_domain = &identity_domain.domain, diff --git a/drivers/iommu/amd/iommufd.c b/drivers/iommu/amd/iommufd.c new file mode 100644 index 000000000000..72eaaa923d04 --- /dev/null +++ b/drivers/iommu/amd/iommufd.c @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2025 Advanced Micro Devices, Inc. + */ + +#include + +#include "iommufd.h" +#include "amd_iommu.h" +#include "amd_iommu_types.h" + +void *amd_iommufd_hw_info(struct device *dev, u32 *length, u32 *type) +{ + struct iommu_hw_info_amd *hwinfo; + + if (*type != IOMMU_HW_INFO_TYPE_DEFAULT && + *type != IOMMU_HW_INFO_TYPE_AMD) + return ERR_PTR(-EOPNOTSUPP); + + hwinfo = kzalloc(sizeof(*hwinfo), GFP_KERNEL); + if (!hwinfo) + return ERR_PTR(-ENOMEM); + + *length = sizeof(*hwinfo); + *type = IOMMU_HW_INFO_TYPE_AMD; + + hwinfo->efr = amd_iommu_efr; + hwinfo->efr2 = amd_iommu_efr2; + + return hwinfo; +} diff --git a/drivers/iommu/amd/iommufd.h b/drivers/iommu/amd/iommufd.h new file mode 100644 index 000000000000..f880be80a30d --- /dev/null +++ b/drivers/iommu/amd/iommufd.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2025 Advanced Micro Devices, Inc. + */ + +#ifndef AMD_IOMMUFD_H +#define AMD_IOMMUFD_H + +#if IS_ENABLED(CONFIG_AMD_IOMMU_IOMMUFD) +void *amd_iommufd_hw_info(struct device *dev, u32 *length, u32 *type); +#else +#define amd_iommufd_hw_info NULL +#endif /* CONFIG_AMD_IOMMU_IOMMUFD */ + +#endif /* AMD_IOMMUFD_H */ diff --git a/include/uapi/linux/iommufd.h b/include/uapi/linux/iommufd.h index 2c41920b641d..3db37f6042a0 100644 --- a/include/uapi/linux/iommufd.h +++ b/include/uapi/linux/iommufd.h @@ -623,6 +623,32 @@ struct iommu_hw_info_tegra241_cmdqv { __u8 __reserved; }; +/** + * struct iommu_hw_info_amd - AMD IOMMU device info + * + * @efr : Value of AMD IOMMU Extended Feature Register (EFR) + * @efr2: Value of AMD IOMMU Extended Feature 2 Register (EFR2) + * + * Please See description of these registers in the following sections of + * the AMD I/O Virtualization Technology (IOMMU) Specification. + * (https://docs.amd.com/v/u/en-US/48882_3.10_PUB) + * + * - MMIO Offset 0030h IOMMU Extended Feature Register + * - MMIO Offset 01A0h IOMMU Extended Feature 2 Register + * + * Note: The EFR and EFR2 are raw values reported by hardware. + * VMM is responsible to determine the appropriate flags to be exposed to + * the VM since cetertain features are not currently supported by the kernel + * for HW-vIOMMU. + * + * Current VMM-allowed list of feature flags are: + * - EFR[GTSup, GASup, GioSup, PPRSup, EPHSup, GATS, GLX, PASmax] + */ +struct iommu_hw_info_amd { + __aligned_u64 efr; + __aligned_u64 efr2; +}; + /** * enum iommu_hw_info_type - IOMMU Hardware Info Types * @IOMMU_HW_INFO_TYPE_NONE: Output by the drivers that do not report hardware @@ -632,6 +658,7 @@ struct iommu_hw_info_tegra241_cmdqv { * @IOMMU_HW_INFO_TYPE_ARM_SMMUV3: ARM SMMUv3 iommu info type * @IOMMU_HW_INFO_TYPE_TEGRA241_CMDQV: NVIDIA Tegra241 CMDQV (extension for ARM * SMMUv3) info type + * @IOMMU_HW_INFO_TYPE_AMD: AMD IOMMU info type */ enum iommu_hw_info_type { IOMMU_HW_INFO_TYPE_NONE = 0, @@ -639,6 +666,7 @@ enum iommu_hw_info_type { IOMMU_HW_INFO_TYPE_INTEL_VTD = 1, IOMMU_HW_INFO_TYPE_ARM_SMMUV3 = 2, IOMMU_HW_INFO_TYPE_TEGRA241_CMDQV = 3, + IOMMU_HW_INFO_TYPE_AMD = 4, }; /** -- cgit v1.2.3 From e05698c10d980ac0a0b57ed81ec9353b9e9533c6 Mon Sep 17 00:00:00 2001 From: Suravee Suthikulpanit Date: Thu, 15 Jan 2026 06:08:06 +0000 Subject: iommufd: Introduce data struct for AMD nested domain allocation Introduce IOMMU_HWPT_DATA_AMD_GUEST data type for IOMMU guest page table, which is used for stage-1 in nested translation. The data structure contains information necessary for setting up the AMD HW-vIOMMU support. Reviewed-by: Jason Gunthorpe Reviewed-by: Nicolin Chen Reviewed-by: Vasant Hegde Signed-off-by: Suravee Suthikulpanit Signed-off-by: Joerg Roedel --- include/uapi/linux/iommufd.h | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/iommufd.h b/include/uapi/linux/iommufd.h index 3db37f6042a0..1dafbc552d37 100644 --- a/include/uapi/linux/iommufd.h +++ b/include/uapi/linux/iommufd.h @@ -465,16 +465,27 @@ struct iommu_hwpt_arm_smmuv3 { __aligned_le64 ste[2]; }; +/** + * struct iommu_hwpt_amd_guest - AMD IOMMU guest I/O page table data + * (IOMMU_HWPT_DATA_AMD_GUEST) + * @dte: Guest Device Table Entry (DTE) + */ +struct iommu_hwpt_amd_guest { + __aligned_u64 dte[4]; +}; + /** * enum iommu_hwpt_data_type - IOMMU HWPT Data Type * @IOMMU_HWPT_DATA_NONE: no data * @IOMMU_HWPT_DATA_VTD_S1: Intel VT-d stage-1 page table * @IOMMU_HWPT_DATA_ARM_SMMUV3: ARM SMMUv3 Context Descriptor Table + * @IOMMU_HWPT_DATA_AMD_GUEST: AMD IOMMU guest page table */ enum iommu_hwpt_data_type { IOMMU_HWPT_DATA_NONE = 0, IOMMU_HWPT_DATA_VTD_S1 = 1, IOMMU_HWPT_DATA_ARM_SMMUV3 = 2, + IOMMU_HWPT_DATA_AMD_GUEST = 3, }; /** -- cgit v1.2.3 From cd16edba1c6a24af138e1a5ded2711231fffa99f Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Thu, 4 Dec 2025 11:19:10 +0100 Subject: ext4: fix ext4_tune_sb_params padding The padding at the end of struct ext4_tune_sb_params is architecture specific and in particular is different between x86-32 and x86-64, since the __u64 member only enforces struct alignment on the latter. This shows up as a new warning when test-building the headers with -Wpadded: include/linux/ext4.h:144:1: error: padding struct size to alignment boundary with 4 bytes [-Werror=padded] All members inside the structure are naturally aligned, so the only difference here is the amount of padding at the end. Make the padding explicit, to have a consistent sizeof(struct ext4_tune_sb_params) of 232 on all architectures and avoid adding compat ioctl handling for EXT4_IOC_GET_TUNE_SB_PARAM/EXT4_IOC_SET_TUNE_SB_PARAM. This is an ABI break on x86-32 but hopefully this can go into 6.18.y early enough as a fixup so no actual users will be affected. Alternatively, the kernel could handle the ioctl commands for both sizes (232 and 228 bytes) on all architectures. Fixes: 04a91570ac67 ("ext4: implemet new ioctls to set and get superblock parameters") Signed-off-by: Arnd Bergmann Reviewed-by: Jan Kara Link: https://patch.msgid.link/20251204101914.1037148-1-arnd@kernel.org Signed-off-by: Theodore Ts'o Cc: stable@kernel.org --- include/uapi/linux/ext4.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/ext4.h b/include/uapi/linux/ext4.h index 411dcc1e4a35..9c683991c32f 100644 --- a/include/uapi/linux/ext4.h +++ b/include/uapi/linux/ext4.h @@ -139,7 +139,7 @@ struct ext4_tune_sb_params { __u32 clear_feature_incompat_mask; __u32 clear_feature_ro_compat_mask; __u8 mount_opts[64]; - __u8 pad[64]; + __u8 pad[68]; }; #define EXT4_TUNE_FL_ERRORS_BEHAVIOR 0x00000001 -- cgit v1.2.3 From 8a42938a28941da29bf3e4cd2af877b0d5d929e1 Mon Sep 17 00:00:00 2001 From: Miri Korenblit Date: Wed, 7 Jan 2026 14:22:54 +0200 Subject: wifi: nl80211: ignore cluster id after NAN started After NAN was started, cluster id updates from the user space should not happen, since the device already started a cluster with the previousely provided id. Since NL80211_CMD_CHANGE_NAN_CONFIG requires to set the full NAN configuration, we can't require that NL80211_NAN_CONF_CLUSTER_ID won't be included in this command, and keeping the last confgiured value just to be able to compare it against the new one seems a bit overkill. Therefore, just ignore cluster id in this command and clarify the documentation. Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260107142229.fb55e5853269.I10d18c8f69d98b28916596d6da4207c15ea4abb5@changeid Signed-off-by: Johannes Berg --- include/uapi/linux/nl80211.h | 4 +++- net/wireless/nl80211.c | 11 ++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index eb92296457c9..b0f050e36fa4 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -11,7 +11,7 @@ * Copyright 2008 Jouni Malinen * Copyright 2008 Colin McCabe * Copyright 2015-2017 Intel Deutschland GmbH - * Copyright (C) 2018-2025 Intel Corporation + * Copyright (C) 2018-2026 Intel Corporation * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -7454,6 +7454,8 @@ enum nl80211_nan_band_conf_attributes { * address that can take values from 50-6F-9A-01-00-00 to * 50-6F-9A-01-FF-FF. This attribute is optional. If not present, * a random Cluster ID will be chosen. + * This attribute will be ignored in NL80211_CMD_CHANGE_NAN_CONFIG + * since after NAN was started, the cluster ID can no longer change. * @NL80211_NAN_CONF_EXTRA_ATTRS: Additional NAN attributes to be * published in the beacons. This is an optional byte array. * @NL80211_NAN_CONF_VENDOR_ELEMS: Vendor-specific elements that will diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index bcf30c5f5042..56cc5ed33ea3 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -5,7 +5,7 @@ * Copyright 2006-2010 Johannes Berg * Copyright 2013-2014 Intel Mobile Communications GmbH * Copyright 2015-2017 Intel Deutschland GmbH - * Copyright (C) 2018-2025 Intel Corporation + * Copyright (C) 2018-2026 Intel Corporation */ #include @@ -15583,7 +15583,8 @@ static int nl80211_parse_nan_band_config(struct wiphy *wiphy, static int nl80211_parse_nan_conf(struct wiphy *wiphy, struct genl_info *info, struct cfg80211_nan_conf *conf, - u32 *changed_flags) + u32 *changed_flags, + bool start) { struct nlattr *attrs[NL80211_NAN_CONF_ATTR_MAX + 1]; int err, rem; @@ -15630,7 +15631,7 @@ static int nl80211_parse_nan_conf(struct wiphy *wiphy, return err; changed |= CFG80211_NAN_CONF_CHANGED_CONFIG; - if (attrs[NL80211_NAN_CONF_CLUSTER_ID]) + if (attrs[NL80211_NAN_CONF_CLUSTER_ID] && start) conf->cluster_id = nla_data(attrs[NL80211_NAN_CONF_CLUSTER_ID]); @@ -15741,7 +15742,7 @@ static int nl80211_start_nan(struct sk_buff *skb, struct genl_info *info) if (!info->attrs[NL80211_ATTR_NAN_MASTER_PREF]) return -EINVAL; - err = nl80211_parse_nan_conf(&rdev->wiphy, info, &conf, NULL); + err = nl80211_parse_nan_conf(&rdev->wiphy, info, &conf, NULL, true); if (err) return err; @@ -16107,7 +16108,7 @@ static int nl80211_nan_change_config(struct sk_buff *skb, if (!wdev_running(wdev)) return -ENOTCONN; - err = nl80211_parse_nan_conf(&rdev->wiphy, info, &conf, &changed); + err = nl80211_parse_nan_conf(&rdev->wiphy, info, &conf, &changed, false); if (err) return err; -- cgit v1.2.3 From a9927022c4491ba44249af079e8799ce56f8053c Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Thu, 15 Jan 2026 12:56:44 +0100 Subject: net: ethtool: Add support for 80Gbps speed USB4 v2 link used in peer-to-peer networking is symmetric 80Gbps so in order to support reading this link speed, add support for it to ethtool. Signed-off-by: Mika Westerberg Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260115115646.328898-3-mika.westerberg@linux.intel.com Signed-off-by: Jakub Kicinski --- drivers/net/phy/phy-caps.h | 1 + drivers/net/phy/phy-core.c | 2 ++ drivers/net/phy/phy_caps.c | 2 ++ drivers/net/phy/phylink.c | 1 + include/linux/phylink.h | 7 ++++--- include/uapi/linux/ethtool.h | 1 + 6 files changed, 11 insertions(+), 3 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/net/phy/phy-caps.h b/drivers/net/phy/phy-caps.h index 5f3f757e0b2f..421088e6f6e8 100644 --- a/drivers/net/phy/phy-caps.h +++ b/drivers/net/phy/phy-caps.h @@ -25,6 +25,7 @@ enum { LINK_CAPA_40000FD, LINK_CAPA_50000FD, LINK_CAPA_56000FD, + LINK_CAPA_80000FD, LINK_CAPA_100000FD, LINK_CAPA_200000FD, LINK_CAPA_400000FD, diff --git a/drivers/net/phy/phy-core.c b/drivers/net/phy/phy-core.c index 3badf6e84554..d7a4a977fc8a 100644 --- a/drivers/net/phy/phy-core.c +++ b/drivers/net/phy/phy-core.c @@ -48,6 +48,8 @@ const char *phy_speed_to_str(int speed) return "50Gbps"; case SPEED_56000: return "56Gbps"; + case SPEED_80000: + return "80Gbps"; case SPEED_100000: return "100Gbps"; case SPEED_200000: diff --git a/drivers/net/phy/phy_caps.c b/drivers/net/phy/phy_caps.c index 17a63c931335..942d43191561 100644 --- a/drivers/net/phy/phy_caps.c +++ b/drivers/net/phy/phy_caps.c @@ -21,6 +21,7 @@ static struct link_capabilities link_caps[__LINK_CAPA_MAX] __ro_after_init = { { SPEED_40000, DUPLEX_FULL, {0} }, /* LINK_CAPA_40000FD */ { SPEED_50000, DUPLEX_FULL, {0} }, /* LINK_CAPA_50000FD */ { SPEED_56000, DUPLEX_FULL, {0} }, /* LINK_CAPA_56000FD */ + { SPEED_80000, DUPLEX_FULL, {0} }, /* LINK_CAPA_80000FD */ { SPEED_100000, DUPLEX_FULL, {0} }, /* LINK_CAPA_100000FD */ { SPEED_200000, DUPLEX_FULL, {0} }, /* LINK_CAPA_200000FD */ { SPEED_400000, DUPLEX_FULL, {0} }, /* LINK_CAPA_400000FD */ @@ -49,6 +50,7 @@ static int speed_duplex_to_capa(int speed, unsigned int duplex) case SPEED_40000: return LINK_CAPA_40000FD; case SPEED_50000: return LINK_CAPA_50000FD; case SPEED_56000: return LINK_CAPA_56000FD; + case SPEED_80000: return LINK_CAPA_80000FD; case SPEED_100000: return LINK_CAPA_100000FD; case SPEED_200000: return LINK_CAPA_200000FD; case SPEED_400000: return LINK_CAPA_400000FD; diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index 43d8380aaefb..c8fd6b91cdd4 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -311,6 +311,7 @@ static struct { { MAC_400000FD, SPEED_400000, DUPLEX_FULL, BIT(LINK_CAPA_400000FD) }, { MAC_200000FD, SPEED_200000, DUPLEX_FULL, BIT(LINK_CAPA_200000FD) }, { MAC_100000FD, SPEED_100000, DUPLEX_FULL, BIT(LINK_CAPA_100000FD) }, + { MAC_80000FD, SPEED_80000, DUPLEX_FULL, BIT(LINK_CAPA_80000FD) }, { MAC_56000FD, SPEED_56000, DUPLEX_FULL, BIT(LINK_CAPA_56000FD) }, { MAC_50000FD, SPEED_50000, DUPLEX_FULL, BIT(LINK_CAPA_50000FD) }, { MAC_40000FD, SPEED_40000, DUPLEX_FULL, BIT(LINK_CAPA_40000FD) }, diff --git a/include/linux/phylink.h b/include/linux/phylink.h index 38363e566ac3..20996f5778d1 100644 --- a/include/linux/phylink.h +++ b/include/linux/phylink.h @@ -90,9 +90,10 @@ enum { MAC_40000FD = BIT(13), MAC_50000FD = BIT(14), MAC_56000FD = BIT(15), - MAC_100000FD = BIT(16), - MAC_200000FD = BIT(17), - MAC_400000FD = BIT(18), + MAC_80000FD = BIT(16), + MAC_100000FD = BIT(17), + MAC_200000FD = BIT(18), + MAC_400000FD = BIT(19), }; static inline bool phylink_autoneg_inband(unsigned int mode) diff --git a/include/uapi/linux/ethtool.h b/include/uapi/linux/ethtool.h index ce9aeb65a8e1..b74b80508553 100644 --- a/include/uapi/linux/ethtool.h +++ b/include/uapi/linux/ethtool.h @@ -2203,6 +2203,7 @@ enum ethtool_link_mode_bit_indices { #define SPEED_40000 40000 #define SPEED_50000 50000 #define SPEED_56000 56000 +#define SPEED_80000 80000 #define SPEED_100000 100000 #define SPEED_200000 200000 #define SPEED_400000 400000 -- cgit v1.2.3 From 50b359896fe55d0443ed550e1fabba71d242031a Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Sun, 18 Jan 2026 09:51:15 +0200 Subject: wifi: cfg80211: ignore link disabled flag from userspace When the AP has an advertised TID to Link Mapping (TTLM) it shall include the element in the association response. As such, when this element is present it needs to be used for the currently dormant links. See Draft P802.11REVmf_D1.0 section 35.3.7.2.3 ("Negotiation of TTLM") for the details. The flag is also not usable in case userspace wants to specify a negotiated TTLM during association. Note that for the link reconfiguration case, mac80211 did not use the information. Draft P802.11REVmf_D1.0 states in section 35.3.6.4 ("Link reconfiguration to the setup links) that we "shall operate with all the TIDs mapped to the newly added links ..." All this means that the flag is not needed. The implementation should parse the information from the association response. Signed-off-by: Benjamin Berg Reviewed-by: Johannes Berg Reviewed-by: Ilan Peer Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260118093904.754e057896a5.Ifd06f5ef839a93bfd54d0593dc932870f95f3242@changeid Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 3 --- include/uapi/linux/nl80211.h | 5 +++-- net/wireless/nl80211.c | 10 ---------- 3 files changed, 3 insertions(+), 15 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 899f267b7cf9..2900202588a5 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -3221,8 +3221,6 @@ struct cfg80211_auth_request { * if this is %NULL for a link, that link is not requested * @elems: extra elements for the per-STA profile for this link * @elems_len: length of the elements - * @disabled: If set this link should be included during association etc. but it - * should not be used until enabled by the AP MLD. * @error: per-link error code, must be <= 0. If there is an error, then the * operation as a whole must fail. */ @@ -3230,7 +3228,6 @@ struct cfg80211_assoc_link { struct cfg80211_bss *bss; const u8 *elems; size_t elems_len; - bool disabled; int error; }; diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index 8134f10e4e6c..8433bac48112 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -2880,8 +2880,9 @@ enum nl80211_commands { * index. If the userspace includes more RNR elements than number of * MBSSID elements then these will be added in every EMA beacon. * - * @NL80211_ATTR_MLO_LINK_DISABLED: Flag attribute indicating that the link is - * disabled. + * @NL80211_ATTR_MLO_LINK_DISABLED: Unused. It was used to indicate that a link + * is disabled during association. However, the AP will send the + * information by including a TTLM in the association response. * * @NL80211_ATTR_BSS_DUMP_INCLUDE_USE_DATA: Include BSS usage data, i.e. * include BSSes that can only be used in restricted scenarios and/or diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index c961cd42a832..03efd45c007f 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -12241,9 +12241,6 @@ static int nl80211_process_links(struct cfg80211_registered_device *rdev, return -EINVAL; } } - - links[link_id].disabled = - nla_get_flag(attrs[NL80211_ATTR_MLO_LINK_DISABLED]); } return 0; @@ -12423,13 +12420,6 @@ static int nl80211_associate(struct sk_buff *skb, struct genl_info *info) goto free; } - if (req.links[req.link_id].disabled) { - GENL_SET_ERR_MSG(info, - "cannot have assoc link disabled"); - err = -EINVAL; - goto free; - } - if (info->attrs[NL80211_ATTR_ASSOC_MLD_EXT_CAPA_OPS]) req.ext_mld_capa_ops = nla_get_u16(info->attrs[NL80211_ATTR_ASSOC_MLD_EXT_CAPA_OPS]); -- cgit v1.2.3 From a5546e18f77c0cb15d434bf5b92647687fe483e3 Mon Sep 17 00:00:00 2001 From: Daniel Borkmann Date: Thu, 15 Jan 2026 09:25:48 +0100 Subject: net: Add queue-create operation Add a ynl netdev family operation called queue-create that creates a new queue on a netdevice: name: queue-create attribute-set: queue flags: [admin-perm] do: request: attributes: - ifindex - type - lease reply: &queue-create-op attributes: - id This is a generic operation such that it can be extended for various use cases in future. Right now it is mandatory to specify ifindex, the queue type which is enforced to rx and a lease. The newly created queue id is returned to the caller. A queue from a virtual device can have a lease which refers to another queue from a physical device. This is useful for memory providers and AF_XDP operations which take an ifindex and queue id to allow applications to bind against virtual devices in containers. The lease couples both queues together and allows to proxy the operations from a virtual device in a container to the physical device. In future, the nested lease attribute can be lifted and made optional for other use-cases such as dynamic queue creation for physical netdevs. The lack of lease and the specification of the physical device as an ifindex will imply that we need a real queue to be allocated. Similarly, the queue type enforcement to rx can then be lifted as well to support tx. An early implementation had only driver-specific integration [0], but in order for other virtual devices to reuse, it makes sense to have this as a generic API in core net. For leasing queues, the virtual netdev must have real_num_rx_queue less than num_rx_queues at the time of calling queue-create. The queue-type must be rx as only rx queues are supported for leasing for now. We also enforce that the queue-create ifindex must point to a virtual device, and that the nested lease attribute's ifindex must point to a physical device. The nested lease attribute set contains a netns-id attribute which is currently only intended for dumping as part of the queue-get operation. Also, it is modeled as an s32 type similarly as done elsewhere in the stack. Signed-off-by: Daniel Borkmann Co-developed-by: David Wei Signed-off-by: David Wei Link: https://bpfconf.ebpf.io/bpfconf2025/bpfconf2025_material/lsfmmbpf_2025_netkit_borkmann.pdf [0] Acked-by: Stanislav Fomichev Reviewed-by: Nikolay Aleksandrov Link: https://patch.msgid.link/20260115082603.219152-2-daniel@iogearbox.net Signed-off-by: Paolo Abeni --- Documentation/netlink/specs/netdev.yaml | 44 +++++++++++++++++++++++++++++++++ include/uapi/linux/netdev.h | 11 +++++++++ net/core/netdev-genl-gen.c | 20 +++++++++++++++ net/core/netdev-genl-gen.h | 2 ++ net/core/netdev-genl.c | 5 ++++ tools/include/uapi/linux/netdev.h | 11 +++++++++ 6 files changed, 93 insertions(+) (limited to 'include/uapi/linux') diff --git a/Documentation/netlink/specs/netdev.yaml b/Documentation/netlink/specs/netdev.yaml index 596c306ce52b..b86db8656eac 100644 --- a/Documentation/netlink/specs/netdev.yaml +++ b/Documentation/netlink/specs/netdev.yaml @@ -339,6 +339,15 @@ attribute-sets: doc: XSK information for this queue, if any. type: nest nested-attributes: xsk-info + - + name: lease + doc: | + A queue from a virtual device can have a lease which refers to + another queue from a physical device. This is useful for memory + providers and AF_XDP operations which take an ifindex and queue id + to allow applications to bind against virtual devices in containers. + type: nest + nested-attributes: lease - name: qstats doc: | @@ -537,6 +546,24 @@ attribute-sets: name: id - name: type + - + name: lease + attributes: + - + name: ifindex + doc: The netdev ifindex to lease the queue from. + type: u32 + checks: + min: 1 + - + name: queue + doc: The netdev queue to lease from. + type: nest + nested-attributes: queue-id + - + name: netns-id + doc: The network namespace id of the netdev. + type: s32 - name: dmabuf attributes: @@ -686,6 +713,7 @@ operations: - dmabuf - io-uring - xsk + - lease dump: request: attributes: @@ -797,6 +825,22 @@ operations: reply: attributes: - id + - + name: queue-create + doc: | + Create a new queue for the given netdevice. Whether this operation + is supported depends on the device and the driver. + attribute-set: queue + flags: [admin-perm] + do: + request: + attributes: + - ifindex + - type + - lease + reply: &queue-create-op + attributes: + - id kernel-family: headers: ["net/netdev_netlink.h"] diff --git a/include/uapi/linux/netdev.h b/include/uapi/linux/netdev.h index e0b579a1df4f..7df1056a35fd 100644 --- a/include/uapi/linux/netdev.h +++ b/include/uapi/linux/netdev.h @@ -160,6 +160,7 @@ enum { NETDEV_A_QUEUE_DMABUF, NETDEV_A_QUEUE_IO_URING, NETDEV_A_QUEUE_XSK, + NETDEV_A_QUEUE_LEASE, __NETDEV_A_QUEUE_MAX, NETDEV_A_QUEUE_MAX = (__NETDEV_A_QUEUE_MAX - 1) @@ -202,6 +203,15 @@ enum { NETDEV_A_QSTATS_MAX = (__NETDEV_A_QSTATS_MAX - 1) }; +enum { + NETDEV_A_LEASE_IFINDEX = 1, + NETDEV_A_LEASE_QUEUE, + NETDEV_A_LEASE_NETNS_ID, + + __NETDEV_A_LEASE_MAX, + NETDEV_A_LEASE_MAX = (__NETDEV_A_LEASE_MAX - 1) +}; + enum { NETDEV_A_DMABUF_IFINDEX = 1, NETDEV_A_DMABUF_QUEUES, @@ -228,6 +238,7 @@ enum { NETDEV_CMD_BIND_RX, NETDEV_CMD_NAPI_SET, NETDEV_CMD_BIND_TX, + NETDEV_CMD_QUEUE_CREATE, __NETDEV_CMD_MAX, NETDEV_CMD_MAX = (__NETDEV_CMD_MAX - 1) diff --git a/net/core/netdev-genl-gen.c b/net/core/netdev-genl-gen.c index ba673e81716f..52ba99c019e7 100644 --- a/net/core/netdev-genl-gen.c +++ b/net/core/netdev-genl-gen.c @@ -28,6 +28,12 @@ static const struct netlink_range_validation netdev_a_napi_defer_hard_irqs_range }; /* Common nested types */ +const struct nla_policy netdev_lease_nl_policy[NETDEV_A_LEASE_NETNS_ID + 1] = { + [NETDEV_A_LEASE_IFINDEX] = NLA_POLICY_MIN(NLA_U32, 1), + [NETDEV_A_LEASE_QUEUE] = NLA_POLICY_NESTED(netdev_queue_id_nl_policy), + [NETDEV_A_LEASE_NETNS_ID] = { .type = NLA_S32, }, +}; + const struct nla_policy netdev_page_pool_info_nl_policy[NETDEV_A_PAGE_POOL_IFINDEX + 1] = { [NETDEV_A_PAGE_POOL_ID] = NLA_POLICY_FULL_RANGE(NLA_UINT, &netdev_a_page_pool_id_range), [NETDEV_A_PAGE_POOL_IFINDEX] = NLA_POLICY_FULL_RANGE(NLA_U32, &netdev_a_page_pool_ifindex_range), @@ -107,6 +113,13 @@ static const struct nla_policy netdev_bind_tx_nl_policy[NETDEV_A_DMABUF_FD + 1] [NETDEV_A_DMABUF_FD] = { .type = NLA_U32, }, }; +/* NETDEV_CMD_QUEUE_CREATE - do */ +static const struct nla_policy netdev_queue_create_nl_policy[NETDEV_A_QUEUE_LEASE + 1] = { + [NETDEV_A_QUEUE_IFINDEX] = NLA_POLICY_MIN(NLA_U32, 1), + [NETDEV_A_QUEUE_TYPE] = NLA_POLICY_MAX(NLA_U32, 1), + [NETDEV_A_QUEUE_LEASE] = NLA_POLICY_NESTED(netdev_lease_nl_policy), +}; + /* Ops table for netdev */ static const struct genl_split_ops netdev_nl_ops[] = { { @@ -205,6 +218,13 @@ static const struct genl_split_ops netdev_nl_ops[] = { .maxattr = NETDEV_A_DMABUF_FD, .flags = GENL_CMD_CAP_DO, }, + { + .cmd = NETDEV_CMD_QUEUE_CREATE, + .doit = netdev_nl_queue_create_doit, + .policy = netdev_queue_create_nl_policy, + .maxattr = NETDEV_A_QUEUE_LEASE, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, }; static const struct genl_multicast_group netdev_nl_mcgrps[] = { diff --git a/net/core/netdev-genl-gen.h b/net/core/netdev-genl-gen.h index cffc08517a41..d71b435d72c1 100644 --- a/net/core/netdev-genl-gen.h +++ b/net/core/netdev-genl-gen.h @@ -14,6 +14,7 @@ #include /* Common nested types */ +extern const struct nla_policy netdev_lease_nl_policy[NETDEV_A_LEASE_NETNS_ID + 1]; extern const struct nla_policy netdev_page_pool_info_nl_policy[NETDEV_A_PAGE_POOL_IFINDEX + 1]; extern const struct nla_policy netdev_queue_id_nl_policy[NETDEV_A_QUEUE_TYPE + 1]; @@ -36,6 +37,7 @@ int netdev_nl_qstats_get_dumpit(struct sk_buff *skb, int netdev_nl_bind_rx_doit(struct sk_buff *skb, struct genl_info *info); int netdev_nl_napi_set_doit(struct sk_buff *skb, struct genl_info *info); int netdev_nl_bind_tx_doit(struct sk_buff *skb, struct genl_info *info); +int netdev_nl_queue_create_doit(struct sk_buff *skb, struct genl_info *info); enum { NETDEV_NLGRP_MGMT, diff --git a/net/core/netdev-genl.c b/net/core/netdev-genl.c index 470fabbeacd9..aae75431858d 100644 --- a/net/core/netdev-genl.c +++ b/net/core/netdev-genl.c @@ -1120,6 +1120,11 @@ err_genlmsg_free: return err; } +int netdev_nl_queue_create_doit(struct sk_buff *skb, struct genl_info *info) +{ + return -EOPNOTSUPP; +} + void netdev_nl_sock_priv_init(struct netdev_nl_sock *priv) { INIT_LIST_HEAD(&priv->bindings); diff --git a/tools/include/uapi/linux/netdev.h b/tools/include/uapi/linux/netdev.h index e0b579a1df4f..7df1056a35fd 100644 --- a/tools/include/uapi/linux/netdev.h +++ b/tools/include/uapi/linux/netdev.h @@ -160,6 +160,7 @@ enum { NETDEV_A_QUEUE_DMABUF, NETDEV_A_QUEUE_IO_URING, NETDEV_A_QUEUE_XSK, + NETDEV_A_QUEUE_LEASE, __NETDEV_A_QUEUE_MAX, NETDEV_A_QUEUE_MAX = (__NETDEV_A_QUEUE_MAX - 1) @@ -202,6 +203,15 @@ enum { NETDEV_A_QSTATS_MAX = (__NETDEV_A_QSTATS_MAX - 1) }; +enum { + NETDEV_A_LEASE_IFINDEX = 1, + NETDEV_A_LEASE_QUEUE, + NETDEV_A_LEASE_NETNS_ID, + + __NETDEV_A_LEASE_MAX, + NETDEV_A_LEASE_MAX = (__NETDEV_A_LEASE_MAX - 1) +}; + enum { NETDEV_A_DMABUF_IFINDEX = 1, NETDEV_A_DMABUF_QUEUES, @@ -228,6 +238,7 @@ enum { NETDEV_CMD_BIND_RX, NETDEV_CMD_NAPI_SET, NETDEV_CMD_BIND_TX, + NETDEV_CMD_QUEUE_CREATE, __NETDEV_CMD_MAX, NETDEV_CMD_MAX = (__NETDEV_CMD_MAX - 1) -- cgit v1.2.3 From b5c3fa4a0b16d4a7d0bd0e5626a13fec0024030a Mon Sep 17 00:00:00 2001 From: Daniel Borkmann Date: Thu, 15 Jan 2026 09:25:56 +0100 Subject: netkit: Add single device mode for netkit Add a single device mode for netkit instead of netkit pairs. The primary target for the paired devices is to connect network namespaces, of course, and support has been implemented in projects like Cilium [0]. For the rxq leasing the plan is to support two main scenarios related to single device mode: * For the use-case of io_uring zero-copy, the control plane can either set up a netkit pair where the peer device can perform rxq leasing which is then tied to the lifetime of the peer device, or the control plane can use a regular netkit pair to connect the hostns to a Pod/container and dynamically add/remove rxq leasing through a single device without having to interrupt the device pair. In the case of io_uring, the memory pool is used as skb non-linear pages, and thus the skb will go its way through the regular stack into netkit. Things like the netkit policy when no BPF is attached or skb scrubbing etc apply as-is in case the paired devices are used, or if the backend memory is tied to the single device and traffic goes through a paired device. * For the use-case of AF_XDP, the control plane needs to use netkit in the single device mode. The single device mode currently enforces only a pass policy when no BPF is attached, and does not yet support BPF link attachments for AF_XDP. skbs sent to that device get dropped at the moment. Given AF_XDP operates at a lower layer of the stack tying this to the netkit pair did not make sense. In future, the plan is to allow BPF at the XDP layer which can: i) process traffic coming from the AF_XDP application (e.g. QEMU with AF_XDP backend) to filter egress traffic or to push selected egress traffic up to the single netkit device to the local stack (e.g. DHCP requests), and ii) vice-versa skbs sent to the single netkit into the AF_XDP application (e.g. DHCP replies). Also, the control-plane can dynamically manage rxq leasing for the single netkit device without having to interrupt (e.g. down/up cycle) the main netkit pair for the Pod which has traffic going in and out. Signed-off-by: Daniel Borkmann Co-developed-by: David Wei Signed-off-by: David Wei Reviewed-by: Jordan Rife Reviewed-by: Nikolay Aleksandrov Link: https://docs.cilium.io/en/stable/operations/performance/tuning/#netkit-device-mode [0] Link: https://patch.msgid.link/20260115082603.219152-10-daniel@iogearbox.net Signed-off-by: Paolo Abeni --- drivers/net/netkit.c | 110 +++++++++++++++++++++++++++---------------- include/uapi/linux/if_link.h | 6 +++ 2 files changed, 76 insertions(+), 40 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/net/netkit.c b/drivers/net/netkit.c index 0a2fef7caccb..76332a98af92 100644 --- a/drivers/net/netkit.c +++ b/drivers/net/netkit.c @@ -26,6 +26,7 @@ struct netkit { __cacheline_group_begin(netkit_slowpath); enum netkit_mode mode; + enum netkit_pairing pair; bool primary; u32 headroom; __cacheline_group_end(netkit_slowpath); @@ -135,6 +136,10 @@ static int netkit_open(struct net_device *dev) struct netkit *nk = netkit_priv(dev); struct net_device *peer = rtnl_dereference(nk->peer); + if (nk->pair == NETKIT_DEVICE_SINGLE) { + netif_carrier_on(dev); + return 0; + } if (!peer) return -ENOTCONN; if (peer->flags & IFF_UP) { @@ -335,6 +340,7 @@ static int netkit_new_link(struct net_device *dev, enum netkit_scrub scrub_prim = NETKIT_SCRUB_DEFAULT; enum netkit_scrub scrub_peer = NETKIT_SCRUB_DEFAULT; struct nlattr *peer_tb[IFLA_MAX + 1], **tbp, *attr; + enum netkit_pairing pair = NETKIT_DEVICE_PAIR; enum netkit_action policy_prim = NETKIT_PASS; enum netkit_action policy_peer = NETKIT_PASS; struct nlattr **data = params->data; @@ -343,7 +349,8 @@ static int netkit_new_link(struct net_device *dev, struct nlattr **tb = params->tb; u16 headroom = 0, tailroom = 0; struct ifinfomsg *ifmp = NULL; - struct net_device *peer; + struct net_device *peer = NULL; + bool seen_peer = false; char ifname[IFNAMSIZ]; struct netkit *nk; int err; @@ -380,6 +387,12 @@ static int netkit_new_link(struct net_device *dev, headroom = nla_get_u16(data[IFLA_NETKIT_HEADROOM]); if (data[IFLA_NETKIT_TAILROOM]) tailroom = nla_get_u16(data[IFLA_NETKIT_TAILROOM]); + if (data[IFLA_NETKIT_PAIRING]) + pair = nla_get_u32(data[IFLA_NETKIT_PAIRING]); + + seen_peer = data[IFLA_NETKIT_PEER_INFO] || + data[IFLA_NETKIT_PEER_SCRUB] || + data[IFLA_NETKIT_PEER_POLICY]; } if (ifmp && tbp[IFLA_IFNAME]) { @@ -392,45 +405,46 @@ static int netkit_new_link(struct net_device *dev, if (mode != NETKIT_L2 && (tb[IFLA_ADDRESS] || tbp[IFLA_ADDRESS])) return -EOPNOTSUPP; + if (pair == NETKIT_DEVICE_SINGLE && + (tb != tbp || seen_peer || policy_prim != NETKIT_PASS)) + return -EOPNOTSUPP; - peer = rtnl_create_link(peer_net, ifname, ifname_assign_type, - &netkit_link_ops, tbp, extack); - if (IS_ERR(peer)) - return PTR_ERR(peer); - - netif_inherit_tso_max(peer, dev); - if (headroom) { - peer->needed_headroom = headroom; - dev->needed_headroom = headroom; - } - if (tailroom) { - peer->needed_tailroom = tailroom; - dev->needed_tailroom = tailroom; - } - - if (mode == NETKIT_L2 && !(ifmp && tbp[IFLA_ADDRESS])) - eth_hw_addr_random(peer); - if (ifmp && dev->ifindex) - peer->ifindex = ifmp->ifi_index; - - nk = netkit_priv(peer); - nk->primary = false; - nk->policy = policy_peer; - nk->scrub = scrub_peer; - nk->mode = mode; - nk->headroom = headroom; - bpf_mprog_bundle_init(&nk->bundle); + if (pair == NETKIT_DEVICE_PAIR) { + peer = rtnl_create_link(peer_net, ifname, ifname_assign_type, + &netkit_link_ops, tbp, extack); + if (IS_ERR(peer)) + return PTR_ERR(peer); + + netif_inherit_tso_max(peer, dev); + if (headroom) + peer->needed_headroom = headroom; + if (tailroom) + peer->needed_tailroom = tailroom; + if (mode == NETKIT_L2 && !(ifmp && tbp[IFLA_ADDRESS])) + eth_hw_addr_random(peer); + if (ifmp && dev->ifindex) + peer->ifindex = ifmp->ifi_index; - err = register_netdevice(peer); - if (err < 0) - goto err_register_peer; - netif_carrier_off(peer); - if (mode == NETKIT_L2) - dev_change_flags(peer, peer->flags & ~IFF_NOARP, NULL); + nk = netkit_priv(peer); + nk->primary = false; + nk->policy = policy_peer; + nk->scrub = scrub_peer; + nk->mode = mode; + nk->pair = pair; + nk->headroom = headroom; + bpf_mprog_bundle_init(&nk->bundle); + + err = register_netdevice(peer); + if (err < 0) + goto err_register_peer; + netif_carrier_off(peer); + if (mode == NETKIT_L2) + dev_change_flags(peer, peer->flags & ~IFF_NOARP, NULL); - err = rtnl_configure_link(peer, NULL, 0, NULL); - if (err < 0) - goto err_configure_peer; + err = rtnl_configure_link(peer, NULL, 0, NULL); + if (err < 0) + goto err_configure_peer; + } if (mode == NETKIT_L2 && !tb[IFLA_ADDRESS]) eth_hw_addr_random(dev); @@ -438,12 +452,17 @@ static int netkit_new_link(struct net_device *dev, nla_strscpy(dev->name, tb[IFLA_IFNAME], IFNAMSIZ); else strscpy(dev->name, "nk%d", IFNAMSIZ); + if (headroom) + dev->needed_headroom = headroom; + if (tailroom) + dev->needed_tailroom = tailroom; nk = netkit_priv(dev); nk->primary = true; nk->policy = policy_prim; nk->scrub = scrub_prim; nk->mode = mode; + nk->pair = pair; nk->headroom = headroom; bpf_mprog_bundle_init(&nk->bundle); @@ -455,10 +474,12 @@ static int netkit_new_link(struct net_device *dev, dev_change_flags(dev, dev->flags & ~IFF_NOARP, NULL); rcu_assign_pointer(netkit_priv(dev)->peer, peer); - rcu_assign_pointer(netkit_priv(peer)->peer, dev); + if (peer) + rcu_assign_pointer(netkit_priv(peer)->peer, dev); return 0; err_configure_peer: - unregister_netdevice(peer); + if (peer) + unregister_netdevice(peer); return err; err_register_peer: free_netdev(peer); @@ -518,6 +539,8 @@ static struct net_device *netkit_dev_fetch(struct net *net, u32 ifindex, u32 whi nk = netkit_priv(dev); if (!nk->primary) return ERR_PTR(-EACCES); + if (nk->pair == NETKIT_DEVICE_SINGLE) + return ERR_PTR(-EOPNOTSUPP); if (which == BPF_NETKIT_PEER) { dev = rcu_dereference_rtnl(nk->peer); if (!dev) @@ -879,6 +902,7 @@ static int netkit_change_link(struct net_device *dev, struct nlattr *tb[], { IFLA_NETKIT_PEER_INFO, "peer info" }, { IFLA_NETKIT_HEADROOM, "headroom" }, { IFLA_NETKIT_TAILROOM, "tailroom" }, + { IFLA_NETKIT_PAIRING, "pairing" }, }; if (!nk->primary) { @@ -898,9 +922,11 @@ static int netkit_change_link(struct net_device *dev, struct nlattr *tb[], } if (data[IFLA_NETKIT_POLICY]) { + err = -EOPNOTSUPP; attr = data[IFLA_NETKIT_POLICY]; policy = nla_get_u32(attr); - err = netkit_check_policy(policy, attr, extack); + if (nk->pair == NETKIT_DEVICE_PAIR) + err = netkit_check_policy(policy, attr, extack); if (err) return err; WRITE_ONCE(nk->policy, policy); @@ -931,6 +957,7 @@ static size_t netkit_get_size(const struct net_device *dev) nla_total_size(sizeof(u8)) + /* IFLA_NETKIT_PRIMARY */ nla_total_size(sizeof(u16)) + /* IFLA_NETKIT_HEADROOM */ nla_total_size(sizeof(u16)) + /* IFLA_NETKIT_TAILROOM */ + nla_total_size(sizeof(u32)) + /* IFLA_NETKIT_PAIRING */ 0; } @@ -951,6 +978,8 @@ static int netkit_fill_info(struct sk_buff *skb, const struct net_device *dev) return -EMSGSIZE; if (nla_put_u16(skb, IFLA_NETKIT_TAILROOM, dev->needed_tailroom)) return -EMSGSIZE; + if (nla_put_u32(skb, IFLA_NETKIT_PAIRING, nk->pair)) + return -EMSGSIZE; if (peer) { nk = netkit_priv(peer); @@ -972,6 +1001,7 @@ static const struct nla_policy netkit_policy[IFLA_NETKIT_MAX + 1] = { [IFLA_NETKIT_TAILROOM] = { .type = NLA_U16 }, [IFLA_NETKIT_SCRUB] = NLA_POLICY_MAX(NLA_U32, NETKIT_SCRUB_DEFAULT), [IFLA_NETKIT_PEER_SCRUB] = NLA_POLICY_MAX(NLA_U32, NETKIT_SCRUB_DEFAULT), + [IFLA_NETKIT_PAIRING] = NLA_POLICY_MAX(NLA_U32, NETKIT_DEVICE_SINGLE), [IFLA_NETKIT_PRIMARY] = { .type = NLA_REJECT, .reject_message = "Primary attribute is read-only" }, }; diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h index 3b491d96e52e..bbd565757298 100644 --- a/include/uapi/linux/if_link.h +++ b/include/uapi/linux/if_link.h @@ -1296,6 +1296,11 @@ enum netkit_mode { NETKIT_L3, }; +enum netkit_pairing { + NETKIT_DEVICE_PAIR, + NETKIT_DEVICE_SINGLE, +}; + /* NETKIT_SCRUB_NONE leaves clearing skb->{mark,priority} up to * the BPF program if attached. This also means the latter can * consume the two fields if they were populated earlier. @@ -1320,6 +1325,7 @@ enum { IFLA_NETKIT_PEER_SCRUB, IFLA_NETKIT_HEADROOM, IFLA_NETKIT_TAILROOM, + IFLA_NETKIT_PAIRING, __IFLA_NETKIT_MAX, }; #define IFLA_NETKIT_MAX (__IFLA_NETKIT_MAX - 1) -- cgit v1.2.3 From 8766d61a1d33cb5f15bfdd6ce9832bbe1fc649c2 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Tue, 20 Jan 2026 18:04:55 -0800 Subject: Revert "Merge branch 'netkit-support-for-io_uring-zero-copy-and-af_xdp'" This reverts commit 77b9c4a438fc66e2ab004c411056b3fb71a54f2c, reversing changes made to 4515ec4ad58a37e70a9e1256c0b993958c9b7497: 931420a2fc36 ("selftests/net: Add netkit container tests") ab771c938d9a ("selftests/net: Make NetDrvContEnv support queue leasing") 6be87fbb2776 ("selftests/net: Add env for container based tests") 61d99ce3dfc2 ("selftests/net: Add bpf skb forwarding program") 920da3634194 ("netkit: Add xsk support for af_xdp applications") eef51113f8af ("netkit: Add netkit notifier to check for unregistering devices") b5ef109d22d4 ("netkit: Implement rtnl_link_ops->alloc and ndo_queue_create") b5c3fa4a0b16 ("netkit: Add single device mode for netkit") 0073d2fd679d ("xsk: Proxy pool management for leased queues") 1ecea95dd3b5 ("xsk: Extend xsk_rcv_check validation") 804bf334d08a ("net: Proxy netdev_queue_get_dma_dev for leased queues") 0caa9a8ddec3 ("net: Proxy net_mp_{open,close}_rxq for leased queues") ff8889ff9107 ("net, ethtool: Disallow leased real rxqs to be resized") 9e2103f36110 ("net: Add lease info to queue-get response") 31127deddef4 ("net: Implement netdev_nl_queue_create_doit") a5546e18f77c ("net: Add queue-create operation") The series will conflict with io_uring work, and the code needs more polish. Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/netdev.yaml | 44 --- drivers/net/netkit.c | 360 ++++----------------- include/linux/netdevice.h | 6 - include/net/netdev_queues.h | 19 +- include/net/netdev_rx_queue.h | 21 +- include/net/page_pool/memory_provider.h | 4 +- include/net/xdp_sock_drv.h | 2 +- include/uapi/linux/if_link.h | 6 - include/uapi/linux/netdev.h | 11 - net/core/dev.c | 7 - net/core/dev.h | 2 - net/core/netdev-genl-gen.c | 20 -- net/core/netdev-genl-gen.h | 2 - net/core/netdev-genl.c | 185 ----------- net/core/netdev_queues.c | 74 +---- net/core/netdev_rx_queue.c | 169 ++-------- net/ethtool/channels.c | 12 +- net/ethtool/ioctl.c | 9 +- net/xdp/xsk.c | 79 +---- tools/include/uapi/linux/netdev.h | 11 - tools/testing/selftests/drivers/net/README.rst | 7 - tools/testing/selftests/drivers/net/hw/Makefile | 2 - .../selftests/drivers/net/hw/lib/py/__init__.py | 7 +- .../selftests/drivers/net/hw/nk_forward.bpf.c | 49 --- tools/testing/selftests/drivers/net/hw/nk_netns.py | 23 -- .../testing/selftests/drivers/net/hw/nk_qlease.py | 55 ---- .../selftests/drivers/net/lib/py/__init__.py | 7 +- tools/testing/selftests/drivers/net/lib/py/env.py | 157 --------- 28 files changed, 117 insertions(+), 1233 deletions(-) delete mode 100644 tools/testing/selftests/drivers/net/hw/nk_forward.bpf.c delete mode 100755 tools/testing/selftests/drivers/net/hw/nk_netns.py delete mode 100755 tools/testing/selftests/drivers/net/hw/nk_qlease.py (limited to 'include/uapi/linux') diff --git a/Documentation/netlink/specs/netdev.yaml b/Documentation/netlink/specs/netdev.yaml index b86db8656eac..596c306ce52b 100644 --- a/Documentation/netlink/specs/netdev.yaml +++ b/Documentation/netlink/specs/netdev.yaml @@ -339,15 +339,6 @@ attribute-sets: doc: XSK information for this queue, if any. type: nest nested-attributes: xsk-info - - - name: lease - doc: | - A queue from a virtual device can have a lease which refers to - another queue from a physical device. This is useful for memory - providers and AF_XDP operations which take an ifindex and queue id - to allow applications to bind against virtual devices in containers. - type: nest - nested-attributes: lease - name: qstats doc: | @@ -546,24 +537,6 @@ attribute-sets: name: id - name: type - - - name: lease - attributes: - - - name: ifindex - doc: The netdev ifindex to lease the queue from. - type: u32 - checks: - min: 1 - - - name: queue - doc: The netdev queue to lease from. - type: nest - nested-attributes: queue-id - - - name: netns-id - doc: The network namespace id of the netdev. - type: s32 - name: dmabuf attributes: @@ -713,7 +686,6 @@ operations: - dmabuf - io-uring - xsk - - lease dump: request: attributes: @@ -825,22 +797,6 @@ operations: reply: attributes: - id - - - name: queue-create - doc: | - Create a new queue for the given netdevice. Whether this operation - is supported depends on the device and the driver. - attribute-set: queue - flags: [admin-perm] - do: - request: - attributes: - - ifindex - - type - - lease - reply: &queue-create-op - attributes: - - id kernel-family: headers: ["net/netdev_netlink.h"] diff --git a/drivers/net/netkit.c b/drivers/net/netkit.c index 0519f855d062..0a2fef7caccb 100644 --- a/drivers/net/netkit.c +++ b/drivers/net/netkit.c @@ -9,21 +9,11 @@ #include #include -#include -#include -#include -#include #include #include #include -#define NETKIT_DRV_NAME "netkit" - -#define NETKIT_NUM_RX_QUEUES_MAX 1024 -#define NETKIT_NUM_TX_QUEUES_MAX 1 - -#define NETKIT_NUM_RX_QUEUES_REAL 1 -#define NETKIT_NUM_TX_QUEUES_REAL 1 +#define DRV_NAME "netkit" struct netkit { __cacheline_group_begin(netkit_fastpath); @@ -36,7 +26,6 @@ struct netkit { __cacheline_group_begin(netkit_slowpath); enum netkit_mode mode; - enum netkit_pairing pair; bool primary; u32 headroom; __cacheline_group_end(netkit_slowpath); @@ -47,8 +36,6 @@ struct netkit_link { struct net_device *dev; }; -static struct rtnl_link_ops netkit_link_ops; - static __always_inline int netkit_run(const struct bpf_mprog_entry *entry, struct sk_buff *skb, enum netkit_action ret) @@ -148,10 +135,6 @@ static int netkit_open(struct net_device *dev) struct netkit *nk = netkit_priv(dev); struct net_device *peer = rtnl_dereference(nk->peer); - if (nk->pair == NETKIT_DEVICE_SINGLE) { - netif_carrier_on(dev); - return 0; - } if (!peer) return -ENOTCONN; if (peer->flags & IFF_UP) { @@ -236,86 +219,9 @@ static void netkit_get_stats(struct net_device *dev, stats->tx_dropped = DEV_STATS_READ(dev, tx_dropped); } -static bool netkit_xsk_supported_at_phys(const struct net_device *dev) -{ - if (!dev->netdev_ops->ndo_bpf || - !dev->netdev_ops->ndo_xdp_xmit || - !dev->netdev_ops->ndo_xsk_wakeup) - return false; - if ((dev->xdp_features & NETDEV_XDP_ACT_XSK) != NETDEV_XDP_ACT_XSK) - return false; - return true; -} - -static int netkit_xsk(struct net_device *dev, struct netdev_bpf *xdp) -{ - struct netkit *nk = netkit_priv(dev); - struct netdev_bpf xdp_lower; - struct netdev_rx_queue *rxq; - struct net_device *phys; - int ret = -EBUSY; - - switch (xdp->command) { - case XDP_SETUP_XSK_POOL: - if (nk->pair == NETKIT_DEVICE_PAIR) - return -EOPNOTSUPP; - if (xdp->xsk.queue_id >= dev->real_num_rx_queues) - return -EINVAL; - - rxq = __netif_get_rx_queue(dev, xdp->xsk.queue_id); - if (!rxq->lease) - return -EOPNOTSUPP; - - phys = rxq->lease->dev; - if (!netkit_xsk_supported_at_phys(phys)) - return -EOPNOTSUPP; - - memcpy(&xdp_lower, xdp, sizeof(xdp_lower)); - xdp_lower.xsk.queue_id = get_netdev_rx_queue_index(rxq->lease); - break; - case XDP_SETUP_PROG: - return -EPERM; - default: - return -EINVAL; - } - - netdev_lock(phys); - if (!dev_get_min_mp_channel_count(phys)) - ret = phys->netdev_ops->ndo_bpf(phys, &xdp_lower); - netdev_unlock(phys); - return ret; -} - -static int netkit_xsk_wakeup(struct net_device *dev, u32 queue_id, u32 flags) -{ - struct netdev_rx_queue *rxq; - struct net_device *phys; - - if (queue_id >= dev->real_num_rx_queues) - return -EINVAL; - - rxq = __netif_get_rx_queue(dev, queue_id); - if (!rxq->lease) - return -EOPNOTSUPP; - - phys = rxq->lease->dev; - if (!netkit_xsk_supported_at_phys(phys)) - return -EOPNOTSUPP; - - return phys->netdev_ops->ndo_xsk_wakeup(phys, - get_netdev_rx_queue_index(rxq->lease), flags); -} - -static int netkit_init(struct net_device *dev) -{ - netdev_lockdep_set_classes(dev); - return 0; -} - static void netkit_uninit(struct net_device *dev); static const struct net_device_ops netkit_netdev_ops = { - .ndo_init = netkit_init, .ndo_open = netkit_open, .ndo_stop = netkit_close, .ndo_start_xmit = netkit_xmit, @@ -326,95 +232,19 @@ static const struct net_device_ops netkit_netdev_ops = { .ndo_get_peer_dev = netkit_peer_dev, .ndo_get_stats64 = netkit_get_stats, .ndo_uninit = netkit_uninit, - .ndo_bpf = netkit_xsk, - .ndo_xsk_wakeup = netkit_xsk_wakeup, .ndo_features_check = passthru_features_check, }; static void netkit_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info) { - strscpy(info->driver, NETKIT_DRV_NAME, sizeof(info->driver)); + strscpy(info->driver, DRV_NAME, sizeof(info->driver)); } static const struct ethtool_ops netkit_ethtool_ops = { .get_drvinfo = netkit_get_drvinfo, }; -static int netkit_queue_create(struct net_device *dev) -{ - struct netkit *nk = netkit_priv(dev); - u32 rxq_count_old, rxq_count_new; - int err; - - rxq_count_old = dev->real_num_rx_queues; - rxq_count_new = rxq_count_old + 1; - - /* Only allow to lease a queue in single device mode or to - * lease against the peer device which then ends up in the - * target netns. - */ - if (nk->pair == NETKIT_DEVICE_PAIR && nk->primary) - return -EOPNOTSUPP; - - if (netif_running(dev)) - netif_carrier_off(dev); - err = netif_set_real_num_rx_queues(dev, rxq_count_new); - if (netif_running(dev)) - netif_carrier_on(dev); - - return err ? : rxq_count_old; -} - -static const struct netdev_queue_mgmt_ops netkit_queue_mgmt_ops = { - .ndo_queue_create = netkit_queue_create, -}; - -static struct net_device *netkit_alloc(struct nlattr *tb[], - const char *ifname, - unsigned char name_assign_type, - unsigned int num_tx_queues, - unsigned int num_rx_queues) -{ - const struct rtnl_link_ops *ops = &netkit_link_ops; - struct net_device *dev; - - if (num_tx_queues > NETKIT_NUM_TX_QUEUES_MAX || - num_rx_queues > NETKIT_NUM_RX_QUEUES_MAX) - return ERR_PTR(-EOPNOTSUPP); - - dev = alloc_netdev_mqs(ops->priv_size, ifname, - name_assign_type, ops->setup, - num_tx_queues, num_rx_queues); - if (dev) { - dev->real_num_tx_queues = NETKIT_NUM_TX_QUEUES_REAL; - dev->real_num_rx_queues = NETKIT_NUM_RX_QUEUES_REAL; - } - return dev; -} - -static void netkit_queue_unlease(struct net_device *dev) -{ - struct netdev_rx_queue *rxq, *rxq_lease; - struct net_device *dev_lease; - int i; - - if (dev->real_num_rx_queues == 1) - return; - - netdev_lock(dev); - for (i = 1; i < dev->real_num_rx_queues; i++) { - rxq = __netif_get_rx_queue(dev, i); - rxq_lease = rxq->lease; - dev_lease = rxq_lease->dev; - - netdev_lock(dev_lease); - netdev_rx_queue_unlease(rxq, rxq_lease); - netdev_unlock(dev_lease); - } - netdev_unlock(dev); -} - static void netkit_setup(struct net_device *dev) { static const netdev_features_t netkit_features_hw_vlan = @@ -445,20 +275,18 @@ static void netkit_setup(struct net_device *dev) dev->priv_flags |= IFF_DISABLE_NETPOLL; dev->lltx = true; - dev->netdev_ops = &netkit_netdev_ops; - dev->ethtool_ops = &netkit_ethtool_ops; - dev->queue_mgmt_ops = &netkit_queue_mgmt_ops; + dev->ethtool_ops = &netkit_ethtool_ops; + dev->netdev_ops = &netkit_netdev_ops; dev->features |= netkit_features; dev->hw_features = netkit_features; dev->hw_enc_features = netkit_features; dev->mpls_features = NETIF_F_HW_CSUM | NETIF_F_GSO_SOFTWARE; dev->vlan_features = dev->features & ~netkit_features_hw_vlan; + dev->needs_free_netdev = true; netif_set_tso_max_size(dev, GSO_MAX_SIZE); - - xdp_set_features_flag(dev, NETDEV_XDP_ACT_XSK); } static struct net *netkit_get_link_net(const struct net_device *dev) @@ -497,6 +325,8 @@ static int netkit_validate(struct nlattr *tb[], struct nlattr *data[], return 0; } +static struct rtnl_link_ops netkit_link_ops; + static int netkit_new_link(struct net_device *dev, struct rtnl_newlink_params *params, struct netlink_ext_ack *extack) @@ -505,7 +335,6 @@ static int netkit_new_link(struct net_device *dev, enum netkit_scrub scrub_prim = NETKIT_SCRUB_DEFAULT; enum netkit_scrub scrub_peer = NETKIT_SCRUB_DEFAULT; struct nlattr *peer_tb[IFLA_MAX + 1], **tbp, *attr; - enum netkit_pairing pair = NETKIT_DEVICE_PAIR; enum netkit_action policy_prim = NETKIT_PASS; enum netkit_action policy_peer = NETKIT_PASS; struct nlattr **data = params->data; @@ -514,8 +343,7 @@ static int netkit_new_link(struct net_device *dev, struct nlattr **tb = params->tb; u16 headroom = 0, tailroom = 0; struct ifinfomsg *ifmp = NULL; - struct net_device *peer = NULL; - bool seen_peer = false; + struct net_device *peer; char ifname[IFNAMSIZ]; struct netkit *nk; int err; @@ -552,12 +380,6 @@ static int netkit_new_link(struct net_device *dev, headroom = nla_get_u16(data[IFLA_NETKIT_HEADROOM]); if (data[IFLA_NETKIT_TAILROOM]) tailroom = nla_get_u16(data[IFLA_NETKIT_TAILROOM]); - if (data[IFLA_NETKIT_PAIRING]) - pair = nla_get_u32(data[IFLA_NETKIT_PAIRING]); - - seen_peer = data[IFLA_NETKIT_PEER_INFO] || - data[IFLA_NETKIT_PEER_SCRUB] || - data[IFLA_NETKIT_PEER_POLICY]; } if (ifmp && tbp[IFLA_IFNAME]) { @@ -570,46 +392,45 @@ static int netkit_new_link(struct net_device *dev, if (mode != NETKIT_L2 && (tb[IFLA_ADDRESS] || tbp[IFLA_ADDRESS])) return -EOPNOTSUPP; - if (pair == NETKIT_DEVICE_SINGLE && - (tb != tbp || seen_peer || policy_prim != NETKIT_PASS)) - return -EOPNOTSUPP; - if (pair == NETKIT_DEVICE_PAIR) { - peer = rtnl_create_link(peer_net, ifname, ifname_assign_type, - &netkit_link_ops, tbp, extack); - if (IS_ERR(peer)) - return PTR_ERR(peer); - - netif_inherit_tso_max(peer, dev); - if (headroom) - peer->needed_headroom = headroom; - if (tailroom) - peer->needed_tailroom = tailroom; - if (mode == NETKIT_L2 && !(ifmp && tbp[IFLA_ADDRESS])) - eth_hw_addr_random(peer); - if (ifmp && dev->ifindex) - peer->ifindex = ifmp->ifi_index; + peer = rtnl_create_link(peer_net, ifname, ifname_assign_type, + &netkit_link_ops, tbp, extack); + if (IS_ERR(peer)) + return PTR_ERR(peer); - nk = netkit_priv(peer); - nk->primary = false; - nk->policy = policy_peer; - nk->scrub = scrub_peer; - nk->mode = mode; - nk->pair = pair; - nk->headroom = headroom; - bpf_mprog_bundle_init(&nk->bundle); - - err = register_netdevice(peer); - if (err < 0) - goto err_register_peer; - netif_carrier_off(peer); - if (mode == NETKIT_L2) - dev_change_flags(peer, peer->flags & ~IFF_NOARP, NULL); - - err = rtnl_configure_link(peer, NULL, 0, NULL); - if (err < 0) - goto err_configure_peer; + netif_inherit_tso_max(peer, dev); + if (headroom) { + peer->needed_headroom = headroom; + dev->needed_headroom = headroom; } + if (tailroom) { + peer->needed_tailroom = tailroom; + dev->needed_tailroom = tailroom; + } + + if (mode == NETKIT_L2 && !(ifmp && tbp[IFLA_ADDRESS])) + eth_hw_addr_random(peer); + if (ifmp && dev->ifindex) + peer->ifindex = ifmp->ifi_index; + + nk = netkit_priv(peer); + nk->primary = false; + nk->policy = policy_peer; + nk->scrub = scrub_peer; + nk->mode = mode; + nk->headroom = headroom; + bpf_mprog_bundle_init(&nk->bundle); + + err = register_netdevice(peer); + if (err < 0) + goto err_register_peer; + netif_carrier_off(peer); + if (mode == NETKIT_L2) + dev_change_flags(peer, peer->flags & ~IFF_NOARP, NULL); + + err = rtnl_configure_link(peer, NULL, 0, NULL); + if (err < 0) + goto err_configure_peer; if (mode == NETKIT_L2 && !tb[IFLA_ADDRESS]) eth_hw_addr_random(dev); @@ -617,17 +438,12 @@ static int netkit_new_link(struct net_device *dev, nla_strscpy(dev->name, tb[IFLA_IFNAME], IFNAMSIZ); else strscpy(dev->name, "nk%d", IFNAMSIZ); - if (headroom) - dev->needed_headroom = headroom; - if (tailroom) - dev->needed_tailroom = tailroom; nk = netkit_priv(dev); nk->primary = true; nk->policy = policy_prim; nk->scrub = scrub_prim; nk->mode = mode; - nk->pair = pair; nk->headroom = headroom; bpf_mprog_bundle_init(&nk->bundle); @@ -639,12 +455,10 @@ static int netkit_new_link(struct net_device *dev, dev_change_flags(dev, dev->flags & ~IFF_NOARP, NULL); rcu_assign_pointer(netkit_priv(dev)->peer, peer); - if (peer) - rcu_assign_pointer(netkit_priv(peer)->peer, dev); + rcu_assign_pointer(netkit_priv(peer)->peer, dev); return 0; err_configure_peer: - if (peer) - unregister_netdevice(peer); + unregister_netdevice(peer); return err; err_register_peer: free_netdev(peer); @@ -704,8 +518,6 @@ static struct net_device *netkit_dev_fetch(struct net *net, u32 ifindex, u32 whi nk = netkit_priv(dev); if (!nk->primary) return ERR_PTR(-EACCES); - if (nk->pair == NETKIT_DEVICE_SINGLE) - return ERR_PTR(-EOPNOTSUPP); if (which == BPF_NETKIT_PEER) { dev = rcu_dereference_rtnl(nk->peer); if (!dev) @@ -1032,7 +844,6 @@ static void netkit_release_all(struct net_device *dev) static void netkit_uninit(struct net_device *dev) { netkit_release_all(dev); - netkit_queue_unlease(dev); } static void netkit_del_link(struct net_device *dev, struct list_head *head) @@ -1068,7 +879,6 @@ static int netkit_change_link(struct net_device *dev, struct nlattr *tb[], { IFLA_NETKIT_PEER_INFO, "peer info" }, { IFLA_NETKIT_HEADROOM, "headroom" }, { IFLA_NETKIT_TAILROOM, "tailroom" }, - { IFLA_NETKIT_PAIRING, "pairing" }, }; if (!nk->primary) { @@ -1088,11 +898,9 @@ static int netkit_change_link(struct net_device *dev, struct nlattr *tb[], } if (data[IFLA_NETKIT_POLICY]) { - err = -EOPNOTSUPP; attr = data[IFLA_NETKIT_POLICY]; policy = nla_get_u32(attr); - if (nk->pair == NETKIT_DEVICE_PAIR) - err = netkit_check_policy(policy, attr, extack); + err = netkit_check_policy(policy, attr, extack); if (err) return err; WRITE_ONCE(nk->policy, policy); @@ -1113,48 +921,6 @@ static int netkit_change_link(struct net_device *dev, struct nlattr *tb[], return 0; } -static void netkit_check_lease_unregister(struct net_device *dev) -{ - LIST_HEAD(list_kill); - u32 q_idx; - - if (READ_ONCE(dev->reg_state) != NETREG_UNREGISTERING || - !dev->dev.parent) - return; - - netdev_lock_ops(dev); - for (q_idx = 0; q_idx < dev->real_num_rx_queues; q_idx++) { - struct net_device *tmp = dev; - u32 tmp_q_idx = q_idx; - - if (netif_rx_queue_lease_get_owner(&tmp, &tmp_q_idx)) { - if (tmp->netdev_ops != &netkit_netdev_ops) - continue; - /* A single phys device can have multiple queues leased - * to one netkit device. We can only queue that netkit - * device once to the list_kill. Queues of that phys - * device can be leased with different individual netkit - * devices, hence we batch via list_kill. - */ - if (unregister_netdevice_queued(tmp)) - continue; - netkit_del_link(tmp, &list_kill); - } - } - netdev_unlock_ops(dev); - unregister_netdevice_many(&list_kill); -} - -static int netkit_notifier(struct notifier_block *this, - unsigned long event, void *ptr) -{ - struct net_device *dev = netdev_notifier_info_to_dev(ptr); - - if (event == NETDEV_UNREGISTER) - netkit_check_lease_unregister(dev); - return NOTIFY_DONE; -} - static size_t netkit_get_size(const struct net_device *dev) { return nla_total_size(sizeof(u32)) + /* IFLA_NETKIT_POLICY */ @@ -1165,7 +931,6 @@ static size_t netkit_get_size(const struct net_device *dev) nla_total_size(sizeof(u8)) + /* IFLA_NETKIT_PRIMARY */ nla_total_size(sizeof(u16)) + /* IFLA_NETKIT_HEADROOM */ nla_total_size(sizeof(u16)) + /* IFLA_NETKIT_TAILROOM */ - nla_total_size(sizeof(u32)) + /* IFLA_NETKIT_PAIRING */ 0; } @@ -1186,8 +951,6 @@ static int netkit_fill_info(struct sk_buff *skb, const struct net_device *dev) return -EMSGSIZE; if (nla_put_u16(skb, IFLA_NETKIT_TAILROOM, dev->needed_tailroom)) return -EMSGSIZE; - if (nla_put_u32(skb, IFLA_NETKIT_PAIRING, nk->pair)) - return -EMSGSIZE; if (peer) { nk = netkit_priv(peer); @@ -1209,15 +972,13 @@ static const struct nla_policy netkit_policy[IFLA_NETKIT_MAX + 1] = { [IFLA_NETKIT_TAILROOM] = { .type = NLA_U16 }, [IFLA_NETKIT_SCRUB] = NLA_POLICY_MAX(NLA_U32, NETKIT_SCRUB_DEFAULT), [IFLA_NETKIT_PEER_SCRUB] = NLA_POLICY_MAX(NLA_U32, NETKIT_SCRUB_DEFAULT), - [IFLA_NETKIT_PAIRING] = NLA_POLICY_MAX(NLA_U32, NETKIT_DEVICE_SINGLE), [IFLA_NETKIT_PRIMARY] = { .type = NLA_REJECT, .reject_message = "Primary attribute is read-only" }, }; static struct rtnl_link_ops netkit_link_ops = { - .kind = NETKIT_DRV_NAME, + .kind = DRV_NAME, .priv_size = sizeof(struct netkit), - .alloc = netkit_alloc, .setup = netkit_setup, .newlink = netkit_new_link, .dellink = netkit_del_link, @@ -1231,39 +992,26 @@ static struct rtnl_link_ops netkit_link_ops = { .maxtype = IFLA_NETKIT_MAX, }; -static struct notifier_block netkit_netdev_notifier = { - .notifier_call = netkit_notifier, -}; - -static __init int netkit_mod_init(void) +static __init int netkit_init(void) { - int ret; - BUILD_BUG_ON((int)NETKIT_NEXT != (int)TCX_NEXT || (int)NETKIT_PASS != (int)TCX_PASS || (int)NETKIT_DROP != (int)TCX_DROP || (int)NETKIT_REDIRECT != (int)TCX_REDIRECT); - ret = rtnl_link_register(&netkit_link_ops); - if (ret) - return ret; - ret = register_netdevice_notifier(&netkit_netdev_notifier); - if (ret) - rtnl_link_unregister(&netkit_link_ops); - return ret; + return rtnl_link_register(&netkit_link_ops); } -static __exit void netkit_mod_exit(void) +static __exit void netkit_exit(void) { - unregister_netdevice_notifier(&netkit_netdev_notifier); rtnl_link_unregister(&netkit_link_ops); } -module_init(netkit_mod_init); -module_exit(netkit_mod_exit); +module_init(netkit_init); +module_exit(netkit_exit); MODULE_DESCRIPTION("BPF-programmable network device"); MODULE_AUTHOR("Daniel Borkmann "); MODULE_AUTHOR("Nikolay Aleksandrov "); MODULE_LICENSE("GPL"); -MODULE_ALIAS_RTNL_LINK(NETKIT_DRV_NAME); +MODULE_ALIAS_RTNL_LINK(DRV_NAME); diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 4d146c000e21..d99b0fbc1942 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -3400,17 +3400,11 @@ static inline int dev_direct_xmit(struct sk_buff *skb, u16 queue_id) int register_netdevice(struct net_device *dev); void unregister_netdevice_queue(struct net_device *dev, struct list_head *head); void unregister_netdevice_many(struct list_head *head); - static inline void unregister_netdevice(struct net_device *dev) { unregister_netdevice_queue(dev, NULL); } -static inline bool unregister_netdevice_queued(const struct net_device *dev) -{ - return !list_empty(&dev->unreg_list); -} - int netdev_refcnt_read(const struct net_device *dev); void free_netdev(struct net_device *dev); diff --git a/include/net/netdev_queues.h b/include/net/netdev_queues.h index 81dc7cb2360c..b55d3b9cb9c2 100644 --- a/include/net/netdev_queues.h +++ b/include/net/netdev_queues.h @@ -130,11 +130,6 @@ void netdev_stat_queue_sum(struct net_device *netdev, * @ndo_queue_get_dma_dev: Get dma device for zero-copy operations to be used * for this queue. Return NULL on error. * - * @ndo_queue_create: Create a new RX queue which can be leased to another queue. - * Ops on this queue are redirected to the leased queue e.g. - * when opening a memory provider. Return the new queue id on - * success. Return negative error code on failure. - * * Note that @ndo_queue_mem_alloc and @ndo_queue_mem_free may be called while * the interface is closed. @ndo_queue_start and @ndo_queue_stop will only * be called for an interface which is open. @@ -154,12 +149,9 @@ struct netdev_queue_mgmt_ops { int idx); struct device * (*ndo_queue_get_dma_dev)(struct net_device *dev, int idx); - int (*ndo_queue_create)(struct net_device *dev); }; -bool netif_rxq_has_unreadable_mp(struct net_device *dev, unsigned int rxq_idx); -bool netif_rxq_has_mp(struct net_device *dev, unsigned int rxq_idx); -bool netif_rxq_is_leased(struct net_device *dev, unsigned int rxq_idx); +bool netif_rxq_has_unreadable_mp(struct net_device *dev, int idx); /** * DOC: Lockless queue stopping / waking helpers. @@ -348,10 +340,5 @@ static inline unsigned int netif_xmit_timeout_ms(struct netdev_queue *txq) }) struct device *netdev_queue_get_dma_dev(struct net_device *dev, int idx); -bool netdev_can_create_queue(const struct net_device *dev, - struct netlink_ext_ack *extack); -bool netdev_can_lease_queue(const struct net_device *dev, - struct netlink_ext_ack *extack); -bool netdev_queue_busy(struct net_device *dev, int idx, - struct netlink_ext_ack *extack); -#endif /* _LINUX_NET_QUEUES_H */ + +#endif diff --git a/include/net/netdev_rx_queue.h b/include/net/netdev_rx_queue.h index 508d11afaecb..8cdcd138b33f 100644 --- a/include/net/netdev_rx_queue.h +++ b/include/net/netdev_rx_queue.h @@ -28,8 +28,6 @@ struct netdev_rx_queue { #endif struct napi_struct *napi; struct pp_memory_provider_params mp_params; - struct netdev_rx_queue *lease; - netdevice_tracker lease_tracker; } ____cacheline_aligned_in_smp; /* @@ -59,22 +57,5 @@ get_netdev_rx_queue_index(struct netdev_rx_queue *queue) } int netdev_rx_queue_restart(struct net_device *dev, unsigned int rxq); -void netdev_rx_queue_lease(struct netdev_rx_queue *rxq_dst, - struct netdev_rx_queue *rxq_src); -void netdev_rx_queue_unlease(struct netdev_rx_queue *rxq_dst, - struct netdev_rx_queue *rxq_src); -bool netif_rx_queue_lease_get_owner(struct net_device **dev, unsigned int *rxq); -enum netif_lease_dir { - NETIF_VIRT_TO_PHYS, - NETIF_PHYS_TO_VIRT, -}; - -struct netdev_rx_queue * -__netif_get_rx_queue_lease(struct net_device **dev, unsigned int *rxq, - enum netif_lease_dir dir); -struct netdev_rx_queue * -netif_get_rx_queue_lease_locked(struct net_device **dev, unsigned int *rxq); -void netif_put_rx_queue_lease_locked(struct net_device *orig_dev, - struct net_device *dev); -#endif /* _LINUX_NETDEV_RX_QUEUE_H */ +#endif diff --git a/include/net/page_pool/memory_provider.h b/include/net/page_pool/memory_provider.h index b6f811c3416b..ada4f968960a 100644 --- a/include/net/page_pool/memory_provider.h +++ b/include/net/page_pool/memory_provider.h @@ -23,12 +23,12 @@ bool net_mp_niov_set_dma_addr(struct net_iov *niov, dma_addr_t addr); void net_mp_niov_set_page_pool(struct page_pool *pool, struct net_iov *niov); void net_mp_niov_clear_page_pool(struct net_iov *niov); -int net_mp_open_rxq(struct net_device *dev, unsigned int rxq_idx, +int net_mp_open_rxq(struct net_device *dev, unsigned ifq_idx, struct pp_memory_provider_params *p); int __net_mp_open_rxq(struct net_device *dev, unsigned int rxq_idx, const struct pp_memory_provider_params *p, struct netlink_ext_ack *extack); -void net_mp_close_rxq(struct net_device *dev, unsigned int rxq_idx, +void net_mp_close_rxq(struct net_device *dev, unsigned ifq_idx, struct pp_memory_provider_params *old_p); void __net_mp_close_rxq(struct net_device *dev, unsigned int rxq_idx, const struct pp_memory_provider_params *old_p); diff --git a/include/net/xdp_sock_drv.h b/include/net/xdp_sock_drv.h index c07cfb431eac..242e34f771cc 100644 --- a/include/net/xdp_sock_drv.h +++ b/include/net/xdp_sock_drv.h @@ -28,7 +28,7 @@ void xsk_tx_completed(struct xsk_buff_pool *pool, u32 nb_entries); bool xsk_tx_peek_desc(struct xsk_buff_pool *pool, struct xdp_desc *desc); u32 xsk_tx_peek_release_desc_batch(struct xsk_buff_pool *pool, u32 max); void xsk_tx_release(struct xsk_buff_pool *pool); -struct xsk_buff_pool *xsk_get_pool_from_qid(const struct net_device *dev, +struct xsk_buff_pool *xsk_get_pool_from_qid(struct net_device *dev, u16 queue_id); void xsk_set_rx_need_wakeup(struct xsk_buff_pool *pool); void xsk_set_tx_need_wakeup(struct xsk_buff_pool *pool); diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h index bbd565757298..3b491d96e52e 100644 --- a/include/uapi/linux/if_link.h +++ b/include/uapi/linux/if_link.h @@ -1296,11 +1296,6 @@ enum netkit_mode { NETKIT_L3, }; -enum netkit_pairing { - NETKIT_DEVICE_PAIR, - NETKIT_DEVICE_SINGLE, -}; - /* NETKIT_SCRUB_NONE leaves clearing skb->{mark,priority} up to * the BPF program if attached. This also means the latter can * consume the two fields if they were populated earlier. @@ -1325,7 +1320,6 @@ enum { IFLA_NETKIT_PEER_SCRUB, IFLA_NETKIT_HEADROOM, IFLA_NETKIT_TAILROOM, - IFLA_NETKIT_PAIRING, __IFLA_NETKIT_MAX, }; #define IFLA_NETKIT_MAX (__IFLA_NETKIT_MAX - 1) diff --git a/include/uapi/linux/netdev.h b/include/uapi/linux/netdev.h index 7df1056a35fd..e0b579a1df4f 100644 --- a/include/uapi/linux/netdev.h +++ b/include/uapi/linux/netdev.h @@ -160,7 +160,6 @@ enum { NETDEV_A_QUEUE_DMABUF, NETDEV_A_QUEUE_IO_URING, NETDEV_A_QUEUE_XSK, - NETDEV_A_QUEUE_LEASE, __NETDEV_A_QUEUE_MAX, NETDEV_A_QUEUE_MAX = (__NETDEV_A_QUEUE_MAX - 1) @@ -203,15 +202,6 @@ enum { NETDEV_A_QSTATS_MAX = (__NETDEV_A_QSTATS_MAX - 1) }; -enum { - NETDEV_A_LEASE_IFINDEX = 1, - NETDEV_A_LEASE_QUEUE, - NETDEV_A_LEASE_NETNS_ID, - - __NETDEV_A_LEASE_MAX, - NETDEV_A_LEASE_MAX = (__NETDEV_A_LEASE_MAX - 1) -}; - enum { NETDEV_A_DMABUF_IFINDEX = 1, NETDEV_A_DMABUF_QUEUES, @@ -238,7 +228,6 @@ enum { NETDEV_CMD_BIND_RX, NETDEV_CMD_NAPI_SET, NETDEV_CMD_BIND_TX, - NETDEV_CMD_QUEUE_CREATE, __NETDEV_CMD_MAX, NETDEV_CMD_MAX = (__NETDEV_CMD_MAX - 1) diff --git a/net/core/dev.c b/net/core/dev.c index 13a3de63a825..2661b68f5be3 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -1114,13 +1114,6 @@ netdev_get_by_index_lock_ops_compat(struct net *net, int ifindex) return __netdev_put_lock_ops_compat(dev, net); } -struct net_device * -netdev_put_lock(struct net_device *dev, netdevice_tracker *tracker) -{ - netdev_tracker_free(dev, tracker); - return __netdev_put_lock(dev, dev_net(dev)); -} - struct net_device * netdev_xa_find_lock(struct net *net, struct net_device *dev, unsigned long *index) diff --git a/net/core/dev.h b/net/core/dev.h index 9bcb76b325d0..da18536cbd35 100644 --- a/net/core/dev.h +++ b/net/core/dev.h @@ -30,8 +30,6 @@ netdev_napi_by_id_lock(struct net *net, unsigned int napi_id); struct net_device *dev_get_by_napi_id(unsigned int napi_id); struct net_device *__netdev_put_lock(struct net_device *dev, struct net *net); -struct net_device *netdev_put_lock(struct net_device *dev, - netdevice_tracker *tracker); struct net_device * netdev_xa_find_lock(struct net *net, struct net_device *dev, unsigned long *index); diff --git a/net/core/netdev-genl-gen.c b/net/core/netdev-genl-gen.c index 52ba99c019e7..ba673e81716f 100644 --- a/net/core/netdev-genl-gen.c +++ b/net/core/netdev-genl-gen.c @@ -28,12 +28,6 @@ static const struct netlink_range_validation netdev_a_napi_defer_hard_irqs_range }; /* Common nested types */ -const struct nla_policy netdev_lease_nl_policy[NETDEV_A_LEASE_NETNS_ID + 1] = { - [NETDEV_A_LEASE_IFINDEX] = NLA_POLICY_MIN(NLA_U32, 1), - [NETDEV_A_LEASE_QUEUE] = NLA_POLICY_NESTED(netdev_queue_id_nl_policy), - [NETDEV_A_LEASE_NETNS_ID] = { .type = NLA_S32, }, -}; - const struct nla_policy netdev_page_pool_info_nl_policy[NETDEV_A_PAGE_POOL_IFINDEX + 1] = { [NETDEV_A_PAGE_POOL_ID] = NLA_POLICY_FULL_RANGE(NLA_UINT, &netdev_a_page_pool_id_range), [NETDEV_A_PAGE_POOL_IFINDEX] = NLA_POLICY_FULL_RANGE(NLA_U32, &netdev_a_page_pool_ifindex_range), @@ -113,13 +107,6 @@ static const struct nla_policy netdev_bind_tx_nl_policy[NETDEV_A_DMABUF_FD + 1] [NETDEV_A_DMABUF_FD] = { .type = NLA_U32, }, }; -/* NETDEV_CMD_QUEUE_CREATE - do */ -static const struct nla_policy netdev_queue_create_nl_policy[NETDEV_A_QUEUE_LEASE + 1] = { - [NETDEV_A_QUEUE_IFINDEX] = NLA_POLICY_MIN(NLA_U32, 1), - [NETDEV_A_QUEUE_TYPE] = NLA_POLICY_MAX(NLA_U32, 1), - [NETDEV_A_QUEUE_LEASE] = NLA_POLICY_NESTED(netdev_lease_nl_policy), -}; - /* Ops table for netdev */ static const struct genl_split_ops netdev_nl_ops[] = { { @@ -218,13 +205,6 @@ static const struct genl_split_ops netdev_nl_ops[] = { .maxattr = NETDEV_A_DMABUF_FD, .flags = GENL_CMD_CAP_DO, }, - { - .cmd = NETDEV_CMD_QUEUE_CREATE, - .doit = netdev_nl_queue_create_doit, - .policy = netdev_queue_create_nl_policy, - .maxattr = NETDEV_A_QUEUE_LEASE, - .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, - }, }; static const struct genl_multicast_group netdev_nl_mcgrps[] = { diff --git a/net/core/netdev-genl-gen.h b/net/core/netdev-genl-gen.h index d71b435d72c1..cffc08517a41 100644 --- a/net/core/netdev-genl-gen.h +++ b/net/core/netdev-genl-gen.h @@ -14,7 +14,6 @@ #include /* Common nested types */ -extern const struct nla_policy netdev_lease_nl_policy[NETDEV_A_LEASE_NETNS_ID + 1]; extern const struct nla_policy netdev_page_pool_info_nl_policy[NETDEV_A_PAGE_POOL_IFINDEX + 1]; extern const struct nla_policy netdev_queue_id_nl_policy[NETDEV_A_QUEUE_TYPE + 1]; @@ -37,7 +36,6 @@ int netdev_nl_qstats_get_dumpit(struct sk_buff *skb, int netdev_nl_bind_rx_doit(struct sk_buff *skb, struct genl_info *info); int netdev_nl_napi_set_doit(struct sk_buff *skb, struct genl_info *info); int netdev_nl_bind_tx_doit(struct sk_buff *skb, struct genl_info *info); -int netdev_nl_queue_create_doit(struct sk_buff *skb, struct genl_info *info); enum { NETDEV_NLGRP_MGMT, diff --git a/net/core/netdev-genl.c b/net/core/netdev-genl.c index 51c830f88f10..470fabbeacd9 100644 --- a/net/core/netdev-genl.c +++ b/net/core/netdev-genl.c @@ -391,11 +391,8 @@ netdev_nl_queue_fill_one(struct sk_buff *rsp, struct net_device *netdev, u32 q_idx, u32 q_type, const struct genl_info *info) { struct pp_memory_provider_params *params; - struct net_device *orig_netdev = netdev; - struct nlattr *nest_lease, *nest_queue; struct netdev_rx_queue *rxq; struct netdev_queue *txq; - u32 lease_q_idx = q_idx; void *hdr; hdr = genlmsg_iput(rsp, info); @@ -413,37 +410,6 @@ netdev_nl_queue_fill_one(struct sk_buff *rsp, struct net_device *netdev, if (nla_put_napi_id(rsp, rxq->napi)) goto nla_put_failure; - if (netif_rx_queue_lease_get_owner(&netdev, &lease_q_idx)) { - struct net *net, *peer_net; - - nest_lease = nla_nest_start(rsp, NETDEV_A_QUEUE_LEASE); - if (!nest_lease) - goto nla_put_failure; - nest_queue = nla_nest_start(rsp, NETDEV_A_LEASE_QUEUE); - if (!nest_queue) - goto nla_put_failure; - if (nla_put_u32(rsp, NETDEV_A_QUEUE_ID, lease_q_idx)) - goto nla_put_failure; - if (nla_put_u32(rsp, NETDEV_A_QUEUE_TYPE, q_type)) - goto nla_put_failure; - nla_nest_end(rsp, nest_queue); - if (nla_put_u32(rsp, NETDEV_A_LEASE_IFINDEX, - READ_ONCE(netdev->ifindex))) - goto nla_put_failure; - rcu_read_lock(); - peer_net = dev_net_rcu(netdev); - net = dev_net_rcu(orig_netdev); - if (!net_eq(net, peer_net)) { - s32 id = peernet2id_alloc(net, peer_net, GFP_ATOMIC); - - if (nla_put_s32(rsp, NETDEV_A_LEASE_NETNS_ID, id)) - goto nla_put_failure_unlock; - } - rcu_read_unlock(); - nla_nest_end(rsp, nest_lease); - netdev = orig_netdev; - } - params = &rxq->mp_params; if (params->mp_ops && params->mp_ops->nl_fill(params->mp_priv, rsp, rxq)) @@ -471,8 +437,6 @@ netdev_nl_queue_fill_one(struct sk_buff *rsp, struct net_device *netdev, return 0; -nla_put_failure_unlock: - rcu_read_unlock(); nla_put_failure: genlmsg_cancel(rsp, hdr); return -EMSGSIZE; @@ -1156,155 +1120,6 @@ err_genlmsg_free: return err; } -int netdev_nl_queue_create_doit(struct sk_buff *skb, struct genl_info *info) -{ - const int qmaxtype = ARRAY_SIZE(netdev_queue_id_nl_policy) - 1; - const int lmaxtype = ARRAY_SIZE(netdev_lease_nl_policy) - 1; - int err, ifindex, ifindex_lease, queue_id, queue_id_lease; - struct nlattr *qtb[ARRAY_SIZE(netdev_queue_id_nl_policy)]; - struct nlattr *ltb[ARRAY_SIZE(netdev_lease_nl_policy)]; - struct netdev_rx_queue *rxq, *rxq_lease; - struct net_device *dev, *dev_lease; - netdevice_tracker dev_tracker; - struct nlattr *nest; - struct sk_buff *rsp; - void *hdr; - - if (GENL_REQ_ATTR_CHECK(info, NETDEV_A_QUEUE_IFINDEX) || - GENL_REQ_ATTR_CHECK(info, NETDEV_A_QUEUE_TYPE) || - GENL_REQ_ATTR_CHECK(info, NETDEV_A_QUEUE_LEASE)) - return -EINVAL; - if (nla_get_u32(info->attrs[NETDEV_A_QUEUE_TYPE]) != - NETDEV_QUEUE_TYPE_RX) { - NL_SET_BAD_ATTR(info->extack, info->attrs[NETDEV_A_QUEUE_TYPE]); - return -EINVAL; - } - - ifindex = nla_get_u32(info->attrs[NETDEV_A_QUEUE_IFINDEX]); - - nest = info->attrs[NETDEV_A_QUEUE_LEASE]; - err = nla_parse_nested(ltb, lmaxtype, nest, - netdev_lease_nl_policy, info->extack); - if (err < 0) - return err; - if (NL_REQ_ATTR_CHECK(info->extack, nest, ltb, NETDEV_A_LEASE_IFINDEX) || - NL_REQ_ATTR_CHECK(info->extack, nest, ltb, NETDEV_A_LEASE_QUEUE)) - return -EINVAL; - if (ltb[NETDEV_A_LEASE_NETNS_ID]) { - NL_SET_BAD_ATTR(info->extack, ltb[NETDEV_A_LEASE_NETNS_ID]); - return -EINVAL; - } - - ifindex_lease = nla_get_u32(ltb[NETDEV_A_LEASE_IFINDEX]); - - nest = ltb[NETDEV_A_LEASE_QUEUE]; - err = nla_parse_nested(qtb, qmaxtype, nest, - netdev_queue_id_nl_policy, info->extack); - if (err < 0) - return err; - if (NL_REQ_ATTR_CHECK(info->extack, nest, qtb, NETDEV_A_QUEUE_ID) || - NL_REQ_ATTR_CHECK(info->extack, nest, qtb, NETDEV_A_QUEUE_TYPE)) - return -EINVAL; - if (nla_get_u32(qtb[NETDEV_A_QUEUE_TYPE]) != NETDEV_QUEUE_TYPE_RX) { - NL_SET_BAD_ATTR(info->extack, qtb[NETDEV_A_QUEUE_TYPE]); - return -EINVAL; - } - if (ifindex == ifindex_lease) { - NL_SET_ERR_MSG(info->extack, - "Lease ifindex cannot be the same as queue creation ifindex"); - return -EINVAL; - } - - queue_id_lease = nla_get_u32(qtb[NETDEV_A_QUEUE_ID]); - - rsp = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL); - if (!rsp) - return -ENOMEM; - - hdr = genlmsg_iput(rsp, info); - if (!hdr) { - err = -EMSGSIZE; - goto err_genlmsg_free; - } - - /* Locking order is always from the virtual to the physical device - * since this is also the same order when applications open the - * memory provider later on. - */ - dev = netdev_get_by_index_lock(genl_info_net(info), ifindex); - if (!dev) { - err = -ENODEV; - goto err_genlmsg_free; - } - if (!netdev_can_create_queue(dev, info->extack)) { - err = -EINVAL; - goto err_unlock_dev; - } - - dev_lease = netdev_get_by_index(genl_info_net(info), ifindex_lease, - &dev_tracker, GFP_KERNEL); - if (!dev_lease) { - err = -ENODEV; - goto err_unlock_dev; - } - if (!netdev_can_lease_queue(dev_lease, info->extack)) { - netdev_put(dev_lease, &dev_tracker); - err = -EINVAL; - goto err_unlock_dev; - } - - dev_lease = netdev_put_lock(dev_lease, &dev_tracker); - if (!dev_lease) { - err = -ENODEV; - goto err_unlock_dev; - } - if (queue_id_lease >= dev_lease->real_num_rx_queues) { - err = -ERANGE; - NL_SET_BAD_ATTR(info->extack, qtb[NETDEV_A_QUEUE_ID]); - goto err_unlock_dev_lease; - } - if (netdev_queue_busy(dev_lease, queue_id_lease, info->extack)) { - err = -EBUSY; - goto err_unlock_dev_lease; - } - - rxq_lease = __netif_get_rx_queue(dev_lease, queue_id_lease); - rxq = __netif_get_rx_queue(dev, dev->real_num_rx_queues - 1); - - if (rxq->lease && rxq->lease->dev != dev_lease) { - err = -EOPNOTSUPP; - NL_SET_ERR_MSG(info->extack, - "Leasing multiple queues from different devices not supported"); - goto err_unlock_dev_lease; - } - - err = queue_id = dev->queue_mgmt_ops->ndo_queue_create(dev); - if (err < 0) { - NL_SET_ERR_MSG(info->extack, - "Device is unable to create a new queue"); - goto err_unlock_dev_lease; - } - - rxq = __netif_get_rx_queue(dev, queue_id); - netdev_rx_queue_lease(rxq, rxq_lease); - - nla_put_u32(rsp, NETDEV_A_QUEUE_ID, queue_id); - genlmsg_end(rsp, hdr); - - netdev_unlock(dev_lease); - netdev_unlock(dev); - - return genlmsg_reply(rsp, info); - -err_unlock_dev_lease: - netdev_unlock(dev_lease); -err_unlock_dev: - netdev_unlock(dev); -err_genlmsg_free: - nlmsg_free(rsp); - return err; -} - void netdev_nl_sock_priv_init(struct netdev_nl_sock *priv) { INIT_LIST_HEAD(&priv->bindings); diff --git a/net/core/netdev_queues.c b/net/core/netdev_queues.c index 97acf6440829..251f27a8307f 100644 --- a/net/core/netdev_queues.c +++ b/net/core/netdev_queues.c @@ -1,37 +1,22 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include -#include -#include /** * netdev_queue_get_dma_dev() - get dma device for zero-copy operations * @dev: net_device * @idx: queue index * - * Get dma device for zero-copy operations to be used for this queue. If the - * queue is leased to a physical queue, we retrieve the latter's dma device. + * Get dma device for zero-copy operations to be used for this queue. * When such device is not available or valid, the function will return NULL. * * Return: Device or NULL on error */ struct device *netdev_queue_get_dma_dev(struct net_device *dev, int idx) { - const struct netdev_queue_mgmt_ops *queue_ops; + const struct netdev_queue_mgmt_ops *queue_ops = dev->queue_mgmt_ops; struct device *dma_dev; - if (idx < dev->real_num_rx_queues) { - struct netdev_rx_queue *rxq = __netif_get_rx_queue(dev, idx); - - if (rxq->lease) { - rxq = rxq->lease; - dev = rxq->dev; - idx = get_netdev_rx_queue_index(rxq); - } - } - - queue_ops = dev->queue_mgmt_ops; - if (queue_ops && queue_ops->ndo_queue_get_dma_dev) dma_dev = queue_ops->ndo_queue_get_dma_dev(dev, idx); else @@ -40,58 +25,3 @@ struct device *netdev_queue_get_dma_dev(struct net_device *dev, int idx) return dma_dev && dma_dev->dma_mask ? dma_dev : NULL; } -bool netdev_can_create_queue(const struct net_device *dev, - struct netlink_ext_ack *extack) -{ - if (dev->dev.parent) { - NL_SET_ERR_MSG(extack, "Device is not a virtual device"); - return false; - } - if (!dev->queue_mgmt_ops || - !dev->queue_mgmt_ops->ndo_queue_create) { - NL_SET_ERR_MSG(extack, "Device does not support queue creation"); - return false; - } - if (dev->real_num_rx_queues < 1 || - dev->real_num_tx_queues < 1) { - NL_SET_ERR_MSG(extack, "Device must have at least one real queue"); - return false; - } - return true; -} - -bool netdev_can_lease_queue(const struct net_device *dev, - struct netlink_ext_ack *extack) -{ - if (!dev->dev.parent) { - NL_SET_ERR_MSG(extack, "Lease device is a virtual device"); - return false; - } - if (!netif_device_present(dev)) { - NL_SET_ERR_MSG(extack, "Lease device has been removed from the system"); - return false; - } - if (!dev->queue_mgmt_ops) { - NL_SET_ERR_MSG(extack, "Lease device does not support queue management operations"); - return false; - } - return true; -} - -bool netdev_queue_busy(struct net_device *dev, int idx, - struct netlink_ext_ack *extack) -{ - if (netif_rxq_is_leased(dev, idx)) { - NL_SET_ERR_MSG(extack, "Lease device queue is already leased"); - return true; - } - if (xsk_get_pool_from_qid(dev, idx)) { - NL_SET_ERR_MSG(extack, "Lease device queue in use by AF_XDP"); - return true; - } - if (netif_rxq_has_mp(dev, idx)) { - NL_SET_ERR_MSG(extack, "Lease device queue in use by memory provider"); - return true; - } - return false; -} diff --git a/net/core/netdev_rx_queue.c b/net/core/netdev_rx_queue.c index 75c7a68cb90d..c7d9341b7630 100644 --- a/net/core/netdev_rx_queue.c +++ b/net/core/netdev_rx_queue.c @@ -9,120 +9,14 @@ #include "page_pool_priv.h" -void netdev_rx_queue_lease(struct netdev_rx_queue *rxq_dst, - struct netdev_rx_queue *rxq_src) -{ - netdev_assert_locked(rxq_src->dev); - netdev_assert_locked(rxq_dst->dev); - - netdev_hold(rxq_src->dev, &rxq_src->lease_tracker, GFP_KERNEL); - - WRITE_ONCE(rxq_src->lease, rxq_dst); - WRITE_ONCE(rxq_dst->lease, rxq_src); -} - -void netdev_rx_queue_unlease(struct netdev_rx_queue *rxq_dst, - struct netdev_rx_queue *rxq_src) -{ - netdev_assert_locked(rxq_dst->dev); - netdev_assert_locked(rxq_src->dev); - - WRITE_ONCE(rxq_src->lease, NULL); - WRITE_ONCE(rxq_dst->lease, NULL); - - netdev_put(rxq_src->dev, &rxq_src->lease_tracker); -} - -bool netif_rxq_is_leased(struct net_device *dev, unsigned int rxq_idx) -{ - if (rxq_idx < dev->real_num_rx_queues) - return READ_ONCE(__netif_get_rx_queue(dev, rxq_idx)->lease); - return false; -} - -static bool netif_lease_dir_ok(const struct net_device *dev, - enum netif_lease_dir dir) -{ - if (dir == NETIF_VIRT_TO_PHYS && !dev->dev.parent) - return true; - if (dir == NETIF_PHYS_TO_VIRT && dev->dev.parent) - return true; - return false; -} - -struct netdev_rx_queue * -__netif_get_rx_queue_lease(struct net_device **dev, unsigned int *rxq_idx, - enum netif_lease_dir dir) -{ - struct net_device *orig_dev = *dev; - struct netdev_rx_queue *rxq = __netif_get_rx_queue(orig_dev, *rxq_idx); - - if (rxq->lease) { - if (!netif_lease_dir_ok(orig_dev, dir)) - return NULL; - rxq = rxq->lease; - *rxq_idx = get_netdev_rx_queue_index(rxq); - *dev = rxq->dev; - } - return rxq; -} - -struct netdev_rx_queue * -netif_get_rx_queue_lease_locked(struct net_device **dev, unsigned int *rxq_idx) -{ - struct net_device *orig_dev = *dev; - struct netdev_rx_queue *rxq; - - /* Locking order is always from the virtual to the physical device - * see netdev_nl_queue_create_doit(). - */ - netdev_ops_assert_locked(orig_dev); - rxq = __netif_get_rx_queue_lease(dev, rxq_idx, NETIF_VIRT_TO_PHYS); - if (rxq && orig_dev != *dev) - netdev_lock(*dev); - return rxq; -} - -void netif_put_rx_queue_lease_locked(struct net_device *orig_dev, - struct net_device *dev) -{ - if (orig_dev != dev) - netdev_unlock(dev); -} - -bool netif_rx_queue_lease_get_owner(struct net_device **dev, - unsigned int *rxq_idx) -{ - struct net_device *orig_dev = *dev; - struct netdev_rx_queue *rxq; - - /* The physical device needs to be locked. If there is indeed a lease, - * then the virtual device holds a reference on the physical device - * and the lease stays active until the virtual device is torn down. - * When queues get {un,}leased both devices are always locked. - */ - netdev_ops_assert_locked(orig_dev); - rxq = __netif_get_rx_queue_lease(dev, rxq_idx, NETIF_PHYS_TO_VIRT); - if (rxq && orig_dev != *dev) - return true; - return false; -} - /* See also page_pool_is_unreadable() */ -bool netif_rxq_has_unreadable_mp(struct net_device *dev, unsigned int rxq_idx) +bool netif_rxq_has_unreadable_mp(struct net_device *dev, int idx) { - if (rxq_idx < dev->real_num_rx_queues) - return __netif_get_rx_queue(dev, rxq_idx)->mp_params.mp_ops; - return false; -} -EXPORT_SYMBOL(netif_rxq_has_unreadable_mp); + struct netdev_rx_queue *rxq = __netif_get_rx_queue(dev, idx); -bool netif_rxq_has_mp(struct net_device *dev, unsigned int rxq_idx) -{ - if (rxq_idx < dev->real_num_rx_queues) - return __netif_get_rx_queue(dev, rxq_idx)->mp_params.mp_priv; - return false; + return !!rxq->mp_params.mp_ops; } +EXPORT_SYMBOL(netif_rxq_has_unreadable_mp); int netdev_rx_queue_restart(struct net_device *dev, unsigned int rxq_idx) { @@ -206,63 +100,49 @@ int __net_mp_open_rxq(struct net_device *dev, unsigned int rxq_idx, const struct pp_memory_provider_params *p, struct netlink_ext_ack *extack) { - struct net_device *orig_dev = dev; struct netdev_rx_queue *rxq; int ret; if (!netdev_need_ops_lock(dev)) return -EOPNOTSUPP; + if (rxq_idx >= dev->real_num_rx_queues) { NL_SET_ERR_MSG(extack, "rx queue index out of range"); return -ERANGE; } - rxq_idx = array_index_nospec(rxq_idx, dev->real_num_rx_queues); - rxq = netif_get_rx_queue_lease_locked(&dev, &rxq_idx); - if (!rxq) { - NL_SET_ERR_MSG(extack, "rx queue peered to a virtual netdev"); - return -EBUSY; - } - if (!dev->dev.parent) { - NL_SET_ERR_MSG(extack, "rx queue is mapped to a virtual netdev"); - ret = -EBUSY; - goto out; - } + if (dev->cfg->hds_config != ETHTOOL_TCP_DATA_SPLIT_ENABLED) { NL_SET_ERR_MSG(extack, "tcp-data-split is disabled"); - ret = -EINVAL; - goto out; + return -EINVAL; } if (dev->cfg->hds_thresh) { NL_SET_ERR_MSG(extack, "hds-thresh is not zero"); - ret = -EINVAL; - goto out; + return -EINVAL; } if (dev_xdp_prog_count(dev)) { NL_SET_ERR_MSG(extack, "unable to custom memory provider to device with XDP program attached"); - ret = -EEXIST; - goto out; + return -EEXIST; } + + rxq = __netif_get_rx_queue(dev, rxq_idx); if (rxq->mp_params.mp_ops) { NL_SET_ERR_MSG(extack, "designated queue already memory provider bound"); - ret = -EEXIST; - goto out; + return -EEXIST; } #ifdef CONFIG_XDP_SOCKETS if (rxq->pool) { NL_SET_ERR_MSG(extack, "designated queue already in use by AF_XDP"); - ret = -EBUSY; - goto out; + return -EBUSY; } #endif + rxq->mp_params = *p; ret = netdev_rx_queue_restart(dev, rxq_idx); if (ret) { rxq->mp_params.mp_ops = NULL; rxq->mp_params.mp_priv = NULL; } -out: - netif_put_rx_queue_lease_locked(orig_dev, dev); return ret; } @@ -277,43 +157,38 @@ int net_mp_open_rxq(struct net_device *dev, unsigned int rxq_idx, return ret; } -void __net_mp_close_rxq(struct net_device *dev, unsigned int rxq_idx, +void __net_mp_close_rxq(struct net_device *dev, unsigned int ifq_idx, const struct pp_memory_provider_params *old_p) { - struct net_device *orig_dev = dev; struct netdev_rx_queue *rxq; int err; - if (WARN_ON_ONCE(rxq_idx >= dev->real_num_rx_queues)) + if (WARN_ON_ONCE(ifq_idx >= dev->real_num_rx_queues)) return; - rxq = netif_get_rx_queue_lease_locked(&dev, &rxq_idx); - if (WARN_ON_ONCE(!rxq)) - return; + rxq = __netif_get_rx_queue(dev, ifq_idx); /* Callers holding a netdev ref may get here after we already * went thru shutdown via dev_memory_provider_uninstall(). */ if (dev->reg_state > NETREG_REGISTERED && !rxq->mp_params.mp_ops) - goto out; + return; if (WARN_ON_ONCE(rxq->mp_params.mp_ops != old_p->mp_ops || rxq->mp_params.mp_priv != old_p->mp_priv)) - goto out; + return; rxq->mp_params.mp_ops = NULL; rxq->mp_params.mp_priv = NULL; - err = netdev_rx_queue_restart(dev, rxq_idx); + err = netdev_rx_queue_restart(dev, ifq_idx); WARN_ON(err && err != -ENETDOWN); -out: - netif_put_rx_queue_lease_locked(orig_dev, dev); } -void net_mp_close_rxq(struct net_device *dev, unsigned int rxq_idx, +void net_mp_close_rxq(struct net_device *dev, unsigned ifq_idx, struct pp_memory_provider_params *old_p) { netdev_lock(dev); - __net_mp_close_rxq(dev, rxq_idx, old_p); + __net_mp_close_rxq(dev, ifq_idx, old_p); netdev_unlock(dev); } diff --git a/net/ethtool/channels.c b/net/ethtool/channels.c index 797d2a08c515..ca4f80282448 100644 --- a/net/ethtool/channels.c +++ b/net/ethtool/channels.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only -#include +#include #include "netlink.h" #include "common.h" @@ -169,16 +169,14 @@ ethnl_set_channels(struct ethnl_req_info *req_info, struct genl_info *info) if (ret) return ret; - /* ensure channels are not busy at the moment */ + /* Disabling channels, query zero-copy AF_XDP sockets */ from_channel = channels.combined_count + min(channels.rx_count, channels.tx_count); - for (i = from_channel; i < old_total; i++) { - if (netdev_queue_busy(dev, i, NULL)) { - GENL_SET_ERR_MSG(info, - "requested channel counts are too low due to busy queues (AF_XDP or queue leasing)"); + for (i = from_channel; i < old_total; i++) + if (xsk_get_pool_from_qid(dev, i)) { + GENL_SET_ERR_MSG(info, "requested channel counts are too low for existing zerocopy AF_XDP sockets"); return -EINVAL; } - } ret = dev->ethtool_ops->set_channels(dev, &channels); return ret < 0 ? ret : 1; diff --git a/net/ethtool/ioctl.c b/net/ethtool/ioctl.c index 02a3454234d6..9431e305b233 100644 --- a/net/ethtool/ioctl.c +++ b/net/ethtool/ioctl.c @@ -27,13 +27,12 @@ #include #include #include -#include #include #include +#include #include #include -#include - +#include #include "common.h" /* State held across locks and calls for commands which have devlink fallback */ @@ -2283,12 +2282,12 @@ static noinline_for_stack int ethtool_set_channels(struct net_device *dev, if (ret) return ret; - /* Disabling channels, query busy queues (AF_XDP, queue leasing) */ + /* Disabling channels, query zero-copy AF_XDP sockets */ from_channel = channels.combined_count + min(channels.rx_count, channels.tx_count); to_channel = curr.combined_count + max(curr.rx_count, curr.tx_count); for (i = from_channel; i < to_channel; i++) - if (netdev_queue_busy(dev, i, NULL)) + if (xsk_get_pool_from_qid(dev, i)) return -EINVAL; ret = dev->ethtool_ops->set_channels(dev, &channels); diff --git a/net/xdp/xsk.c b/net/xdp/xsk.c index 92f791433725..3b46bc635c43 100644 --- a/net/xdp/xsk.c +++ b/net/xdp/xsk.c @@ -23,8 +23,6 @@ #include #include #include - -#include #include #include #include @@ -105,7 +103,7 @@ bool xsk_uses_need_wakeup(struct xsk_buff_pool *pool) } EXPORT_SYMBOL(xsk_uses_need_wakeup); -struct xsk_buff_pool *xsk_get_pool_from_qid(const struct net_device *dev, +struct xsk_buff_pool *xsk_get_pool_from_qid(struct net_device *dev, u16 queue_id) { if (queue_id < dev->real_num_rx_queues) @@ -119,18 +117,10 @@ EXPORT_SYMBOL(xsk_get_pool_from_qid); void xsk_clear_pool_at_qid(struct net_device *dev, u16 queue_id) { - struct net_device *orig_dev = dev; - unsigned int id = queue_id; - - if (id < dev->real_num_rx_queues) - WARN_ON_ONCE(!netif_get_rx_queue_lease_locked(&dev, &id)); - - if (id < dev->real_num_rx_queues) - dev->_rx[id].pool = NULL; - if (id < dev->real_num_tx_queues) - dev->_tx[id].pool = NULL; - - netif_put_rx_queue_lease_locked(orig_dev, dev); + if (queue_id < dev->num_rx_queues) + dev->_rx[queue_id].pool = NULL; + if (queue_id < dev->num_tx_queues) + dev->_tx[queue_id].pool = NULL; } /* The buffer pool is stored both in the _rx struct and the _tx struct as we do @@ -140,29 +130,17 @@ void xsk_clear_pool_at_qid(struct net_device *dev, u16 queue_id) int xsk_reg_pool_at_qid(struct net_device *dev, struct xsk_buff_pool *pool, u16 queue_id) { - struct net_device *orig_dev = dev; - unsigned int id = queue_id; - int ret = 0; - - if (id >= max(dev->real_num_rx_queues, - dev->real_num_tx_queues)) + if (queue_id >= max_t(unsigned int, + dev->real_num_rx_queues, + dev->real_num_tx_queues)) return -EINVAL; - if (id < dev->real_num_rx_queues) { - if (!netif_get_rx_queue_lease_locked(&dev, &id)) - return -EBUSY; - if (xsk_get_pool_from_qid(dev, id)) { - ret = -EBUSY; - goto out; - } - } - if (id < dev->real_num_rx_queues) - dev->_rx[id].pool = pool; - if (id < dev->real_num_tx_queues) - dev->_tx[id].pool = pool; -out: - netif_put_rx_queue_lease_locked(orig_dev, dev); - return ret; + if (queue_id < dev->real_num_rx_queues) + dev->_rx[queue_id].pool = pool; + if (queue_id < dev->real_num_tx_queues) + dev->_tx[queue_id].pool = pool; + + return 0; } static int __xsk_rcv_zc(struct xdp_sock *xs, struct xdp_buff_xsk *xskb, u32 len, @@ -346,37 +324,14 @@ static bool xsk_is_bound(struct xdp_sock *xs) return false; } -static bool xsk_dev_queue_valid(const struct xdp_sock *xs, - const struct xdp_rxq_info *info) -{ - struct net_device *dev = xs->dev; - u32 queue_index = xs->queue_id; - struct netdev_rx_queue *rxq; - - if (info->dev == dev && - info->queue_index == queue_index) - return true; - - if (queue_index < dev->real_num_rx_queues) { - rxq = READ_ONCE(__netif_get_rx_queue(dev, queue_index)->lease); - if (!rxq) - return false; - - dev = rxq->dev; - queue_index = get_netdev_rx_queue_index(rxq); - - return info->dev == dev && - info->queue_index == queue_index; - } - return false; -} - static int xsk_rcv_check(struct xdp_sock *xs, struct xdp_buff *xdp, u32 len) { if (!xsk_is_bound(xs)) return -ENXIO; - if (!xsk_dev_queue_valid(xs, xdp->rxq)) + + if (xs->dev != xdp->rxq->dev || xs->queue_id != xdp->rxq->queue_index) return -EINVAL; + if (len > xsk_pool_get_rx_frame_size(xs->pool) && !xs->sg) { xs->rx_dropped++; return -ENOSPC; diff --git a/tools/include/uapi/linux/netdev.h b/tools/include/uapi/linux/netdev.h index 7df1056a35fd..e0b579a1df4f 100644 --- a/tools/include/uapi/linux/netdev.h +++ b/tools/include/uapi/linux/netdev.h @@ -160,7 +160,6 @@ enum { NETDEV_A_QUEUE_DMABUF, NETDEV_A_QUEUE_IO_URING, NETDEV_A_QUEUE_XSK, - NETDEV_A_QUEUE_LEASE, __NETDEV_A_QUEUE_MAX, NETDEV_A_QUEUE_MAX = (__NETDEV_A_QUEUE_MAX - 1) @@ -203,15 +202,6 @@ enum { NETDEV_A_QSTATS_MAX = (__NETDEV_A_QSTATS_MAX - 1) }; -enum { - NETDEV_A_LEASE_IFINDEX = 1, - NETDEV_A_LEASE_QUEUE, - NETDEV_A_LEASE_NETNS_ID, - - __NETDEV_A_LEASE_MAX, - NETDEV_A_LEASE_MAX = (__NETDEV_A_LEASE_MAX - 1) -}; - enum { NETDEV_A_DMABUF_IFINDEX = 1, NETDEV_A_DMABUF_QUEUES, @@ -238,7 +228,6 @@ enum { NETDEV_CMD_BIND_RX, NETDEV_CMD_NAPI_SET, NETDEV_CMD_BIND_TX, - NETDEV_CMD_QUEUE_CREATE, __NETDEV_CMD_MAX, NETDEV_CMD_MAX = (__NETDEV_CMD_MAX - 1) diff --git a/tools/testing/selftests/drivers/net/README.rst b/tools/testing/selftests/drivers/net/README.rst index b94e81c2e030..eb838ae94844 100644 --- a/tools/testing/selftests/drivers/net/README.rst +++ b/tools/testing/selftests/drivers/net/README.rst @@ -62,13 +62,6 @@ LOCAL_V4, LOCAL_V6, REMOTE_V4, REMOTE_V6 Local and remote endpoint IP addresses. -LOCAL_PREFIX_V4, LOCAL_PREFIX_V6 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Local IP prefix/subnet which can be used to allocate extra IP addresses (for -network name spaces behind macvlan, veth, netkit devices). DUT must be -reachable using these addresses from the endpoint. - REMOTE_TYPE ~~~~~~~~~~~ diff --git a/tools/testing/selftests/drivers/net/hw/Makefile b/tools/testing/selftests/drivers/net/hw/Makefile index 39ad86d693b3..9c163ba6feee 100644 --- a/tools/testing/selftests/drivers/net/hw/Makefile +++ b/tools/testing/selftests/drivers/net/hw/Makefile @@ -32,8 +32,6 @@ TEST_PROGS = \ irq.py \ loopback.sh \ nic_timestamp.py \ - nk_netns.py \ - nk_qlease.py \ pp_alloc_fail.py \ rss_api.py \ rss_ctx.py \ diff --git a/tools/testing/selftests/drivers/net/hw/lib/py/__init__.py b/tools/testing/selftests/drivers/net/hw/lib/py/__init__.py index 022008249313..d5d247eca6b7 100644 --- a/tools/testing/selftests/drivers/net/hw/lib/py/__init__.py +++ b/tools/testing/selftests/drivers/net/hw/lib/py/__init__.py @@ -3,7 +3,6 @@ """ Driver test environment (hardware-only tests). NetDrvEnv and NetDrvEpEnv are the main environment classes. -NetDrvContEnv extends NetDrvEpEnv with netkit container support. Former is for local host only tests, latter creates / connects to a remote endpoint. See NIPA wiki for more information about running and writing driver tests. @@ -30,7 +29,7 @@ try: from net.lib.py import ksft_eq, ksft_ge, ksft_in, ksft_is, ksft_lt, \ ksft_ne, ksft_not_in, ksft_raises, ksft_true, ksft_gt, ksft_not_none from drivers.net.lib.py import GenerateTraffic, Remote, Iperf3Runner - from drivers.net.lib.py import NetDrvEnv, NetDrvEpEnv, NetDrvContEnv + from drivers.net.lib.py import NetDrvEnv, NetDrvEpEnv __all__ = ["NetNS", "NetNSEnter", "NetdevSimDev", "EthtoolFamily", "NetdevFamily", "NetshaperFamily", @@ -45,8 +44,8 @@ try: "ksft_eq", "ksft_ge", "ksft_in", "ksft_is", "ksft_lt", "ksft_ne", "ksft_not_in", "ksft_raises", "ksft_true", "ksft_gt", "ksft_not_none", "ksft_not_none", - "NetDrvEnv", "NetDrvEpEnv", "NetDrvContEnv", "GenerateTraffic", - "Remote", "Iperf3Runner"] + "NetDrvEnv", "NetDrvEpEnv", "GenerateTraffic", "Remote", + "Iperf3Runner"] except ModuleNotFoundError as e: print("Failed importing `net` library from kernel sources") print(str(e)) diff --git a/tools/testing/selftests/drivers/net/hw/nk_forward.bpf.c b/tools/testing/selftests/drivers/net/hw/nk_forward.bpf.c deleted file mode 100644 index 86ebfc1445b6..000000000000 --- a/tools/testing/selftests/drivers/net/hw/nk_forward.bpf.c +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#include -#include -#include -#include -#include -#include -#include - -#define TC_ACT_OK 0 -#define ETH_P_IPV6 0x86DD - -#define ctx_ptr(field) ((void *)(long)(field)) - -#define v6_p64_equal(a, b) (a.s6_addr32[0] == b.s6_addr32[0] && \ - a.s6_addr32[1] == b.s6_addr32[1]) - -volatile __u32 netkit_ifindex; -volatile __u8 ipv6_prefix[16]; - -SEC("tc/ingress") -int tc_redirect_peer(struct __sk_buff *skb) -{ - void *data_end = ctx_ptr(skb->data_end); - void *data = ctx_ptr(skb->data); - struct in6_addr *peer_addr; - struct ipv6hdr *ip6h; - struct ethhdr *eth; - - peer_addr = (struct in6_addr *)ipv6_prefix; - - if (skb->protocol != bpf_htons(ETH_P_IPV6)) - return TC_ACT_OK; - - eth = data; - if ((void *)(eth + 1) > data_end) - return TC_ACT_OK; - - ip6h = data + sizeof(struct ethhdr); - if ((void *)(ip6h + 1) > data_end) - return TC_ACT_OK; - - if (!v6_p64_equal(ip6h->daddr, (*peer_addr))) - return TC_ACT_OK; - - return bpf_redirect_peer(netkit_ifindex, 0); -} - -char __license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/drivers/net/hw/nk_netns.py b/tools/testing/selftests/drivers/net/hw/nk_netns.py deleted file mode 100755 index afa8638195d8..000000000000 --- a/tools/testing/selftests/drivers/net/hw/nk_netns.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env python3 -# SPDX-License-Identifier: GPL-2.0 - -from lib.py import ksft_run, ksft_exit -from lib.py import NetDrvContEnv -from lib.py import cmd - - -def test_ping(cfg) -> None: - cfg.require_ipver("6") - - cmd(f"ping -c 1 -W5 {cfg.nk_guest_ipv6}", host=cfg.remote) - cmd(f"ping -c 1 -W5 {cfg.remote_addr_v['6']}", ns=cfg.netns) - - -def main() -> None: - with NetDrvContEnv(__file__) as cfg: - ksft_run([test_ping], args=(cfg,)) - ksft_exit() - - -if __name__ == "__main__": - main() diff --git a/tools/testing/selftests/drivers/net/hw/nk_qlease.py b/tools/testing/selftests/drivers/net/hw/nk_qlease.py deleted file mode 100755 index 738a46d2d20c..000000000000 --- a/tools/testing/selftests/drivers/net/hw/nk_qlease.py +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env python3 -# SPDX-License-Identifier: GPL-2.0 - -import re -from os import path -from lib.py import ksft_run, ksft_exit -from lib.py import NetDrvContEnv -from lib.py import bkg, cmd, defer, ethtool, rand_port, wait_port_listen - - -def create_rss_ctx(cfg): - output = ethtool(f"-X {cfg.ifname} context new start {cfg.src_queue} equal 1").stdout - values = re.search(r'New RSS context is (\d+)', output).group(1) - return int(values) - - -def set_flow_rule(cfg): - output = ethtool(f"-N {cfg.ifname} flow-type tcp6 dst-port {cfg.port} action {cfg.src_queue}").stdout - values = re.search(r'ID (\d+)', output).group(1) - return int(values) - - -def set_flow_rule_rss(cfg, rss_ctx_id): - output = ethtool(f"-N {cfg.ifname} flow-type tcp6 dst-port {cfg.port} context {rss_ctx_id}").stdout - values = re.search(r'ID (\d+)', output).group(1) - return int(values) - - -def test_iou_zcrx(cfg) -> None: - cfg.require_ipver('6') - - ethtool(f"-X {cfg.ifname} equal {cfg.src_queue}") - defer(ethtool, f"-X {cfg.ifname} default") - - flow_rule_id = set_flow_rule(cfg) - defer(ethtool, f"-N {cfg.ifname} delete {flow_rule_id}") - - rx_cmd = f"ip netns exec {cfg.netns.name} {cfg.bin_local} -s -p {cfg.port} -i {cfg._nk_guest_ifname} -q {cfg.nk_queue}" - tx_cmd = f"{cfg.bin_remote} -c -h {cfg.nk_guest_ipv6} -p {cfg.port} -l 12840" - with bkg(rx_cmd, exit_wait=True): - wait_port_listen(cfg.port, proto="tcp", ns=cfg.netns) - cmd(tx_cmd, host=cfg.remote) - - -def main() -> None: - with NetDrvContEnv(__file__, lease=True) as cfg: - cfg.bin_local = path.abspath(path.dirname(__file__) + "/../../../drivers/net/hw/iou-zcrx") - cfg.bin_remote = cfg.remote.deploy(cfg.bin_local) - cfg.port = rand_port() - ksft_run([test_iou_zcrx], args=(cfg,)) - ksft_exit() - - -if __name__ == "__main__": - main() diff --git a/tools/testing/selftests/drivers/net/lib/py/__init__.py b/tools/testing/selftests/drivers/net/lib/py/__init__.py index be3a8a936882..8b75faa9af6d 100644 --- a/tools/testing/selftests/drivers/net/lib/py/__init__.py +++ b/tools/testing/selftests/drivers/net/lib/py/__init__.py @@ -3,7 +3,6 @@ """ Driver test environment. NetDrvEnv and NetDrvEpEnv are the main environment classes. -NetDrvContEnv extends NetDrvEpEnv with netkit container support. Former is for local host only tests, latter creates / connects to a remote endpoint. See NIPA wiki for more information about running and writing driver tests. @@ -44,12 +43,12 @@ try: "ksft_ne", "ksft_not_in", "ksft_raises", "ksft_true", "ksft_gt", "ksft_not_none", "ksft_not_none"] - from .env import NetDrvEnv, NetDrvEpEnv, NetDrvContEnv + from .env import NetDrvEnv, NetDrvEpEnv from .load import GenerateTraffic, Iperf3Runner from .remote import Remote - __all__ += ["NetDrvEnv", "NetDrvEpEnv", "NetDrvContEnv", "GenerateTraffic", - "Remote", "Iperf3Runner"] + __all__ += ["NetDrvEnv", "NetDrvEpEnv", "GenerateTraffic", "Remote", + "Iperf3Runner"] except ModuleNotFoundError as e: print("Failed importing `net` library from kernel sources") print(str(e)) diff --git a/tools/testing/selftests/drivers/net/lib/py/env.py b/tools/testing/selftests/drivers/net/lib/py/env.py index 7066d78395c6..41cc248ac848 100644 --- a/tools/testing/selftests/drivers/net/lib/py/env.py +++ b/tools/testing/selftests/drivers/net/lib/py/env.py @@ -1,17 +1,13 @@ # SPDX-License-Identifier: GPL-2.0 -import ipaddress import os -import re import time from pathlib import Path from lib.py import KsftSkipEx, KsftXfailEx from lib.py import ksft_setup, wait_file from lib.py import cmd, ethtool, ip, CmdExitFailure from lib.py import NetNS, NetdevSimDev -from lib.py import NetdevFamily, EthtoolFamily from .remote import Remote -from . import bpftool class NetDrvEnvBase: @@ -293,156 +289,3 @@ class NetDrvEpEnv(NetDrvEnvBase): data.get('stats-block-usecs', 0) / 1000 / 1000 time.sleep(self._stats_settle_time) - - -class NetDrvContEnv(NetDrvEpEnv): - """ - Class for an environment with a netkit pair setup for forwarding traffic - between the physical interface and a network namespace. - """ - - def __init__(self, src_path, lease=False, **kwargs): - super().__init__(src_path, **kwargs) - - self.require_ipver("6") - local_prefix = self.env.get("LOCAL_PREFIX_V6") - if not local_prefix: - raise KsftSkipEx("LOCAL_PREFIX_V6 required") - - self.netdevnl = NetdevFamily() - self.ethnl = EthtoolFamily() - - local_prefix = local_prefix.rstrip("/64").rstrip("::").rstrip(":") - self.ipv6_prefix = f"{local_prefix}::" - self.nk_host_ipv6 = f"{local_prefix}::2:1" - self.nk_guest_ipv6 = f"{local_prefix}::2:2" - - self.netns = None - self._nk_host_ifname = None - self._nk_guest_ifname = None - self._tc_attached = False - self._bpf_prog_pref = None - self._bpf_prog_id = None - self._leased = False - - nk_rxqueues = 1 - if lease: - nk_rxqueues = 2 - ip(f"link add type netkit mode l2 forward peer forward numrxqueues {nk_rxqueues}") - - all_links = ip("-d link show", json=True) - netkit_links = [link for link in all_links - if link.get('linkinfo', {}).get('info_kind') == 'netkit' - and 'UP' not in link.get('flags', [])] - - if len(netkit_links) != 2: - raise KsftSkipEx("Failed to create netkit pair") - - netkit_links.sort(key=lambda x: x['ifindex']) - self._nk_host_ifname = netkit_links[1]['ifname'] - self._nk_guest_ifname = netkit_links[0]['ifname'] - self.nk_host_ifindex = netkit_links[1]['ifindex'] - self.nk_guest_ifindex = netkit_links[0]['ifindex'] - - if lease: - self._lease_queues() - - self._setup_ns() - self._attach_bpf() - - def __del__(self): - if self._tc_attached: - cmd(f"tc filter del dev {self.ifname} ingress pref {self._bpf_prog_pref}") - self._tc_attached = False - - if self._nk_host_ifname: - cmd(f"ip link del dev {self._nk_host_ifname}") - self._nk_host_ifname = None - self._nk_guest_ifname = None - - if self.netns: - del self.netns - self.netns = None - - if self._leased: - self.ethnl.rings_set({'header': {'dev-index': self.ifindex}, - 'tcp-data-split': 'unknown', - 'hds-thresh': self._hds_thresh, - 'rx': self._rx_rings}) - self._leased = False - - super().__del__() - - def _lease_queues(self): - channels = self.ethnl.channels_get({'header': {'dev-index': self.ifindex}}) - channels = channels['combined-count'] - if channels < 2: - raise KsftSkipEx('Test requires NETIF with at least 2 combined channels') - - rings = self.ethnl.rings_get({'header': {'dev-index': self.ifindex}}) - self._rx_rings = rings['rx'] - self._hds_thresh = rings.get('hds-thresh', 0) - self.ethnl.rings_set({'header': {'dev-index': self.ifindex}, - 'tcp-data-split': 'enabled', - 'hds-thresh': 0, - 'rx': 64}) - self.src_queue = channels - 1 - bind_result = self.netdevnl.queue_create( - { - "ifindex": self.nk_guest_ifindex, - "type": "rx", - "lease": { - "ifindex": self.ifindex, - "queue": {"id": self.src_queue, "type": "rx"}, - }, - } - ) - self.nk_queue = bind_result['id'] - self._leased = True - - def _setup_ns(self): - self.netns = NetNS() - ip(f"link set dev {self._nk_guest_ifname} netns {self.netns.name}") - ip(f"link set dev {self._nk_host_ifname} up") - ip(f"-6 addr add fe80::1/64 dev {self._nk_host_ifname} nodad") - ip(f"-6 route add {self.nk_guest_ipv6}/128 via fe80::2 dev {self._nk_host_ifname}") - - ip("link set lo up", ns=self.netns) - ip(f"link set dev {self._nk_guest_ifname} up", ns=self.netns) - ip(f"-6 addr add fe80::2/64 dev {self._nk_guest_ifname}", ns=self.netns) - ip(f"-6 addr add {self.nk_guest_ipv6}/64 dev {self._nk_guest_ifname} nodad", ns=self.netns) - ip(f"-6 route add default via fe80::1 dev {self._nk_guest_ifname}", ns=self.netns) - - def _attach_bpf(self): - bpf_obj = self.test_dir / "nk_forward.bpf.o" - if not bpf_obj.exists(): - raise KsftSkipEx("BPF prog not found") - - cmd(f"tc filter add dev {self.ifname} ingress bpf obj {bpf_obj} sec tc/ingress direct-action") - self._tc_attached = True - - tc_info = cmd(f"tc filter show dev {self.ifname} ingress").stdout - match = re.search(r'pref (\d+).*nk_forward\.bpf.*id (\d+)', tc_info) - if not match: - raise Exception("Failed to get BPF prog ID") - self._bpf_prog_pref = int(match.group(1)) - self._bpf_prog_id = int(match.group(2)) - - prog_info = bpftool(f"prog show id {self._bpf_prog_id}", json=True) - map_ids = prog_info.get("map_ids", []) - - bss_map_id = None - for map_id in map_ids: - map_info = bpftool(f"map show id {map_id}", json=True) - if map_info.get("name").endswith("bss"): - bss_map_id = map_id - - if bss_map_id is None: - raise Exception("Failed to find .bss map") - - ipv6_addr = ipaddress.IPv6Address(self.ipv6_prefix) - ipv6_bytes = ipv6_addr.packed - ifindex_bytes = self.nk_host_ifindex.to_bytes(4, byteorder='little') - value = ipv6_bytes + ifindex_bytes - value_hex = ' '.join(f'{b:02x}' for b in value) - bpftool(f"map update id {bss_map_id} key hex 00 00 00 00 value hex {value_hex}") -- cgit v1.2.3 From 64dd89ae01f2708a508e028c28b7906e4702a9a7 Mon Sep 17 00:00:00 2001 From: Johannes Weiner Date: Mon, 15 Dec 2025 12:57:53 -0500 Subject: mm/block/fs: remove laptop_mode Laptop mode was introduced to save battery, by delaying and consolidating writes and thereby maximize the time rotating hard drives wouldn't have to spin. Luckily, rotating hard drives, with their high spin-up times and power draw, are a thing of the past for battery-powered devices. Reclaim has also since changed to not write single filesystem pages anymore, and regular filesystem writeback is lumpy by design. The juice doesn't appear worth the squeeze anymore. The footprint of the feature is small, but nevertheless it's a complicating factor in mm, block, filesystems. Developers don't think about it, and it likely hasn't been tested with new reclaim and writeback changes in years. Let's sunset it. Keep the sysctl with a deprecation warning around for a few more cycles, but remove all functionality behind it. [akpm@linux-foundation.org: fix Documentation/admin-guide/laptops/index.rst] Link: https://lkml.kernel.org/r/20251216185201.GH905277@cmpxchg.org Signed-off-by: Johannes Weiner Suggested-by: Christoph Hellwig Reviewed-by: Christoph Hellwig Acked-by: Jens Axboe Reviewed-by: Shakeel Butt Acked-by: Michal Hocko Cc: Deepanshu Kartikey Signed-off-by: Andrew Morton --- Documentation/admin-guide/laptops/index.rst | 1 - Documentation/admin-guide/laptops/laptop-mode.rst | 770 ---------------------- Documentation/admin-guide/sysctl/vm.rst | 8 - block/blk-mq.c | 3 - fs/ext4/inode.c | 3 +- fs/sync.c | 2 - fs/xfs/xfs_super.c | 9 - include/linux/backing-dev-defs.h | 3 - include/linux/writeback.h | 4 - include/trace/events/writeback.h | 1 - include/uapi/linux/sysctl.h | 2 +- mm/backing-dev.c | 3 - mm/page-writeback.c | 74 +-- mm/vmscan.c | 30 +- 14 files changed, 25 insertions(+), 888 deletions(-) delete mode 100644 Documentation/admin-guide/laptops/laptop-mode.rst (limited to 'include/uapi/linux') diff --git a/Documentation/admin-guide/laptops/index.rst b/Documentation/admin-guide/laptops/index.rst index 6432c251dc95..c0b911d05c59 100644 --- a/Documentation/admin-guide/laptops/index.rst +++ b/Documentation/admin-guide/laptops/index.rst @@ -10,7 +10,6 @@ Laptop Drivers alienware-wmi asus-laptop disk-shock-protection - laptop-mode lg-laptop samsung-galaxybook sony-laptop diff --git a/Documentation/admin-guide/laptops/laptop-mode.rst b/Documentation/admin-guide/laptops/laptop-mode.rst deleted file mode 100644 index 66eb9cd918b5..000000000000 --- a/Documentation/admin-guide/laptops/laptop-mode.rst +++ /dev/null @@ -1,770 +0,0 @@ -=============================================== -How to conserve battery power using laptop-mode -=============================================== - -Document Author: Bart Samwel (bart@samwel.tk) - -Date created: January 2, 2004 - -Last modified: December 06, 2004 - -Introduction ------------- - -Laptop mode is used to minimize the time that the hard disk needs to be spun up, -to conserve battery power on laptops. It has been reported to cause significant -power savings. - -.. Contents - - * Introduction - * Installation - * Caveats - * The Details - * Tips & Tricks - * Control script - * ACPI integration - * Monitoring tool - - -Installation ------------- - -To use laptop mode, you don't need to set any kernel configuration options -or anything. Simply install all the files included in this document, and -laptop mode will automatically be started when you're on battery. For -your convenience, a tarball containing an installer can be downloaded at: - - http://www.samwel.tk/laptop_mode/laptop_mode/ - -To configure laptop mode, you need to edit the configuration file, which is -located in /etc/default/laptop-mode on Debian-based systems, or in -/etc/sysconfig/laptop-mode on other systems. - -Unfortunately, automatic enabling of laptop mode does not work for -laptops that don't have ACPI. On those laptops, you need to start laptop -mode manually. To start laptop mode, run "laptop_mode start", and to -stop it, run "laptop_mode stop". (Note: The laptop mode tools package now -has experimental support for APM, you might want to try that first.) - - -Caveats -------- - -* The downside of laptop mode is that you have a chance of losing up to 10 - minutes of work. If you cannot afford this, don't use it! The supplied ACPI - scripts automatically turn off laptop mode when the battery almost runs out, - so that you won't lose any data at the end of your battery life. - -* Most desktop hard drives have a very limited lifetime measured in spindown - cycles, typically about 50.000 times (it's usually listed on the spec sheet). - Check your drive's rating, and don't wear down your drive's lifetime if you - don't need to. - -* If you mount some of your ext3 filesystems with the -n option, then - the control script will not be able to remount them correctly. You must set - DO_REMOUNTS=0 in the control script, otherwise it will remount them with the - wrong options -- or it will fail because it cannot write to /etc/mtab. - -* If you have your filesystems listed as type "auto" in fstab, like I did, then - the control script will not recognize them as filesystems that need remounting. - You must list the filesystems with their true type instead. - -* It has been reported that some versions of the mutt mail client use file access - times to determine whether a folder contains new mail. If you use mutt and - experience this, you must disable the noatime remounting by setting the option - DO_REMOUNT_NOATIME to 0 in the configuration file. - - -The Details ------------ - -Laptop mode is controlled by the knob /proc/sys/vm/laptop_mode. This knob is -present for all kernels that have the laptop mode patch, regardless of any -configuration options. When the knob is set, any physical disk I/O (that might -have caused the hard disk to spin up) causes Linux to flush all dirty blocks. The -result of this is that after a disk has spun down, it will not be spun up -anymore to write dirty blocks, because those blocks had already been written -immediately after the most recent read operation. The value of the laptop_mode -knob determines the time between the occurrence of disk I/O and when the flush -is triggered. A sensible value for the knob is 5 seconds. Setting the knob to -0 disables laptop mode. - -To increase the effectiveness of the laptop_mode strategy, the laptop_mode -control script increases dirty_expire_centisecs and dirty_writeback_centisecs in -/proc/sys/vm to about 10 minutes (by default), which means that pages that are -dirtied are not forced to be written to disk as often. The control script also -changes the dirty background ratio, so that background writeback of dirty pages -is not done anymore. Combined with a higher commit value (also 10 minutes) for -ext3 filesystem (also done automatically by the control script), -this results in concentration of disk activity in a small time interval which -occurs only once every 10 minutes, or whenever the disk is forced to spin up by -a cache miss. The disk can then be spun down in the periods of inactivity. - - -Configuration -------------- - -The laptop mode configuration file is located in /etc/default/laptop-mode on -Debian-based systems, or in /etc/sysconfig/laptop-mode on other systems. It -contains the following options: - -MAX_AGE: - -Maximum time, in seconds, of hard drive spindown time that you are -comfortable with. Worst case, it's possible that you could lose this -amount of work if your battery fails while you're in laptop mode. - -MINIMUM_BATTERY_MINUTES: - -Automatically disable laptop mode if the remaining number of minutes of -battery power is less than this value. Default is 10 minutes. - -AC_HD/BATT_HD: - -The idle timeout that should be set on your hard drive when laptop mode -is active (BATT_HD) and when it is not active (AC_HD). The defaults are -20 seconds (value 4) for BATT_HD and 2 hours (value 244) for AC_HD. The -possible values are those listed in the manual page for "hdparm" for the -"-S" option. - -HD: - -The devices for which the spindown timeout should be adjusted by laptop mode. -Default is /dev/hda. If you specify multiple devices, separate them by a space. - -READAHEAD: - -Disk readahead, in 512-byte sectors, while laptop mode is active. A large -readahead can prevent disk accesses for things like executable pages (which are -loaded on demand while the application executes) and sequentially accessed data -(MP3s). - -DO_REMOUNTS: - -The control script automatically remounts any mounted journaled filesystems -with appropriate commit interval options. When this option is set to 0, this -feature is disabled. - -DO_REMOUNT_NOATIME: - -When remounting, should the filesystems be remounted with the noatime option? -Normally, this is set to "1" (enabled), but there may be programs that require -access time recording. - -DIRTY_RATIO: - -The percentage of memory that is allowed to contain "dirty" or unsaved data -before a writeback is forced, while laptop mode is active. Corresponds to -the /proc/sys/vm/dirty_ratio sysctl. - -DIRTY_BACKGROUND_RATIO: - -The percentage of memory that is allowed to contain "dirty" or unsaved data -after a forced writeback is done due to an exceeding of DIRTY_RATIO. Set -this nice and low. This corresponds to the /proc/sys/vm/dirty_background_ratio -sysctl. - -Note that the behaviour of dirty_background_ratio is quite different -when laptop mode is active and when it isn't. When laptop mode is inactive, -dirty_background_ratio is the threshold percentage at which background writeouts -start taking place. When laptop mode is active, however, background writeouts -are disabled, and the dirty_background_ratio only determines how much writeback -is done when dirty_ratio is reached. - -DO_CPU: - -Enable CPU frequency scaling when in laptop mode. (Requires CPUFreq to be setup. -See Documentation/admin-guide/pm/cpufreq.rst for more info. Disabled by default.) - -CPU_MAXFREQ: - -When on battery, what is the maximum CPU speed that the system should use? Legal -values are "slowest" for the slowest speed that your CPU is able to operate at, -or a value listed in /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies. - - -Tips & Tricks -------------- - -* Bartek Kania reports getting up to 50 minutes of extra battery life (on top - of his regular 3 to 3.5 hours) using a spindown time of 5 seconds (BATT_HD=1). - -* You can spin down the disk while playing MP3, by setting disk readahead - to 8MB (READAHEAD=16384). Effectively, the disk will read a complete MP3 at - once, and will then spin down while the MP3 is playing. (Thanks to Bartek - Kania.) - -* Drew Scott Daniels observed: "I don't know why, but when I decrease the number - of colours that my display uses it consumes less battery power. I've seen - this on powerbooks too. I hope that this is a piece of information that - might be useful to the Laptop Mode patch or its users." - -* In syslog.conf, you can prefix entries with a dash `-` to omit syncing the - file after every logging. When you're using laptop-mode and your disk doesn't - spin down, this is a likely culprit. - -* Richard Atterer observed that laptop mode does not work well with noflushd - (http://noflushd.sourceforge.net/), it seems that noflushd prevents laptop-mode - from doing its thing. - -* If you're worried about your data, you might want to consider using a USB - memory stick or something like that as a "working area". (Be aware though - that flash memory can only handle a limited number of writes, and overuse - may wear out your memory stick pretty quickly. Do _not_ use journalling - filesystems on flash memory sticks.) - - -Configuration file for control and ACPI battery scripts -------------------------------------------------------- - -This allows the tunables to be changed for the scripts via an external -configuration file - -It should be installed as /etc/default/laptop-mode on Debian, and as -/etc/sysconfig/laptop-mode on Red Hat, SUSE, Mandrake, and other work-alikes. - -Config file:: - - # Maximum time, in seconds, of hard drive spindown time that you are - # comfortable with. Worst case, it's possible that you could lose this - # amount of work if your battery fails you while in laptop mode. - #MAX_AGE=600 - - # Automatically disable laptop mode when the number of minutes of battery - # that you have left goes below this threshold. - MINIMUM_BATTERY_MINUTES=10 - - # Read-ahead, in 512-byte sectors. You can spin down the disk while playing MP3/OGG - # by setting the disk readahead to 8MB (READAHEAD=16384). Effectively, the disk - # will read a complete MP3 at once, and will then spin down while the MP3/OGG is - # playing. - #READAHEAD=4096 - - # Shall we remount journaled fs. with appropriate commit interval? (1=yes) - #DO_REMOUNTS=1 - - # And shall we add the "noatime" option to that as well? (1=yes) - #DO_REMOUNT_NOATIME=1 - - # Dirty synchronous ratio. At this percentage of dirty pages the process - # which - # calls write() does its own writeback - #DIRTY_RATIO=40 - - # - # Allowed dirty background ratio, in percent. Once DIRTY_RATIO has been - # exceeded, the kernel will wake flusher threads which will then reduce the - # amount of dirty memory to dirty_background_ratio. Set this nice and low, - # so once some writeout has commenced, we do a lot of it. - # - #DIRTY_BACKGROUND_RATIO=5 - - # kernel default dirty buffer age - #DEF_AGE=30 - #DEF_UPDATE=5 - #DEF_DIRTY_BACKGROUND_RATIO=10 - #DEF_DIRTY_RATIO=40 - #DEF_XFS_AGE_BUFFER=15 - #DEF_XFS_SYNC_INTERVAL=30 - #DEF_XFS_BUFD_INTERVAL=1 - - # This must be adjusted manually to the value of HZ in the running kernel - # on 2.4, until the XFS people change their 2.4 external interfaces to work in - # centisecs. This can be automated, but it's a work in progress that still - # needs# some fixes. On 2.6 kernels, XFS uses USER_HZ instead of HZ for - # external interfaces, and that is currently always set to 100. So you don't - # need to change this on 2.6. - #XFS_HZ=100 - - # Should the maximum CPU frequency be adjusted down while on battery? - # Requires CPUFreq to be setup. - # See Documentation/admin-guide/pm/cpufreq.rst for more info - #DO_CPU=0 - - # When on battery what is the maximum CPU speed that the system should - # use? Legal values are "slowest" for the slowest speed that your - # CPU is able to operate at, or a value listed in: - # /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies - # Only applicable if DO_CPU=1. - #CPU_MAXFREQ=slowest - - # Idle timeout for your hard drive (man hdparm for valid values, -S option) - # Default is 2 hours on AC (AC_HD=244) and 20 seconds for battery (BATT_HD=4). - #AC_HD=244 - #BATT_HD=4 - - # The drives for which to adjust the idle timeout. Separate them by a space, - # e.g. HD="/dev/hda /dev/hdb". - #HD="/dev/hda" - - # Set the spindown timeout on a hard drive? - #DO_HD=1 - - -Control script --------------- - -Please note that this control script works for the Linux 2.4 and 2.6 series (thanks -to Kiko Piris). - -Control script:: - - #!/bin/bash - - # start or stop laptop_mode, best run by a power management daemon when - # ac gets connected/disconnected from a laptop - # - # install as /sbin/laptop_mode - # - # Contributors to this script: Kiko Piris - # Bart Samwel - # Micha Feigin - # Andrew Morton - # Herve Eychenne - # Dax Kelson - # - # Original Linux 2.4 version by: Jens Axboe - - ############################################################################# - - # Source config - if [ -f /etc/default/laptop-mode ] ; then - # Debian - . /etc/default/laptop-mode - elif [ -f /etc/sysconfig/laptop-mode ] ; then - # Others - . /etc/sysconfig/laptop-mode - fi - - # Don't raise an error if the config file is incomplete - # set defaults instead: - - # Maximum time, in seconds, of hard drive spindown time that you are - # comfortable with. Worst case, it's possible that you could lose this - # amount of work if your battery fails you while in laptop mode. - MAX_AGE=${MAX_AGE:-'600'} - - # Read-ahead, in kilobytes - READAHEAD=${READAHEAD:-'4096'} - - # Shall we remount journaled fs. with appropriate commit interval? (1=yes) - DO_REMOUNTS=${DO_REMOUNTS:-'1'} - - # And shall we add the "noatime" option to that as well? (1=yes) - DO_REMOUNT_NOATIME=${DO_REMOUNT_NOATIME:-'1'} - - # Shall we adjust the idle timeout on a hard drive? - DO_HD=${DO_HD:-'1'} - - # Adjust idle timeout on which hard drive? - HD="${HD:-'/dev/hda'}" - - # spindown time for HD (hdparm -S values) - AC_HD=${AC_HD:-'244'} - BATT_HD=${BATT_HD:-'4'} - - # Dirty synchronous ratio. At this percentage of dirty pages the process which - # calls write() does its own writeback - DIRTY_RATIO=${DIRTY_RATIO:-'40'} - - # cpu frequency scaling - # See Documentation/admin-guide/pm/cpufreq.rst for more info - DO_CPU=${CPU_MANAGE:-'0'} - CPU_MAXFREQ=${CPU_MAXFREQ:-'slowest'} - - # - # Allowed dirty background ratio, in percent. Once DIRTY_RATIO has been - # exceeded, the kernel will wake flusher threads which will then reduce the - # amount of dirty memory to dirty_background_ratio. Set this nice and low, - # so once some writeout has commenced, we do a lot of it. - # - DIRTY_BACKGROUND_RATIO=${DIRTY_BACKGROUND_RATIO:-'5'} - - # kernel default dirty buffer age - DEF_AGE=${DEF_AGE:-'30'} - DEF_UPDATE=${DEF_UPDATE:-'5'} - DEF_DIRTY_BACKGROUND_RATIO=${DEF_DIRTY_BACKGROUND_RATIO:-'10'} - DEF_DIRTY_RATIO=${DEF_DIRTY_RATIO:-'40'} - DEF_XFS_AGE_BUFFER=${DEF_XFS_AGE_BUFFER:-'15'} - DEF_XFS_SYNC_INTERVAL=${DEF_XFS_SYNC_INTERVAL:-'30'} - DEF_XFS_BUFD_INTERVAL=${DEF_XFS_BUFD_INTERVAL:-'1'} - - # This must be adjusted manually to the value of HZ in the running kernel - # on 2.4, until the XFS people change their 2.4 external interfaces to work in - # centisecs. This can be automated, but it's a work in progress that still needs - # some fixes. On 2.6 kernels, XFS uses USER_HZ instead of HZ for external - # interfaces, and that is currently always set to 100. So you don't need to - # change this on 2.6. - XFS_HZ=${XFS_HZ:-'100'} - - ############################################################################# - - KLEVEL="$(uname -r | - { - IFS='.' read a b c - echo $a.$b - } - )" - case "$KLEVEL" in - "2.4"|"2.6") - ;; - *) - echo "Unhandled kernel version: $KLEVEL ('uname -r' = '$(uname -r)')" >&2 - exit 1 - ;; - esac - - if [ ! -e /proc/sys/vm/laptop_mode ] ; then - echo "Kernel is not patched with laptop_mode patch." >&2 - exit 1 - fi - - if [ ! -w /proc/sys/vm/laptop_mode ] ; then - echo "You do not have enough privileges to enable laptop_mode." >&2 - exit 1 - fi - - # Remove an option (the first parameter) of the form option= from - # a mount options string (the rest of the parameters). - parse_mount_opts () { - OPT="$1" - shift - echo ",$*," | sed \ - -e 's/,'"$OPT"'=[0-9]*,/,/g' \ - -e 's/,,*/,/g' \ - -e 's/^,//' \ - -e 's/,$//' - } - - # Remove an option (the first parameter) without any arguments from - # a mount option string (the rest of the parameters). - parse_nonumber_mount_opts () { - OPT="$1" - shift - echo ",$*," | sed \ - -e 's/,'"$OPT"',/,/g' \ - -e 's/,,*/,/g' \ - -e 's/^,//' \ - -e 's/,$//' - } - - # Find out the state of a yes/no option (e.g. "atime"/"noatime") in - # fstab for a given filesystem, and use this state to replace the - # value of the option in another mount options string. The device - # is the first argument, the option name the second, and the default - # value the third. The remainder is the mount options string. - # - # Example: - # parse_yesno_opts_wfstab /dev/hda1 atime atime defaults,noatime - # - # If fstab contains, say, "rw" for this filesystem, then the result - # will be "defaults,atime". - parse_yesno_opts_wfstab () { - L_DEV="$1" - OPT="$2" - DEF_OPT="$3" - shift 3 - L_OPTS="$*" - PARSEDOPTS1="$(parse_nonumber_mount_opts $OPT $L_OPTS)" - PARSEDOPTS1="$(parse_nonumber_mount_opts no$OPT $PARSEDOPTS1)" - # Watch for a default atime in fstab - FSTAB_OPTS="$(awk '$1 == "'$L_DEV'" { print $4 }' /etc/fstab)" - if echo "$FSTAB_OPTS" | grep "$OPT" > /dev/null ; then - # option specified in fstab: extract the value and use it - if echo "$FSTAB_OPTS" | grep "no$OPT" > /dev/null ; then - echo "$PARSEDOPTS1,no$OPT" - else - # no$OPT not found -- so we must have $OPT. - echo "$PARSEDOPTS1,$OPT" - fi - else - # option not specified in fstab -- choose the default. - echo "$PARSEDOPTS1,$DEF_OPT" - fi - } - - # Find out the state of a numbered option (e.g. "commit=NNN") in - # fstab for a given filesystem, and use this state to replace the - # value of the option in another mount options string. The device - # is the first argument, and the option name the second. The - # remainder is the mount options string in which the replacement - # must be done. - # - # Example: - # parse_mount_opts_wfstab /dev/hda1 commit defaults,commit=7 - # - # If fstab contains, say, "commit=3,rw" for this filesystem, then the - # result will be "rw,commit=3". - parse_mount_opts_wfstab () { - L_DEV="$1" - OPT="$2" - shift 2 - L_OPTS="$*" - PARSEDOPTS1="$(parse_mount_opts $OPT $L_OPTS)" - # Watch for a default commit in fstab - FSTAB_OPTS="$(awk '$1 == "'$L_DEV'" { print $4 }' /etc/fstab)" - if echo "$FSTAB_OPTS" | grep "$OPT=" > /dev/null ; then - # option specified in fstab: extract the value, and use it - echo -n "$PARSEDOPTS1,$OPT=" - echo ",$FSTAB_OPTS," | sed \ - -e 's/.*,'"$OPT"'=//' \ - -e 's/,.*//' - else - # option not specified in fstab: set it to 0 - echo "$PARSEDOPTS1,$OPT=0" - fi - } - - deduce_fstype () { - MP="$1" - # My root filesystem unfortunately has - # type "unknown" in /etc/mtab. If we encounter - # "unknown", we try to get the type from fstab. - cat /etc/fstab | - grep -v '^#' | - while read FSTAB_DEV FSTAB_MP FSTAB_FST FSTAB_OPTS FSTAB_DUMP FSTAB_DUMP ; do - if [ "$FSTAB_MP" = "$MP" ]; then - echo $FSTAB_FST - exit 0 - fi - done - } - - if [ $DO_REMOUNT_NOATIME -eq 1 ] ; then - NOATIME_OPT=",noatime" - fi - - case "$1" in - start) - AGE=$((100*$MAX_AGE)) - XFS_AGE=$(($XFS_HZ*$MAX_AGE)) - echo -n "Starting laptop_mode" - - if [ -d /proc/sys/vm/pagebuf ] ; then - # (For 2.4 and early 2.6.) - # This only needs to be set, not reset -- it is only used when - # laptop mode is enabled. - echo $XFS_AGE > /proc/sys/vm/pagebuf/lm_flush_age - echo $XFS_AGE > /proc/sys/fs/xfs/lm_sync_interval - elif [ -f /proc/sys/fs/xfs/lm_age_buffer ] ; then - # (A couple of early 2.6 laptop mode patches had these.) - # The same goes for these. - echo $XFS_AGE > /proc/sys/fs/xfs/lm_age_buffer - echo $XFS_AGE > /proc/sys/fs/xfs/lm_sync_interval - elif [ -f /proc/sys/fs/xfs/age_buffer ] ; then - # (2.6.6) - # But not for these -- they are also used in normal - # operation. - echo $XFS_AGE > /proc/sys/fs/xfs/age_buffer - echo $XFS_AGE > /proc/sys/fs/xfs/sync_interval - elif [ -f /proc/sys/fs/xfs/age_buffer_centisecs ] ; then - # (2.6.7 upwards) - # And not for these either. These are in centisecs, - # not USER_HZ, so we have to use $AGE, not $XFS_AGE. - echo $AGE > /proc/sys/fs/xfs/age_buffer_centisecs - echo $AGE > /proc/sys/fs/xfs/xfssyncd_centisecs - echo 3000 > /proc/sys/fs/xfs/xfsbufd_centisecs - fi - - case "$KLEVEL" in - "2.4") - echo 1 > /proc/sys/vm/laptop_mode - echo "30 500 0 0 $AGE $AGE 60 20 0" > /proc/sys/vm/bdflush - ;; - "2.6") - echo 5 > /proc/sys/vm/laptop_mode - echo "$AGE" > /proc/sys/vm/dirty_writeback_centisecs - echo "$AGE" > /proc/sys/vm/dirty_expire_centisecs - echo "$DIRTY_RATIO" > /proc/sys/vm/dirty_ratio - echo "$DIRTY_BACKGROUND_RATIO" > /proc/sys/vm/dirty_background_ratio - ;; - esac - if [ $DO_REMOUNTS -eq 1 ]; then - cat /etc/mtab | while read DEV MP FST OPTS DUMP PASS ; do - PARSEDOPTS="$(parse_mount_opts "$OPTS")" - if [ "$FST" = 'unknown' ]; then - FST=$(deduce_fstype $MP) - fi - case "$FST" in - "ext3") - PARSEDOPTS="$(parse_mount_opts commit "$OPTS")" - mount $DEV -t $FST $MP -o remount,$PARSEDOPTS,commit=$MAX_AGE$NOATIME_OPT - ;; - "xfs") - mount $DEV -t $FST $MP -o remount,$OPTS$NOATIME_OPT - ;; - esac - if [ -b $DEV ] ; then - blockdev --setra $(($READAHEAD * 2)) $DEV - fi - done - fi - if [ $DO_HD -eq 1 ] ; then - for THISHD in $HD ; do - /sbin/hdparm -S $BATT_HD $THISHD > /dev/null 2>&1 - /sbin/hdparm -B 1 $THISHD > /dev/null 2>&1 - done - fi - if [ $DO_CPU -eq 1 -a -e /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq ]; then - if [ $CPU_MAXFREQ = 'slowest' ]; then - CPU_MAXFREQ=`cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq` - fi - echo $CPU_MAXFREQ > /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq - fi - echo "." - ;; - stop) - U_AGE=$((100*$DEF_UPDATE)) - B_AGE=$((100*$DEF_AGE)) - echo -n "Stopping laptop_mode" - echo 0 > /proc/sys/vm/laptop_mode - if [ -f /proc/sys/fs/xfs/age_buffer -a ! -f /proc/sys/fs/xfs/lm_age_buffer ] ; then - # These need to be restored, if there are no lm_*. - echo $(($XFS_HZ*$DEF_XFS_AGE_BUFFER)) > /proc/sys/fs/xfs/age_buffer - echo $(($XFS_HZ*$DEF_XFS_SYNC_INTERVAL)) > /proc/sys/fs/xfs/sync_interval - elif [ -f /proc/sys/fs/xfs/age_buffer_centisecs ] ; then - # These need to be restored as well. - echo $((100*$DEF_XFS_AGE_BUFFER)) > /proc/sys/fs/xfs/age_buffer_centisecs - echo $((100*$DEF_XFS_SYNC_INTERVAL)) > /proc/sys/fs/xfs/xfssyncd_centisecs - echo $((100*$DEF_XFS_BUFD_INTERVAL)) > /proc/sys/fs/xfs/xfsbufd_centisecs - fi - case "$KLEVEL" in - "2.4") - echo "30 500 0 0 $U_AGE $B_AGE 60 20 0" > /proc/sys/vm/bdflush - ;; - "2.6") - echo "$U_AGE" > /proc/sys/vm/dirty_writeback_centisecs - echo "$B_AGE" > /proc/sys/vm/dirty_expire_centisecs - echo "$DEF_DIRTY_RATIO" > /proc/sys/vm/dirty_ratio - echo "$DEF_DIRTY_BACKGROUND_RATIO" > /proc/sys/vm/dirty_background_ratio - ;; - esac - if [ $DO_REMOUNTS -eq 1 ] ; then - cat /etc/mtab | while read DEV MP FST OPTS DUMP PASS ; do - # Reset commit and atime options to defaults. - if [ "$FST" = 'unknown' ]; then - FST=$(deduce_fstype $MP) - fi - case "$FST" in - "ext3") - PARSEDOPTS="$(parse_mount_opts_wfstab $DEV commit $OPTS)" - PARSEDOPTS="$(parse_yesno_opts_wfstab $DEV atime atime $PARSEDOPTS)" - mount $DEV -t $FST $MP -o remount,$PARSEDOPTS - ;; - "xfs") - PARSEDOPTS="$(parse_yesno_opts_wfstab $DEV atime atime $OPTS)" - mount $DEV -t $FST $MP -o remount,$PARSEDOPTS - ;; - esac - if [ -b $DEV ] ; then - blockdev --setra 256 $DEV - fi - done - fi - if [ $DO_HD -eq 1 ] ; then - for THISHD in $HD ; do - /sbin/hdparm -S $AC_HD $THISHD > /dev/null 2>&1 - /sbin/hdparm -B 255 $THISHD > /dev/null 2>&1 - done - fi - if [ $DO_CPU -eq 1 -a -e /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq ]; then - echo `cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq` > /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq - fi - echo "." - ;; - *) - echo "Usage: $0 {start|stop}" 2>&1 - exit 1 - ;; - - esac - - exit 0 - - -ACPI integration ----------------- - -Dax Kelson submitted this so that the ACPI acpid daemon will -kick off the laptop_mode script and run hdparm. The part that -automatically disables laptop mode when the battery is low was -written by Jan Topinski. - -/etc/acpi/events/ac_adapter:: - - event=ac_adapter - action=/etc/acpi/actions/ac.sh %e - -/etc/acpi/events/battery:: - - event=battery.* - action=/etc/acpi/actions/battery.sh %e - -/etc/acpi/actions/ac.sh:: - - #!/bin/bash - - # ac on/offline event handler - - status=`awk '/^state: / { print $2 }' /proc/acpi/ac_adapter/$2/state` - - case $status in - "on-line") - /sbin/laptop_mode stop - exit 0 - ;; - "off-line") - /sbin/laptop_mode start - exit 0 - ;; - esac - - -/etc/acpi/actions/battery.sh:: - - #! /bin/bash - - # Automatically disable laptop mode when the battery almost runs out. - - BATT_INFO=/proc/acpi/battery/$2/state - - if [[ -f /proc/sys/vm/laptop_mode ]] - then - LM=`cat /proc/sys/vm/laptop_mode` - if [[ $LM -gt 0 ]] - then - if [[ -f $BATT_INFO ]] - then - # Source the config file only now that we know we need - if [ -f /etc/default/laptop-mode ] ; then - # Debian - . /etc/default/laptop-mode - elif [ -f /etc/sysconfig/laptop-mode ] ; then - # Others - . /etc/sysconfig/laptop-mode - fi - MINIMUM_BATTERY_MINUTES=${MINIMUM_BATTERY_MINUTES:-'10'} - - ACTION="`cat $BATT_INFO | grep charging | cut -c 26-`" - if [[ ACTION -eq "discharging" ]] - then - PRESENT_RATE=`cat $BATT_INFO | grep "present rate:" | sed "s/.* \([0-9][0-9]* \).*/\1/" ` - REMAINING=`cat $BATT_INFO | grep "remaining capacity:" | sed "s/.* \([0-9][0-9]* \).*/\1/" ` - fi - if (($REMAINING * 60 / $PRESENT_RATE < $MINIMUM_BATTERY_MINUTES)) - then - /sbin/laptop_mode stop - fi - else - logger -p daemon.warning "You are using laptop mode and your battery interface $BATT_INFO is missing. This may lead to loss of data when the battery runs out. Check kernel ACPI support and /proc/acpi/battery folder, and edit /etc/acpi/battery.sh to set BATT_INFO to the correct path." - fi - fi - fi - - -Monitoring tool ---------------- - -Bartek Kania submitted this, it can be used to measure how much time your disk -spends spun up/down. See tools/laptop/dslm/dslm.c diff --git a/Documentation/admin-guide/sysctl/vm.rst b/Documentation/admin-guide/sysctl/vm.rst index 245bf6394935..ca6ebeb5171c 100644 --- a/Documentation/admin-guide/sysctl/vm.rst +++ b/Documentation/admin-guide/sysctl/vm.rst @@ -41,7 +41,6 @@ Currently, these files are in /proc/sys/vm: - extfrag_threshold - highmem_is_dirtyable - hugetlb_shm_group -- laptop_mode - legacy_va_layout - lowmem_reserve_ratio - max_map_count @@ -363,13 +362,6 @@ hugetlb_shm_group contains group id that is allowed to create SysV shared memory segment using hugetlb page. -laptop_mode -=========== - -laptop_mode is a knob that controls "laptop mode". All the things that are -controlled by this knob are discussed in Documentation/admin-guide/laptops/laptop-mode.rst. - - legacy_va_layout ================ diff --git a/block/blk-mq.c b/block/blk-mq.c index a29d8ac9d3e3..4bae7c4c664e 100644 --- a/block/blk-mq.c +++ b/block/blk-mq.c @@ -811,9 +811,6 @@ void blk_mq_free_request(struct request *rq) blk_mq_finish_request(rq); - if (unlikely(laptop_mode && !blk_rq_is_passthrough(rq))) - laptop_io_completion(q->disk->bdi); - rq_qos_done(q, rq); WRITE_ONCE(rq->state, MQ_RQ_IDLE); diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c index 0c466ccbed69..15eb463d5a9b 100644 --- a/fs/ext4/inode.c +++ b/fs/ext4/inode.c @@ -3305,8 +3305,7 @@ int ext4_alloc_da_blocks(struct inode *inode) /* * We do something simple for now. The filemap_flush() will * also start triggering a write of the data blocks, which is - * not strictly speaking necessary (and for users of - * laptop_mode, not even desirable). However, to do otherwise + * not strictly speaking necessary. However, to do otherwise * would require replicating code paths in: * * ext4_writepages() -> diff --git a/fs/sync.c b/fs/sync.c index 431fc5f5be06..6330150792f6 100644 --- a/fs/sync.c +++ b/fs/sync.c @@ -104,8 +104,6 @@ void ksys_sync(void) iterate_supers(sync_fs_one_sb, &wait); sync_bdevs(false); sync_bdevs(true); - if (unlikely(laptop_mode)) - laptop_sync_completion(); } SYSCALL_DEFINE0(sync) diff --git a/fs/xfs/xfs_super.c b/fs/xfs/xfs_super.c index bc71aa9dcee8..a2014fb1bc66 100644 --- a/fs/xfs/xfs_super.c +++ b/fs/xfs/xfs_super.c @@ -845,15 +845,6 @@ xfs_fs_sync_fs( if (error) return error; - if (laptop_mode) { - /* - * The disk must be active because we're syncing. - * We schedule log work now (now that the disk is - * active) instead of later (when it might not be). - */ - flush_delayed_work(&mp->m_log->l_work); - } - /* * If we are called with page faults frozen out, it means we are about * to freeze the transaction subsystem. Take the opportunity to shut diff --git a/include/linux/backing-dev-defs.h b/include/linux/backing-dev-defs.h index 0217c1073735..c88fd4d37d1f 100644 --- a/include/linux/backing-dev-defs.h +++ b/include/linux/backing-dev-defs.h @@ -46,7 +46,6 @@ enum wb_reason { WB_REASON_VMSCAN, WB_REASON_SYNC, WB_REASON_PERIODIC, - WB_REASON_LAPTOP_TIMER, WB_REASON_FS_FREE_SPACE, /* * There is no bdi forker thread any more and works are done @@ -204,8 +203,6 @@ struct backing_dev_info { char dev_name[64]; struct device *owner; - struct timer_list laptop_mode_wb_timer; - #ifdef CONFIG_DEBUG_FS struct dentry *debug_dir; #endif diff --git a/include/linux/writeback.h b/include/linux/writeback.h index f48e8ccffe81..e530112c4b3a 100644 --- a/include/linux/writeback.h +++ b/include/linux/writeback.h @@ -328,9 +328,6 @@ struct dirty_throttle_control { bool dirty_exceeded; }; -void laptop_io_completion(struct backing_dev_info *info); -void laptop_sync_completion(void); -void laptop_mode_timer_fn(struct timer_list *t); bool node_dirty_ok(struct pglist_data *pgdat); int wb_domain_init(struct wb_domain *dom, gfp_t gfp); #ifdef CONFIG_CGROUP_WRITEBACK @@ -342,7 +339,6 @@ extern struct wb_domain global_wb_domain; /* These are exported to sysctl. */ extern unsigned int dirty_writeback_interval; extern unsigned int dirty_expire_interval; -extern int laptop_mode; void global_dirty_limits(unsigned long *pbackground, unsigned long *pdirty); unsigned long wb_calc_thresh(struct bdi_writeback *wb, unsigned long thresh); diff --git a/include/trace/events/writeback.h b/include/trace/events/writeback.h index 311a341e6fe4..b6f94e97788a 100644 --- a/include/trace/events/writeback.h +++ b/include/trace/events/writeback.h @@ -42,7 +42,6 @@ EM( WB_REASON_VMSCAN, "vmscan") \ EM( WB_REASON_SYNC, "sync") \ EM( WB_REASON_PERIODIC, "periodic") \ - EM( WB_REASON_LAPTOP_TIMER, "laptop_timer") \ EM( WB_REASON_FS_FREE_SPACE, "fs_free_space") \ EM( WB_REASON_FORKER_THREAD, "forker_thread") \ EMe(WB_REASON_FOREIGN_FLUSH, "foreign_flush") diff --git a/include/uapi/linux/sysctl.h b/include/uapi/linux/sysctl.h index 63d1464cb71c..6ea9ea8413fa 100644 --- a/include/uapi/linux/sysctl.h +++ b/include/uapi/linux/sysctl.h @@ -183,7 +183,7 @@ enum VM_LOWMEM_RESERVE_RATIO=20,/* reservation ratio for lower memory zones */ VM_MIN_FREE_KBYTES=21, /* Minimum free kilobytes to maintain */ VM_MAX_MAP_COUNT=22, /* int: Maximum number of mmaps/address-space */ - VM_LAPTOP_MODE=23, /* vm laptop mode */ + VM_BLOCK_DUMP=24, /* block dump mode */ VM_HUGETLB_GROUP=25, /* permitted hugetlb group */ VM_VFS_CACHE_PRESSURE=26, /* dcache/icache reclaim pressure */ diff --git a/mm/backing-dev.c b/mm/backing-dev.c index c5740c6d37a2..a0e26d1b717f 100644 --- a/mm/backing-dev.c +++ b/mm/backing-dev.c @@ -1034,7 +1034,6 @@ struct backing_dev_info *bdi_alloc(int node_id) bdi->capabilities = BDI_CAP_WRITEBACK; bdi->ra_pages = VM_READAHEAD_PAGES; bdi->io_pages = VM_READAHEAD_PAGES; - timer_setup(&bdi->laptop_mode_wb_timer, laptop_mode_timer_fn, 0); return bdi; } EXPORT_SYMBOL(bdi_alloc); @@ -1156,8 +1155,6 @@ static void bdi_remove_from_list(struct backing_dev_info *bdi) void bdi_unregister(struct backing_dev_info *bdi) { - timer_delete_sync(&bdi->laptop_mode_wb_timer); - /* make sure nobody finds us on the bdi_list anymore */ bdi_remove_from_list(bdi); wb_shutdown(&bdi->wb); diff --git a/mm/page-writeback.c b/mm/page-writeback.c index ccdeb0e84d39..601a5e048d12 100644 --- a/mm/page-writeback.c +++ b/mm/page-writeback.c @@ -109,14 +109,6 @@ EXPORT_SYMBOL_GPL(dirty_writeback_interval); */ unsigned int dirty_expire_interval = 30 * 100; /* centiseconds */ -/* - * Flag that puts the machine in "laptop mode". Doubles as a timeout in jiffies: - * a full sync is triggered after this time elapses without any disk activity. - */ -int laptop_mode; - -EXPORT_SYMBOL(laptop_mode); - /* End of sysctl-exported parameters */ struct wb_domain global_wb_domain; @@ -1843,17 +1835,7 @@ static int balance_dirty_pages(struct bdi_writeback *wb, balance_domain_limits(mdtc, strictlimit); } - /* - * In laptop mode, we wait until hitting the higher threshold - * before starting background writeout, and then write out all - * the way down to the lower threshold. So slow writers cause - * minimal disk activity. - * - * In normal mode, we start background writeout at the lower - * background_thresh, to keep the amount of dirty memory low. - */ - if (!laptop_mode && nr_dirty > gdtc->bg_thresh && - !writeback_in_progress(wb)) + if (nr_dirty > gdtc->bg_thresh && !writeback_in_progress(wb)) wb_start_background_writeback(wb); /* @@ -1876,10 +1858,6 @@ free_running: break; } - /* Start writeback even when in laptop mode */ - if (unlikely(!writeback_in_progress(wb))) - wb_start_background_writeback(wb); - mem_cgroup_flush_foreign(wb); /* @@ -2198,41 +2176,6 @@ static int dirty_writeback_centisecs_handler(const struct ctl_table *table, int } #endif -void laptop_mode_timer_fn(struct timer_list *t) -{ - struct backing_dev_info *backing_dev_info = - timer_container_of(backing_dev_info, t, laptop_mode_wb_timer); - - wakeup_flusher_threads_bdi(backing_dev_info, WB_REASON_LAPTOP_TIMER); -} - -/* - * We've spun up the disk and we're in laptop mode: schedule writeback - * of all dirty data a few seconds from now. If the flush is already scheduled - * then push it back - the user is still using the disk. - */ -void laptop_io_completion(struct backing_dev_info *info) -{ - mod_timer(&info->laptop_mode_wb_timer, jiffies + laptop_mode); -} - -/* - * We're in laptop mode and we've just synced. The sync's writes will have - * caused another writeback to be scheduled by laptop_io_completion. - * Nothing needs to be written back anymore, so we unschedule the writeback. - */ -void laptop_sync_completion(void) -{ - struct backing_dev_info *bdi; - - rcu_read_lock(); - - list_for_each_entry_rcu(bdi, &bdi_list, bdi_list) - timer_delete(&bdi->laptop_mode_wb_timer); - - rcu_read_unlock(); -} - /* * If ratelimit_pages is too high then we can get into dirty-data overload * if a large number of processes all perform writes at the same time. @@ -2263,6 +2206,19 @@ static int page_writeback_cpu_online(unsigned int cpu) #ifdef CONFIG_SYSCTL +static int laptop_mode; +static int laptop_mode_handler(const struct ctl_table *table, int write, + void *buffer, size_t *lenp, loff_t *ppos) +{ + int ret = proc_dointvec_jiffies(table, write, buffer, lenp, ppos); + + if (!ret && write) + pr_warn("%s: vm.laptop_mode is deprecated. Ignoring setting.\n", + current->comm); + + return ret; +} + /* this is needed for the proc_doulongvec_minmax of vm_dirty_bytes */ static const unsigned long dirty_bytes_min = 2 * PAGE_SIZE; @@ -2332,7 +2288,7 @@ static const struct ctl_table vm_page_writeback_sysctls[] = { .data = &laptop_mode, .maxlen = sizeof(laptop_mode), .mode = 0644, - .proc_handler = proc_dointvec_jiffies, + .proc_handler = laptop_mode_handler, }, }; #endif diff --git a/mm/vmscan.c b/mm/vmscan.c index 1c87945fa761..fc5691afb998 100644 --- a/mm/vmscan.c +++ b/mm/vmscan.c @@ -104,13 +104,13 @@ struct scan_control { unsigned int force_deactivate:1; unsigned int skipped_deactivate:1; - /* Writepage batching in laptop mode; RECLAIM_WRITE */ + /* zone_reclaim_mode, boost reclaim */ unsigned int may_writepage:1; - /* Can mapped folios be reclaimed? */ + /* zone_reclaim_mode */ unsigned int may_unmap:1; - /* Can folios be swapped as part of reclaim? */ + /* zome_reclaim_mode, boost reclaim, cgroup restrictions */ unsigned int may_swap:1; /* Not allow cache_trim_mode to be turned on as part of reclaim? */ @@ -6365,13 +6365,6 @@ retry: if (sc->compaction_ready) break; - - /* - * If we're getting trouble reclaiming, start doing - * writepage even in laptop mode. - */ - if (sc->priority < DEF_PRIORITY - 2) - sc->may_writepage = 1; } while (--sc->priority >= 0); last_pgdat = NULL; @@ -6580,7 +6573,7 @@ unsigned long try_to_free_pages(struct zonelist *zonelist, int order, .order = order, .nodemask = nodemask, .priority = DEF_PRIORITY, - .may_writepage = !laptop_mode, + .may_writepage = 1, .may_unmap = 1, .may_swap = 1, }; @@ -6624,7 +6617,7 @@ unsigned long mem_cgroup_shrink_node(struct mem_cgroup *memcg, struct scan_control sc = { .nr_to_reclaim = SWAP_CLUSTER_MAX, .target_mem_cgroup = memcg, - .may_writepage = !laptop_mode, + .may_writepage = 1, .may_unmap = 1, .reclaim_idx = MAX_NR_ZONES - 1, .may_swap = !noswap, @@ -6670,7 +6663,7 @@ unsigned long try_to_free_mem_cgroup_pages(struct mem_cgroup *memcg, .reclaim_idx = MAX_NR_ZONES - 1, .target_mem_cgroup = memcg, .priority = DEF_PRIORITY, - .may_writepage = !laptop_mode, + .may_writepage = 1, .may_unmap = 1, .may_swap = !!(reclaim_options & MEMCG_RECLAIM_MAY_SWAP), .proactive = !!(reclaim_options & MEMCG_RECLAIM_PROACTIVE), @@ -7051,7 +7044,7 @@ restart: * from reclaim context. If no pages are reclaimed, the * reclaim will be aborted. */ - sc.may_writepage = !laptop_mode && !nr_boost_reclaim; + sc.may_writepage = !nr_boost_reclaim; sc.may_swap = !nr_boost_reclaim; /* @@ -7061,13 +7054,6 @@ restart: */ kswapd_age_node(pgdat, &sc); - /* - * If we're getting trouble reclaiming, start doing writepage - * even in laptop mode. - */ - if (sc.priority < DEF_PRIORITY - 2) - sc.may_writepage = 1; - /* Call soft limit reclaim before calling shrink_node. */ sc.nr_scanned = 0; nr_soft_scanned = 0; @@ -7799,7 +7785,7 @@ int user_proactive_reclaim(char *buf, .reclaim_idx = gfp_zone(gfp_mask), .proactive_swappiness = swappiness == -1 ? NULL : &swappiness, .priority = DEF_PRIORITY, - .may_writepage = !laptop_mode, + .may_writepage = 1, .nr_to_reclaim = max(batch_size, SWAP_CLUSTER_MAX), .may_unmap = 1, .may_swap = 1, -- cgit v1.2.3 From fa05705107a40131a8335ad37817153709261738 Mon Sep 17 00:00:00 2001 From: Detlev Casanova Date: Fri, 9 Jan 2026 11:15:18 -0500 Subject: media: v4l2-ctrls: Add hevc_ext_sps_[ls]t_rps controls The vdpu381 decoder found on newer Rockchip SoC need the information from the long term and short term ref pic sets from the SPS. So far, it wasn't included in the v4l2 API, so add it with new dynamic sized controls. Each element of the hevc_ext_sps_lt_rps array contains the long term ref pic set at that index. Each element of the hevc_ext_sps_st_rps contains the short term ref pic set at that index, as the raw data. It is the role of the drivers to calculate the reference sets values. Reviewed-by: Nicolas Dufresne Signed-off-by: Detlev Casanova Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- drivers/media/v4l2-core/v4l2-ctrls-core.c | 28 ++++++++++++++ drivers/media/v4l2-core/v4l2-ctrls-defs.c | 10 +++++ include/uapi/linux/v4l2-controls.h | 61 +++++++++++++++++++++++++++++++ include/uapi/linux/videodev2.h | 2 + 4 files changed, 101 insertions(+) (limited to 'include/uapi/linux') diff --git a/drivers/media/v4l2-core/v4l2-ctrls-core.c b/drivers/media/v4l2-core/v4l2-ctrls-core.c index 1d4b5859f0e2..79a157975f70 100644 --- a/drivers/media/v4l2-core/v4l2-ctrls-core.c +++ b/drivers/media/v4l2-core/v4l2-ctrls-core.c @@ -424,6 +424,12 @@ void v4l2_ctrl_type_op_log(const struct v4l2_ctrl *ctrl) case V4L2_CTRL_TYPE_HEVC_SLICE_PARAMS: pr_cont("HEVC_SLICE_PARAMS"); break; + case V4L2_CTRL_TYPE_HEVC_EXT_SPS_ST_RPS: + pr_cont("HEVC_EXT_SPS_ST_RPS"); + break; + case V4L2_CTRL_TYPE_HEVC_EXT_SPS_LT_RPS: + pr_cont("HEVC_EXT_SPS_LT_RPS"); + break; case V4L2_CTRL_TYPE_HEVC_SCALING_MATRIX: pr_cont("HEVC_SCALING_MATRIX"); break; @@ -961,6 +967,8 @@ static int std_validate_compound(const struct v4l2_ctrl *ctrl, u32 idx, struct v4l2_ctrl_h264_pred_weights *p_h264_pred_weights; struct v4l2_ctrl_h264_slice_params *p_h264_slice_params; struct v4l2_ctrl_h264_decode_params *p_h264_dec_params; + struct v4l2_ctrl_hevc_ext_sps_lt_rps *p_hevc_lt_rps; + struct v4l2_ctrl_hevc_ext_sps_st_rps *p_hevc_st_rps; struct v4l2_ctrl_hevc_sps *p_hevc_sps; struct v4l2_ctrl_hevc_pps *p_hevc_pps; struct v4l2_ctrl_hdr10_mastering_display *p_hdr10_mastering; @@ -1254,6 +1262,20 @@ static int std_validate_compound(const struct v4l2_ctrl *ctrl, u32 idx, case V4L2_CTRL_TYPE_HEVC_SLICE_PARAMS: break; + case V4L2_CTRL_TYPE_HEVC_EXT_SPS_ST_RPS: + p_hevc_st_rps = p; + + if (p_hevc_st_rps->flags & ~V4L2_HEVC_EXT_SPS_ST_RPS_FLAG_INTER_REF_PIC_SET_PRED) + return -EINVAL; + break; + + case V4L2_CTRL_TYPE_HEVC_EXT_SPS_LT_RPS: + p_hevc_lt_rps = p; + + if (p_hevc_lt_rps->flags & ~V4L2_HEVC_EXT_SPS_LT_RPS_FLAG_USED_LT) + return -EINVAL; + break; + case V4L2_CTRL_TYPE_HDR10_CLL_INFO: break; @@ -2006,6 +2028,12 @@ static struct v4l2_ctrl *v4l2_ctrl_new(struct v4l2_ctrl_handler *hdl, case V4L2_CTRL_TYPE_HEVC_SLICE_PARAMS: elem_size = sizeof(struct v4l2_ctrl_hevc_slice_params); break; + case V4L2_CTRL_TYPE_HEVC_EXT_SPS_ST_RPS: + elem_size = sizeof(struct v4l2_ctrl_hevc_ext_sps_st_rps); + break; + case V4L2_CTRL_TYPE_HEVC_EXT_SPS_LT_RPS: + elem_size = sizeof(struct v4l2_ctrl_hevc_ext_sps_lt_rps); + break; case V4L2_CTRL_TYPE_HEVC_SCALING_MATRIX: elem_size = sizeof(struct v4l2_ctrl_hevc_scaling_matrix); break; diff --git a/drivers/media/v4l2-core/v4l2-ctrls-defs.c b/drivers/media/v4l2-core/v4l2-ctrls-defs.c index 765aeeec84fe..551426c4cd01 100644 --- a/drivers/media/v4l2-core/v4l2-ctrls-defs.c +++ b/drivers/media/v4l2-core/v4l2-ctrls-defs.c @@ -1235,6 +1235,8 @@ const char *v4l2_ctrl_get_name(u32 id) case V4L2_CID_STATELESS_HEVC_DECODE_MODE: return "HEVC Decode Mode"; case V4L2_CID_STATELESS_HEVC_START_CODE: return "HEVC Start Code"; case V4L2_CID_STATELESS_HEVC_ENTRY_POINT_OFFSETS: return "HEVC Entry Point Offsets"; + case V4L2_CID_STATELESS_HEVC_EXT_SPS_ST_RPS: return "HEVC Short Term Ref Sets"; + case V4L2_CID_STATELESS_HEVC_EXT_SPS_LT_RPS: return "HEVC Long Term Ref Sets"; case V4L2_CID_STATELESS_AV1_SEQUENCE: return "AV1 Sequence Parameters"; case V4L2_CID_STATELESS_AV1_TILE_GROUP_ENTRY: return "AV1 Tile Group Entry"; case V4L2_CID_STATELESS_AV1_FRAME: return "AV1 Frame Parameters"; @@ -1581,6 +1583,14 @@ void v4l2_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type, *type = V4L2_CTRL_TYPE_U32; *flags |= V4L2_CTRL_FLAG_DYNAMIC_ARRAY; break; + case V4L2_CID_STATELESS_HEVC_EXT_SPS_ST_RPS: + *type = V4L2_CTRL_TYPE_HEVC_EXT_SPS_ST_RPS; + *flags |= V4L2_CTRL_FLAG_DYNAMIC_ARRAY; + break; + case V4L2_CID_STATELESS_HEVC_EXT_SPS_LT_RPS: + *type = V4L2_CTRL_TYPE_HEVC_EXT_SPS_LT_RPS; + *flags |= V4L2_CTRL_FLAG_DYNAMIC_ARRAY; + break; case V4L2_CID_STATELESS_VP9_COMPRESSED_HDR: *type = V4L2_CTRL_TYPE_VP9_COMPRESSED_HDR; break; diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h index 572622e4535e..68dd0c4e47b2 100644 --- a/include/uapi/linux/v4l2-controls.h +++ b/include/uapi/linux/v4l2-controls.h @@ -2101,6 +2101,8 @@ struct v4l2_ctrl_mpeg2_quantisation { #define V4L2_CID_STATELESS_HEVC_DECODE_MODE (V4L2_CID_CODEC_STATELESS_BASE + 405) #define V4L2_CID_STATELESS_HEVC_START_CODE (V4L2_CID_CODEC_STATELESS_BASE + 406) #define V4L2_CID_STATELESS_HEVC_ENTRY_POINT_OFFSETS (V4L2_CID_CODEC_STATELESS_BASE + 407) +#define V4L2_CID_STATELESS_HEVC_EXT_SPS_ST_RPS (V4L2_CID_CODEC_STATELESS_BASE + 408) +#define V4L2_CID_STATELESS_HEVC_EXT_SPS_LT_RPS (V4L2_CID_CODEC_STATELESS_BASE + 409) enum v4l2_stateless_hevc_decode_mode { V4L2_STATELESS_HEVC_DECODE_MODE_SLICE_BASED, @@ -2556,6 +2558,65 @@ struct v4l2_ctrl_hevc_scaling_matrix { __u8 scaling_list_dc_coef_32x32[2]; }; +#define V4L2_HEVC_EXT_SPS_ST_RPS_FLAG_INTER_REF_PIC_SET_PRED 0x1 + +/* + * struct v4l2_ctrl_hevc_ext_sps_st_rps - HEVC short term RPS parameters + * + * Dynamic size 1-dimension array for short term RPS. The number of elements + * is v4l2_ctrl_hevc_sps::num_short_term_ref_pic_sets. It can contain up to 65 elements. + * + * @delta_idx_minus1: Specifies the delta compare to the index. See details in section 7.4.8 + * "Short-term reference picture set semantics" of the specification. + * @delta_rps_sign: Sign of the delta as specified in section 7.4.8 "Short-term reference picture + * set semantics" of the specification. + * @abs_delta_rps_minus1: Absolute delta RPS as specified in section 7.4.8 "Short-term reference + * picture set semantics" of the specification. + * @num_negative_pics: Number of short-term RPS entries that have picture order count values less + * than the picture order count value of the current picture. + * @num_positive_pics: Number of short-term RPS entries that have picture order count values + * greater than the picture order count value of the current picture. + * @used_by_curr_pic: Bit j specifies if short-term RPS j is used by the current picture. + * @use_delta_flag: Bit j equals to 1 specifies that the j-th entry in the source candidate + * short-term RPS is included in this candidate short-term RPS. + * @delta_poc_s0_minus1: Specifies the negative picture order count delta for the i-th entry in + * the short-term RPS. See details in section 7.4.8 "Short-term reference + * picture set semantics" of the specification. + * @delta_poc_s1_minus1: Specifies the positive picture order count delta for the i-th entry in + * the short-term RPS. See details in section 7.4.8 "Short-term reference + * picture set semantics" of the specification. + * @flags: See V4L2_HEVC_EXT_SPS_ST_RPS_FLAG_{} + */ +struct v4l2_ctrl_hevc_ext_sps_st_rps { + __u8 delta_idx_minus1; + __u8 delta_rps_sign; + __u8 num_negative_pics; + __u8 num_positive_pics; + __u32 used_by_curr_pic; + __u32 use_delta_flag; + __u16 abs_delta_rps_minus1; + __u16 delta_poc_s0_minus1[16]; + __u16 delta_poc_s1_minus1[16]; + __u16 flags; +}; + +#define V4L2_HEVC_EXT_SPS_LT_RPS_FLAG_USED_LT 0x1 + +/* + * struct v4l2_ctrl_hevc_ext_sps_lt_rps - HEVC long term RPS parameters + * + * Dynamic size 1-dimension array for long term RPS. The number of elements + * is v4l2_ctrl_hevc_sps::num_long_term_ref_pics_sps. It can contain up to 65 elements. + * + * @lt_ref_pic_poc_lsb_sps: picture order count modulo MaxPicOrderCntLsb of the i-th candidate + * long-term reference picture. + * @flags: See V4L2_HEVC_EXT_SPS_LT_RPS_FLAG_{} + */ +struct v4l2_ctrl_hevc_ext_sps_lt_rps { + __u16 lt_ref_pic_poc_lsb_sps; + __u16 flags; +}; + /* Stateless VP9 controls */ #define V4L2_VP9_LOOP_FILTER_FLAG_DELTA_ENABLED 0x1 diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h index 848e86617d5c..eda4492e40dc 100644 --- a/include/uapi/linux/videodev2.h +++ b/include/uapi/linux/videodev2.h @@ -1986,6 +1986,8 @@ enum v4l2_ctrl_type { V4L2_CTRL_TYPE_HEVC_SLICE_PARAMS = 0x0272, V4L2_CTRL_TYPE_HEVC_SCALING_MATRIX = 0x0273, V4L2_CTRL_TYPE_HEVC_DECODE_PARAMS = 0x0274, + V4L2_CTRL_TYPE_HEVC_EXT_SPS_ST_RPS = 0x0275, + V4L2_CTRL_TYPE_HEVC_EXT_SPS_LT_RPS = 0x0276, V4L2_CTRL_TYPE_AV1_SEQUENCE = 0x280, V4L2_CTRL_TYPE_AV1_TILE_GROUP_ENTRY = 0x281, -- cgit v1.2.3 From f5f2bad67a45cd1ef6f5b727da104694a81b3666 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Wed, 21 Jan 2026 08:31:49 +0100 Subject: block: make the new blkzoned UAPI constants discoverable The Linux 6.19 merge window added the new BLKREPORTZONESV2 ioctl, and with it the new BLK_ZONE_REP_CACHED and BLK_ZONE_COND_ACTIVE constants. The two constants are defined as part of enums, which makes it very painful for userspace to discover if they are present in the installed system headers. Use the #define to the same name trick to make them trivially discoverable using CPP directives. Fixes: 0bf0e2e46668 ("block: track zone conditions") Fixes: b30ffcdc0c15 ("block: introduce BLKREPORTZONESV2 ioctl") Reported-by: Andrey Albershteyn Signed-off-by: Christoph Hellwig Reviewed-by: Johannes Thumshirn Signed-off-by: Jens Axboe --- include/uapi/linux/blkzoned.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/blkzoned.h b/include/uapi/linux/blkzoned.h index e33f02703350..663836120966 100644 --- a/include/uapi/linux/blkzoned.h +++ b/include/uapi/linux/blkzoned.h @@ -81,7 +81,8 @@ enum blk_zone_cond { BLK_ZONE_COND_FULL = 0xE, BLK_ZONE_COND_OFFLINE = 0xF, - BLK_ZONE_COND_ACTIVE = 0xFF, + BLK_ZONE_COND_ACTIVE = 0xFF, /* added in Linux 6.19 */ +#define BLK_ZONE_COND_ACTIVE BLK_ZONE_COND_ACTIVE }; /** @@ -100,7 +101,8 @@ enum blk_zone_report_flags { BLK_ZONE_REP_CAPACITY = (1U << 0), /* Input flags */ - BLK_ZONE_REP_CACHED = (1U << 31), + BLK_ZONE_REP_CACHED = (1U << 31), /* added in Linux 6.19 */ +#define BLK_ZONE_REP_CACHED BLK_ZONE_REP_CACHED }; /** -- cgit v1.2.3 From d7a5da7a0f7fa7ff081140c4f6f971db98882703 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Mon, 15 Dec 2025 17:52:04 +0100 Subject: rseq: Add fields and constants for time slice extension Aside of a Kconfig knob add the following items: - Two flag bits for the rseq user space ABI, which allow user space to query the availability and enablement without a syscall. - A new member to the user space ABI struct rseq, which is going to be used to communicate request and grant between kernel and user space. - A rseq state struct to hold the kernel state of this - Documentation of the new mechanism Signed-off-by: Thomas Gleixner Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20251215155708.669472597@linutronix.de --- Documentation/userspace-api/index.rst | 1 + Documentation/userspace-api/rseq.rst | 135 ++++++++++++++++++++++++++++++++++ include/linux/rseq_types.h | 28 ++++++- include/uapi/linux/rseq.h | 38 ++++++++++ init/Kconfig | 12 +++ kernel/rseq.c | 7 ++ 6 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 Documentation/userspace-api/rseq.rst (limited to 'include/uapi/linux') diff --git a/Documentation/userspace-api/index.rst b/Documentation/userspace-api/index.rst index 8a61ac4c1bf1..fa0fe8ada68e 100644 --- a/Documentation/userspace-api/index.rst +++ b/Documentation/userspace-api/index.rst @@ -21,6 +21,7 @@ System calls ebpf/index ioctl/index mseal + rseq Security-related interfaces =========================== diff --git a/Documentation/userspace-api/rseq.rst b/Documentation/userspace-api/rseq.rst new file mode 100644 index 000000000000..e1fdb0d5ce69 --- /dev/null +++ b/Documentation/userspace-api/rseq.rst @@ -0,0 +1,135 @@ +===================== +Restartable Sequences +===================== + +Restartable Sequences allow to register a per thread userspace memory area +to be used as an ABI between kernel and userspace for three purposes: + + * userspace restartable sequences + + * quick access to read the current CPU number, node ID from userspace + + * scheduler time slice extensions + +Restartable sequences (per-cpu atomics) +--------------------------------------- + +Restartable sequences allow userspace to perform update operations on +per-cpu data without requiring heavyweight atomic operations. The actual +ABI is unfortunately only available in the code and selftests. + +Quick access to CPU number, node ID +----------------------------------- + +Allows to implement per CPU data efficiently. Documentation is in code and +selftests. :( + +Scheduler time slice extensions +------------------------------- + +This allows a thread to request a time slice extension when it enters a +critical section to avoid contention on a resource when the thread is +scheduled out inside of the critical section. + +The prerequisites for this functionality are: + + * Enabled in Kconfig + + * Enabled at boot time (default is enabled) + + * A rseq userspace pointer has been registered for the thread + +The thread has to enable the functionality via prctl(2):: + + prctl(PR_RSEQ_SLICE_EXTENSION, PR_RSEQ_SLICE_EXTENSION_SET, + PR_RSEQ_SLICE_EXT_ENABLE, 0, 0); + +prctl() returns 0 on success or otherwise with the following error codes: + +========= ============================================================== +Errorcode Meaning +========= ============================================================== +EINVAL Functionality not available or invalid function arguments. + Note: arg4 and arg5 must be zero +ENOTSUPP Functionality was disabled on the kernel command line +ENXIO Available, but no rseq user struct registered +========= ============================================================== + +The state can be also queried via prctl(2):: + + prctl(PR_RSEQ_SLICE_EXTENSION, PR_RSEQ_SLICE_EXTENSION_GET, 0, 0, 0); + +prctl() returns ``PR_RSEQ_SLICE_EXT_ENABLE`` when it is enabled or 0 if +disabled. Otherwise it returns with the following error codes: + +========= ============================================================== +Errorcode Meaning +========= ============================================================== +EINVAL Functionality not available or invalid function arguments. + Note: arg3 and arg4 and arg5 must be zero +========= ============================================================== + +The availability and status is also exposed via the rseq ABI struct flags +field via the ``RSEQ_CS_FLAG_SLICE_EXT_AVAILABLE_BIT`` and the +``RSEQ_CS_FLAG_SLICE_EXT_ENABLED_BIT``. These bits are read-only for user +space and only for informational purposes. + +If the mechanism was enabled via prctl(), the thread can request a time +slice extension by setting rseq::slice_ctrl::request to 1. If the thread is +interrupted and the interrupt results in a reschedule request in the +kernel, then the kernel can grant a time slice extension and return to +userspace instead of scheduling out. The length of the extension is +determined by the ``rseq_slice_extension_nsec`` sysctl. + +The kernel indicates the grant by clearing rseq::slice_ctrl::request and +setting rseq::slice_ctrl::granted to 1. If there is a reschedule of the +thread after granting the extension, the kernel clears the granted bit to +indicate that to userspace. + +If the request bit is still set when the leaving the critical section, +userspace can clear it and continue. + +If the granted bit is set, then userspace invokes rseq_slice_yield(2) when +leaving the critical section to relinquish the CPU. The kernel enforces +this by arming a timer to prevent misbehaving userspace from abusing this +mechanism. + +If both the request bit and the granted bit are false when leaving the +critical section, then this indicates that a grant was revoked and no +further action is required by userspace. + +The required code flow is as follows:: + + rseq->slice_ctrl.request = 1; + barrier(); // Prevent compiler reordering + critical_section(); + barrier(); // Prevent compiler reordering + rseq->slice_ctrl.request = 0; + if (rseq->slice_ctrl.granted) + rseq_slice_yield(); + +As all of this is strictly CPU local, there are no atomicity requirements. +Checking the granted state is racy, but that cannot be avoided at all:: + + if (rseq->slice_ctrl.granted) + -> Interrupt results in schedule and grant revocation + rseq_slice_yield(); + +So there is no point in pretending that this might be solved by an atomic +operation. + +If the thread issues a syscall other than rseq_slice_yield(2) within the +granted timeslice extension, the grant is also revoked and the CPU is +relinquished immediately when entering the kernel. This is required as +syscalls might consume arbitrary CPU time until they reach a scheduling +point when the preemption model is either NONE or VOLUNTARY and therefore +might exceed the grant by far. + +The preferred solution for user space is to use rseq_slice_yield(2) which +is side effect free. The support for arbitrary syscalls is required to +support onion layer architectured applications, where the code handling the +critical section and requesting the time slice extension has no control +over the code within the critical section. + +The kernel enforces flag consistency and terminates the thread with SIGSEGV +if it detects a violation. diff --git a/include/linux/rseq_types.h b/include/linux/rseq_types.h index 332dc14b81c9..67e40c059b1b 100644 --- a/include/linux/rseq_types.h +++ b/include/linux/rseq_types.h @@ -72,13 +72,36 @@ struct rseq_ids { }; }; +/** + * union rseq_slice_state - Status information for rseq time slice extension + * @state: Compound to access the overall state + * @enabled: Time slice extension is enabled for the task + * @granted: Time slice extension was granted to the task + */ +union rseq_slice_state { + u16 state; + struct { + u8 enabled; + u8 granted; + }; +}; + +/** + * struct rseq_slice - Status information for rseq time slice extension + * @state: Time slice extension state + */ +struct rseq_slice { + union rseq_slice_state state; +}; + /** * struct rseq_data - Storage for all rseq related data * @usrptr: Pointer to the registered user space RSEQ memory * @len: Length of the RSEQ region - * @sig: Signature of critial section abort IPs + * @sig: Signature of critical section abort IPs * @event: Storage for event management * @ids: Storage for cached CPU ID and MM CID + * @slice: Storage for time slice extension data */ struct rseq_data { struct rseq __user *usrptr; @@ -86,6 +109,9 @@ struct rseq_data { u32 sig; struct rseq_event event; struct rseq_ids ids; +#ifdef CONFIG_RSEQ_SLICE_EXTENSION + struct rseq_slice slice; +#endif }; #else /* CONFIG_RSEQ */ diff --git a/include/uapi/linux/rseq.h b/include/uapi/linux/rseq.h index 1b76d508400c..6afc219d1545 100644 --- a/include/uapi/linux/rseq.h +++ b/include/uapi/linux/rseq.h @@ -23,9 +23,15 @@ enum rseq_flags { }; enum rseq_cs_flags_bit { + /* Historical and unsupported bits */ RSEQ_CS_FLAG_NO_RESTART_ON_PREEMPT_BIT = 0, RSEQ_CS_FLAG_NO_RESTART_ON_SIGNAL_BIT = 1, RSEQ_CS_FLAG_NO_RESTART_ON_MIGRATE_BIT = 2, + /* (3) Intentional gap to put new bits into a separate byte */ + + /* User read only feature flags */ + RSEQ_CS_FLAG_SLICE_EXT_AVAILABLE_BIT = 4, + RSEQ_CS_FLAG_SLICE_EXT_ENABLED_BIT = 5, }; enum rseq_cs_flags { @@ -35,6 +41,11 @@ enum rseq_cs_flags { (1U << RSEQ_CS_FLAG_NO_RESTART_ON_SIGNAL_BIT), RSEQ_CS_FLAG_NO_RESTART_ON_MIGRATE = (1U << RSEQ_CS_FLAG_NO_RESTART_ON_MIGRATE_BIT), + + RSEQ_CS_FLAG_SLICE_EXT_AVAILABLE = + (1U << RSEQ_CS_FLAG_SLICE_EXT_AVAILABLE_BIT), + RSEQ_CS_FLAG_SLICE_EXT_ENABLED = + (1U << RSEQ_CS_FLAG_SLICE_EXT_ENABLED_BIT), }; /* @@ -53,6 +64,27 @@ struct rseq_cs { __u64 abort_ip; } __attribute__((aligned(4 * sizeof(__u64)))); +/** + * rseq_slice_ctrl - Time slice extension control structure + * @all: Compound value + * @request: Request for a time slice extension + * @granted: Granted time slice extension + * + * @request is set by user space and can be cleared by user space or kernel + * space. @granted is set and cleared by the kernel and must only be read + * by user space. + */ +struct rseq_slice_ctrl { + union { + __u32 all; + struct { + __u8 request; + __u8 granted; + __u16 __reserved; + }; + }; +}; + /* * struct rseq is aligned on 4 * 8 bytes to ensure it is always * contained within a single cache-line. @@ -141,6 +173,12 @@ struct rseq { */ __u32 mm_cid; + /* + * Time slice extension control structure. CPU local updates from + * kernel and user space. + */ + struct rseq_slice_ctrl slice_ctrl; + /* * Flexible array member at end of structure, after last feature field. */ diff --git a/init/Kconfig b/init/Kconfig index fa79feb8fe57..00c6fbb66a5a 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -1938,6 +1938,18 @@ config RSEQ If unsure, say Y. +config RSEQ_SLICE_EXTENSION + bool "Enable rseq-based time slice extension mechanism" + depends on RSEQ && HIGH_RES_TIMERS && GENERIC_ENTRY && HAVE_GENERIC_TIF_BITS + help + Allows userspace to request a limited time slice extension when + returning from an interrupt to user space via the RSEQ shared + data ABI. If granted, that allows to complete a critical section, + so that other threads are not stuck on a conflicted resource, + while the task is scheduled out. + + If unsure, say N. + config RSEQ_STATS default n bool "Enable lightweight statistics of restartable sequences" if EXPERT diff --git a/kernel/rseq.c b/kernel/rseq.c index 395d8b002350..07c324d5a201 100644 --- a/kernel/rseq.c +++ b/kernel/rseq.c @@ -389,6 +389,8 @@ static bool rseq_reset_ids(void) */ SYSCALL_DEFINE4(rseq, struct rseq __user *, rseq, u32, rseq_len, int, flags, u32, sig) { + u32 rseqfl = 0; + if (flags & RSEQ_FLAG_UNREGISTER) { if (flags & ~RSEQ_FLAG_UNREGISTER) return -EINVAL; @@ -440,6 +442,9 @@ SYSCALL_DEFINE4(rseq, struct rseq __user *, rseq, u32, rseq_len, int, flags, u32 if (!access_ok(rseq, rseq_len)) return -EFAULT; + if (IS_ENABLED(CONFIG_RSEQ_SLICE_EXTENSION)) + rseqfl |= RSEQ_CS_FLAG_SLICE_EXT_AVAILABLE; + scoped_user_write_access(rseq, efault) { /* * If the rseq_cs pointer is non-NULL on registration, clear it to @@ -449,11 +454,13 @@ SYSCALL_DEFINE4(rseq, struct rseq __user *, rseq, u32, rseq_len, int, flags, u32 * clearing the fields. Don't bother reading it, just reset it. */ unsafe_put_user(0UL, &rseq->rseq_cs, efault); + unsafe_put_user(rseqfl, &rseq->flags, efault); /* Initialize IDs in user space */ unsafe_put_user(RSEQ_CPU_ID_UNINITIALIZED, &rseq->cpu_id_start, efault); unsafe_put_user(RSEQ_CPU_ID_UNINITIALIZED, &rseq->cpu_id, efault); unsafe_put_user(0U, &rseq->node_id, efault); unsafe_put_user(0U, &rseq->mm_cid, efault); + unsafe_put_user(0U, &rseq->slice_ctrl.all, efault); } /* -- cgit v1.2.3 From 28621ec2d46c6adf7d33a6facbd83e2fa566bd34 Mon Sep 17 00:00:00 2001 From: Thomas Gleixner Date: Mon, 15 Dec 2025 17:52:12 +0100 Subject: rseq: Add prctl() to enable time slice extensions Implement a prctl() so that tasks can enable the time slice extension mechanism. This fails, when time slice extensions are disabled at compile time or on the kernel command line and when no rseq pointer is registered in the kernel. That allows to implement a single trivial check in the exit to user mode hotpath, to decide whether the whole mechanism needs to be invoked. Signed-off-by: Thomas Gleixner Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20251215155708.858717691@linutronix.de --- include/linux/rseq.h | 9 ++++++++ include/uapi/linux/prctl.h | 10 +++++++++ kernel/rseq.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++ kernel/sys.c | 6 ++++++ 4 files changed, 77 insertions(+) (limited to 'include/uapi/linux') diff --git a/include/linux/rseq.h b/include/linux/rseq.h index 2266f4dc77b6..3c194a02ad0a 100644 --- a/include/linux/rseq.h +++ b/include/linux/rseq.h @@ -163,4 +163,13 @@ void rseq_syscall(struct pt_regs *regs); static inline void rseq_syscall(struct pt_regs *regs) { } #endif /* !CONFIG_DEBUG_RSEQ */ +#ifdef CONFIG_RSEQ_SLICE_EXTENSION +int rseq_slice_extension_prctl(unsigned long arg2, unsigned long arg3); +#else /* CONFIG_RSEQ_SLICE_EXTENSION */ +static inline int rseq_slice_extension_prctl(unsigned long arg2, unsigned long arg3) +{ + return -ENOTSUPP; +} +#endif /* !CONFIG_RSEQ_SLICE_EXTENSION */ + #endif /* _LINUX_RSEQ_H */ diff --git a/include/uapi/linux/prctl.h b/include/uapi/linux/prctl.h index 51c4e8c82b1e..79944b7ae50a 100644 --- a/include/uapi/linux/prctl.h +++ b/include/uapi/linux/prctl.h @@ -386,4 +386,14 @@ struct prctl_mm_map { # define PR_FUTEX_HASH_SET_SLOTS 1 # define PR_FUTEX_HASH_GET_SLOTS 2 +/* RSEQ time slice extensions */ +#define PR_RSEQ_SLICE_EXTENSION 79 +# define PR_RSEQ_SLICE_EXTENSION_GET 1 +# define PR_RSEQ_SLICE_EXTENSION_SET 2 +/* + * Bits for RSEQ_SLICE_EXTENSION_GET/SET + * PR_RSEQ_SLICE_EXT_ENABLE: Enable + */ +# define PR_RSEQ_SLICE_EXT_ENABLE 0x01 + #endif /* _LINUX_PRCTL_H */ diff --git a/kernel/rseq.c b/kernel/rseq.c index 415d75b6df2c..09848bb14ec2 100644 --- a/kernel/rseq.c +++ b/kernel/rseq.c @@ -71,6 +71,7 @@ #define RSEQ_BUILD_SLOW_PATH #include +#include #include #include #include @@ -501,6 +502,57 @@ efault: #ifdef CONFIG_RSEQ_SLICE_EXTENSION DEFINE_STATIC_KEY_TRUE(rseq_slice_extension_key); +int rseq_slice_extension_prctl(unsigned long arg2, unsigned long arg3) +{ + switch (arg2) { + case PR_RSEQ_SLICE_EXTENSION_GET: + if (arg3) + return -EINVAL; + return current->rseq.slice.state.enabled ? PR_RSEQ_SLICE_EXT_ENABLE : 0; + + case PR_RSEQ_SLICE_EXTENSION_SET: { + u32 rflags, valid = RSEQ_CS_FLAG_SLICE_EXT_AVAILABLE; + bool enable = !!(arg3 & PR_RSEQ_SLICE_EXT_ENABLE); + + if (arg3 & ~PR_RSEQ_SLICE_EXT_ENABLE) + return -EINVAL; + if (!rseq_slice_extension_enabled()) + return -ENOTSUPP; + if (!current->rseq.usrptr) + return -ENXIO; + + /* No change? */ + if (enable == !!current->rseq.slice.state.enabled) + return 0; + + if (get_user(rflags, ¤t->rseq.usrptr->flags)) + goto die; + + if (current->rseq.slice.state.enabled) + valid |= RSEQ_CS_FLAG_SLICE_EXT_ENABLED; + + if ((rflags & valid) != valid) + goto die; + + rflags &= ~RSEQ_CS_FLAG_SLICE_EXT_ENABLED; + rflags |= RSEQ_CS_FLAG_SLICE_EXT_AVAILABLE; + if (enable) + rflags |= RSEQ_CS_FLAG_SLICE_EXT_ENABLED; + + if (put_user(rflags, ¤t->rseq.usrptr->flags)) + goto die; + + current->rseq.slice.state.enabled = enable; + return 0; + } + default: + return -EINVAL; + } +die: + force_sig(SIGSEGV); + return -EFAULT; +} + static int __init rseq_slice_cmdline(char *str) { bool on; diff --git a/kernel/sys.c b/kernel/sys.c index 8b58eece4e58..af71987df81c 100644 --- a/kernel/sys.c +++ b/kernel/sys.c @@ -53,6 +53,7 @@ #include #include #include +#include #include #include @@ -2868,6 +2869,11 @@ SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3, case PR_FUTEX_HASH: error = futex_hash_prctl(arg2, arg3, arg4); break; + case PR_RSEQ_SLICE_EXTENSION: + if (arg4 || arg5) + return -EINVAL; + error = rseq_slice_extension_prctl(arg2, arg3); + break; default: trace_task_prctl_unknown(option, arg2, arg3, arg4, arg5); error = -EINVAL; -- cgit v1.2.3 From d6200245c75e832af2087bc60ba2e6641a90eee9 Mon Sep 17 00:00:00 2001 From: Peter Zijlstra Date: Mon, 19 Jan 2026 11:23:57 +0100 Subject: rseq: Allow registering RSEQ with slice extension Since glibc cares about the number of syscalls required to initialize a new thread, allow initializing rseq with slice extension on. This avoids having to do another prctl(). Requested-by: Mathieu Desnoyers Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260121143207.814193010@infradead.org --- include/uapi/linux/rseq.h | 3 ++- kernel/rseq.c | 12 ++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/rseq.h b/include/uapi/linux/rseq.h index 6afc219d1545..863c4a00a66b 100644 --- a/include/uapi/linux/rseq.h +++ b/include/uapi/linux/rseq.h @@ -19,7 +19,8 @@ enum rseq_cpu_id_state { }; enum rseq_flags { - RSEQ_FLAG_UNREGISTER = (1 << 0), + RSEQ_FLAG_UNREGISTER = (1 << 0), + RSEQ_FLAG_SLICE_EXT_DEFAULT_ON = (1 << 1), }; enum rseq_cs_flags_bit { diff --git a/kernel/rseq.c b/kernel/rseq.c index 275d70114107..1c5490a172a8 100644 --- a/kernel/rseq.c +++ b/kernel/rseq.c @@ -424,7 +424,7 @@ SYSCALL_DEFINE4(rseq, struct rseq __user *, rseq, u32, rseq_len, int, flags, u32 return 0; } - if (unlikely(flags)) + if (unlikely(flags & ~(RSEQ_FLAG_SLICE_EXT_DEFAULT_ON))) return -EINVAL; if (current->rseq.usrptr) { @@ -459,8 +459,12 @@ SYSCALL_DEFINE4(rseq, struct rseq __user *, rseq, u32, rseq_len, int, flags, u32 if (!access_ok(rseq, rseq_len)) return -EFAULT; - if (IS_ENABLED(CONFIG_RSEQ_SLICE_EXTENSION)) + if (IS_ENABLED(CONFIG_RSEQ_SLICE_EXTENSION)) { rseqfl |= RSEQ_CS_FLAG_SLICE_EXT_AVAILABLE; + if (rseq_slice_extension_enabled() && + (flags & RSEQ_FLAG_SLICE_EXT_DEFAULT_ON)) + rseqfl |= RSEQ_CS_FLAG_SLICE_EXT_ENABLED; + } scoped_user_write_access(rseq, efault) { /* @@ -488,6 +492,10 @@ SYSCALL_DEFINE4(rseq, struct rseq __user *, rseq, u32, rseq_len, int, flags, u32 current->rseq.len = rseq_len; current->rseq.sig = sig; +#ifdef CONFIG_RSEQ_SLICE_EXTENSION + current->rseq.slice.state.enabled = !!(rseqfl & RSEQ_CS_FLAG_SLICE_EXT_ENABLED); +#endif + /* * If rseq was previously inactive, and has just been * registered, ensure the cpu_id_start and cpu_id fields -- cgit v1.2.3 From f174a9ffcd48d78a45d560c02ce4071ded036b53 Mon Sep 17 00:00:00 2001 From: Marc Zyngier Date: Mon, 19 Jan 2026 10:29:22 +0800 Subject: KVM: arm64: Add exit to userspace on {LD,ST}64B* outside of memslots The main use of {LD,ST}64B* is to talk to a device, which is hopefully directly assigned to the guest and requires no additional handling. However, this does not preclude a VMM from exposing a virtual device to the guest, and to allow 64 byte accesses as part of the programming interface. A direct consequence of this is that we need to be able to forward such access to userspace. Given that such a contraption is very unlikely to ever exist, we choose to offer a limited service: userspace gets (as part of a new exit reason) the ESR, the IPA, and that's it. It is fully expected to handle the full semantics of the instructions, deal with ACCDATA, the return values and increment PC. Much fun. A canonical implementation can also simply inject an abort and be done with it. Frankly, don't try to do anything else unless you have time to waste. Acked-by: Arnd Bergmann Acked-by: Oliver Upton Signed-off-by: Marc Zyngier Signed-off-by: Yicong Yang Signed-off-by: Zhou Wang Signed-off-by: Will Deacon --- arch/arm64/kvm/mmio.c | 27 ++++++++++++++++++++++++++- include/uapi/linux/kvm.h | 3 ++- 2 files changed, 28 insertions(+), 2 deletions(-) (limited to 'include/uapi/linux') diff --git a/arch/arm64/kvm/mmio.c b/arch/arm64/kvm/mmio.c index 54f9358c9e0e..e2285ed8c91d 100644 --- a/arch/arm64/kvm/mmio.c +++ b/arch/arm64/kvm/mmio.c @@ -159,6 +159,9 @@ int io_mem_abort(struct kvm_vcpu *vcpu, phys_addr_t fault_ipa) bool is_write; int len; u8 data_buf[8]; + u64 esr; + + esr = kvm_vcpu_get_esr(vcpu); /* * No valid syndrome? Ask userspace for help if it has @@ -168,7 +171,7 @@ int io_mem_abort(struct kvm_vcpu *vcpu, phys_addr_t fault_ipa) * though, so directly deliver an exception to the guest. */ if (!kvm_vcpu_dabt_isvalid(vcpu)) { - trace_kvm_mmio_nisv(*vcpu_pc(vcpu), kvm_vcpu_get_esr(vcpu), + trace_kvm_mmio_nisv(*vcpu_pc(vcpu), esr, kvm_vcpu_get_hfar(vcpu), fault_ipa); if (vcpu_is_protected(vcpu)) @@ -185,6 +188,28 @@ int io_mem_abort(struct kvm_vcpu *vcpu, phys_addr_t fault_ipa) return -ENOSYS; } + /* + * When (DFSC == 0b00xxxx || DFSC == 0b10101x) && DFSC != 0b0000xx + * ESR_EL2[12:11] describe the Load/Store Type. This allows us to + * punt the LD64B/ST64B/ST64BV/ST64BV0 instructions to userspace, + * which will have to provide a full emulation of these 4 + * instructions. No, we don't expect this do be fast. + * + * We rely on traps being set if the corresponding features are not + * enabled, so if we get here, userspace has promised us to handle + * it already. + */ + switch (kvm_vcpu_trap_get_fault(vcpu)) { + case 0b000100 ... 0b001111: + case 0b101010 ... 0b101011: + if (FIELD_GET(GENMASK(12, 11), esr)) { + run->exit_reason = KVM_EXIT_ARM_LDST64B; + run->arm_nisv.esr_iss = esr & ~(u64)ESR_ELx_FSC; + run->arm_nisv.fault_ipa = fault_ipa; + return 0; + } + } + /* * Prepare MMIO operation. First decode the syndrome data we get * from the CPU. Then try if some in-kernel emulation feels diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h index dddb781b0507..88cca0e22ece 100644 --- a/include/uapi/linux/kvm.h +++ b/include/uapi/linux/kvm.h @@ -180,6 +180,7 @@ struct kvm_xen_exit { #define KVM_EXIT_MEMORY_FAULT 39 #define KVM_EXIT_TDX 40 #define KVM_EXIT_ARM_SEA 41 +#define KVM_EXIT_ARM_LDST64B 42 /* For KVM_EXIT_INTERNAL_ERROR */ /* Emulate instruction failed. */ @@ -402,7 +403,7 @@ struct kvm_run { } eoi; /* KVM_EXIT_HYPERV */ struct kvm_hyperv_exit hyperv; - /* KVM_EXIT_ARM_NISV */ + /* KVM_EXIT_ARM_NISV / KVM_EXIT_ARM_LDST64B */ struct { __u64 esr_iss; __u64 fault_ipa; -- cgit v1.2.3 From 0f7afd80d81b739c4a9a6e4e24109ba1030c9c56 Mon Sep 17 00:00:00 2001 From: Terry Bowman Date: Wed, 14 Jan 2026 12:20:22 -0600 Subject: PCI: Move CXL DVSEC definitions into uapi/linux/pci_regs.h The CXL DVSECs are currently defined in cxl/core/cxlpci.h. These are not accessible to other subsystems. Move these to uapi/linux/pci_regs.h. The CXL DVSEC definitions will be renamed and reformatted to fit better with existing defines. Signed-off-by: Terry Bowman Reviewed-by: Dave Jiang Reviewed-by: Jonathan Cameron Reviewed-by: Dan Williams Signed-off-by: Dan Williams Acked-by: Bjorn Helgaas Link: https://patch.msgid.link/20260114182055.46029-2-terry.bowman@amd.com Signed-off-by: Dave Jiang --- drivers/cxl/cxlpci.h | 53 ----------------------------------- include/uapi/linux/pci_regs.h | 64 +++++++++++++++++++++++++++++++++++++++---- 2 files changed, 59 insertions(+), 58 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/cxl/cxlpci.h b/drivers/cxl/cxlpci.h index 1d526bea8431..cdb7cf3dbcb4 100644 --- a/drivers/cxl/cxlpci.h +++ b/drivers/cxl/cxlpci.h @@ -7,59 +7,6 @@ #define CXL_MEMORY_PROGIF 0x10 -/* - * See section 8.1 Configuration Space Registers in the CXL 2.0 - * Specification. Names are taken straight from the specification with "CXL" and - * "DVSEC" redundancies removed. When obvious, abbreviations may be used. - */ -#define PCI_DVSEC_HEADER1_LENGTH_MASK GENMASK(31, 20) - -/* CXL 2.0 8.1.3: PCIe DVSEC for CXL Device */ -#define CXL_DVSEC_PCIE_DEVICE 0 -#define CXL_DVSEC_CAP_OFFSET 0xA -#define CXL_DVSEC_MEM_CAPABLE BIT(2) -#define CXL_DVSEC_HDM_COUNT_MASK GENMASK(5, 4) -#define CXL_DVSEC_CTRL_OFFSET 0xC -#define CXL_DVSEC_MEM_ENABLE BIT(2) -#define CXL_DVSEC_RANGE_SIZE_HIGH(i) (0x18 + (i * 0x10)) -#define CXL_DVSEC_RANGE_SIZE_LOW(i) (0x1C + (i * 0x10)) -#define CXL_DVSEC_MEM_INFO_VALID BIT(0) -#define CXL_DVSEC_MEM_ACTIVE BIT(1) -#define CXL_DVSEC_MEM_SIZE_LOW_MASK GENMASK(31, 28) -#define CXL_DVSEC_RANGE_BASE_HIGH(i) (0x20 + (i * 0x10)) -#define CXL_DVSEC_RANGE_BASE_LOW(i) (0x24 + (i * 0x10)) -#define CXL_DVSEC_MEM_BASE_LOW_MASK GENMASK(31, 28) - -#define CXL_DVSEC_RANGE_MAX 2 - -/* CXL 2.0 8.1.4: Non-CXL Function Map DVSEC */ -#define CXL_DVSEC_FUNCTION_MAP 2 - -/* CXL 2.0 8.1.5: CXL 2.0 Extensions DVSEC for Ports */ -#define CXL_DVSEC_PORT_EXTENSIONS 3 - -/* CXL 2.0 8.1.6: GPF DVSEC for CXL Port */ -#define CXL_DVSEC_PORT_GPF 4 -#define CXL_DVSEC_PORT_GPF_PHASE_1_CONTROL_OFFSET 0x0C -#define CXL_DVSEC_PORT_GPF_PHASE_1_TMO_BASE_MASK GENMASK(3, 0) -#define CXL_DVSEC_PORT_GPF_PHASE_1_TMO_SCALE_MASK GENMASK(11, 8) -#define CXL_DVSEC_PORT_GPF_PHASE_2_CONTROL_OFFSET 0xE -#define CXL_DVSEC_PORT_GPF_PHASE_2_TMO_BASE_MASK GENMASK(3, 0) -#define CXL_DVSEC_PORT_GPF_PHASE_2_TMO_SCALE_MASK GENMASK(11, 8) - -/* CXL 2.0 8.1.7: GPF DVSEC for CXL Device */ -#define CXL_DVSEC_DEVICE_GPF 5 - -/* CXL 2.0 8.1.8: PCIe DVSEC for Flex Bus Port */ -#define CXL_DVSEC_PCIE_FLEXBUS_PORT 7 - -/* CXL 2.0 8.1.9: Register Locator DVSEC */ -#define CXL_DVSEC_REG_LOCATOR 8 -#define CXL_DVSEC_REG_LOCATOR_BLOCK1_OFFSET 0xC -#define CXL_DVSEC_REG_LOCATOR_BIR_MASK GENMASK(2, 0) -#define CXL_DVSEC_REG_LOCATOR_BLOCK_ID_MASK GENMASK(15, 8) -#define CXL_DVSEC_REG_LOCATOR_BLOCK_OFF_LOW_MASK GENMASK(31, 16) - /* * NOTE: Currently all the functions which are enabled for CXL require their * vectors to be in the first 16. Use this as the default max. diff --git a/include/uapi/linux/pci_regs.h b/include/uapi/linux/pci_regs.h index 3add74ae2594..6c4b6f19b18e 100644 --- a/include/uapi/linux/pci_regs.h +++ b/include/uapi/linux/pci_regs.h @@ -1253,11 +1253,6 @@ #define PCI_DEV3_STA 0x0c /* Device 3 Status Register */ #define PCI_DEV3_STA_SEGMENT 0x8 /* Segment Captured (end-to-end flit-mode detected) */ -/* Compute Express Link (CXL r3.1, sec 8.1.5) */ -#define PCI_DVSEC_CXL_PORT 3 -#define PCI_DVSEC_CXL_PORT_CTL 0x0c -#define PCI_DVSEC_CXL_PORT_CTL_UNMASK_SBR 0x00000001 - /* Integrity and Data Encryption Extended Capability */ #define PCI_IDE_CAP 0x04 #define PCI_IDE_CAP_LINK 0x1 /* Link IDE Stream Supported */ @@ -1338,4 +1333,63 @@ #define PCI_IDE_SEL_ADDR_3(x) (28 + (x) * PCI_IDE_SEL_ADDR_BLOCK_SIZE) #define PCI_IDE_SEL_BLOCK_SIZE(nr_assoc) (20 + PCI_IDE_SEL_ADDR_BLOCK_SIZE * (nr_assoc)) +/* Compute Express Link (CXL r3.1, sec 8.1.5) */ +#define PCI_DVSEC_CXL_PORT 3 +#define PCI_DVSEC_CXL_PORT_CTL 0x0c +#define PCI_DVSEC_CXL_PORT_CTL_UNMASK_SBR 0x00000001 + +/* + * Compute Express Link (CXL r3.2, sec 8.1) + * + * Note that CXL DVSEC id 3 and 7 to be ignored when the CXL link state + * is "disconnected" (CXL r3.2, sec 9.12.3). Re-enumerate these + * registers on downstream link-up events. + */ +#define PCI_DVSEC_HEADER1_LENGTH_MASK __GENMASK(31, 20) + +/* CXL 3.2 8.1.3: PCIe DVSEC for CXL Device */ +#define CXL_DVSEC_PCIE_DEVICE 0 +#define CXL_DVSEC_CAP_OFFSET 0xA +#define CXL_DVSEC_MEM_CAPABLE _BITUL(2) +#define CXL_DVSEC_HDM_COUNT_MASK __GENMASK(5, 4) +#define CXL_DVSEC_CTRL_OFFSET 0xC +#define CXL_DVSEC_MEM_ENABLE _BITUL(2) +#define CXL_DVSEC_RANGE_SIZE_HIGH(i) (0x18 + (i * 0x10)) +#define CXL_DVSEC_RANGE_SIZE_LOW(i) (0x1C + (i * 0x10)) +#define CXL_DVSEC_MEM_INFO_VALID _BITUL(0) +#define CXL_DVSEC_MEM_ACTIVE _BITUL(1) +#define CXL_DVSEC_MEM_SIZE_LOW_MASK __GENMASK(31, 28) +#define CXL_DVSEC_RANGE_BASE_HIGH(i) (0x20 + (i * 0x10)) +#define CXL_DVSEC_RANGE_BASE_LOW(i) (0x24 + (i * 0x10)) +#define CXL_DVSEC_MEM_BASE_LOW_MASK __GENMASK(31, 28) + +#define CXL_DVSEC_RANGE_MAX 2 + +/* CXL 3.2 8.1.4: Non-CXL Function Map DVSEC */ +#define CXL_DVSEC_FUNCTION_MAP 2 + +/* CXL 3.2 8.1.5: Extensions DVSEC for Ports */ +#define CXL_DVSEC_PORT 3 +#define CXL_DVSEC_PORT_CTL 0x0c +#define CXL_DVSEC_PORT_CTL_UNMASK_SBR 0x00000001 + +/* CXL 3.2 8.1.6: GPF DVSEC for CXL Port */ +#define CXL_DVSEC_PORT_GPF 4 +#define CXL_DVSEC_PORT_GPF_PHASE_1_CONTROL_OFFSET 0x0C +#define CXL_DVSEC_PORT_GPF_PHASE_1_TMO_BASE_MASK __GENMASK(3, 0) +#define CXL_DVSEC_PORT_GPF_PHASE_1_TMO_SCALE_MASK __GENMASK(11, 8) +#define CXL_DVSEC_PORT_GPF_PHASE_2_CONTROL_OFFSET 0xE +#define CXL_DVSEC_PORT_GPF_PHASE_2_TMO_BASE_MASK __GENMASK(3, 0) +#define CXL_DVSEC_PORT_GPF_PHASE_2_TMO_SCALE_MASK __GENMASK(11, 8) + +/* CXL 3.2 8.1.7: GPF DVSEC for CXL Device */ +#define CXL_DVSEC_DEVICE_GPF 5 + +/* CXL 3.2 8.1.9: Register Locator DVSEC */ +#define CXL_DVSEC_REG_LOCATOR 8 +#define CXL_DVSEC_REG_LOCATOR_BLOCK1_OFFSET 0xC +#define CXL_DVSEC_REG_LOCATOR_BIR_MASK __GENMASK(2, 0) +#define CXL_DVSEC_REG_LOCATOR_BLOCK_ID_MASK __GENMASK(15, 8) +#define CXL_DVSEC_REG_LOCATOR_BLOCK_OFF_LOW_MASK __GENMASK(31, 16) + #endif /* LINUX_PCI_REGS_H */ -- cgit v1.2.3 From 6612bd9ff0b1001cff5f5d79db6ce44427d2e99c Mon Sep 17 00:00:00 2001 From: Terry Bowman Date: Wed, 14 Jan 2026 12:20:23 -0600 Subject: PCI: Update CXL DVSEC definitions CXL DVSEC definitions were recently moved into uapi/pci_regs.h, but the newly added macros do not follow the file's existing naming conventions. The current format uses CXL_DVSEC_XYZ, while the new CXL entries must instead use the PCI_DVSEC_CXL_XYZ prefix to match the conventions already established in pci_regs.h. The new CXL DVSEC macros also introduce _MASK and _OFFSET suffixes, which are not used anywhere else in the file. These suffixes lengthen the identifiers and reduce readability. Remove _MASK and _OFFSET from the recently added definitions. Additionally, remove PCI_DVSEC_HEADER1_LENGTH, as it duplicates the existing PCI_DVSEC_HEADER1_LEN() macro. Update all existing references to use the new macro names. Finally, update the inline documentation to reference the latest revision of the CXL specification. Signed-off-by: Terry Bowman Reviewed-by: Dan Williams Acked-by: Bjorn Helgaas Link: https://patch.msgid.link/20260114182055.46029-3-terry.bowman@amd.com Signed-off-by: Dan Williams Signed-off-by: Dave Jiang --- drivers/cxl/core/pci.c | 58 +++++++++++++------------- drivers/cxl/core/regs.c | 14 +++---- drivers/cxl/pci.c | 2 +- include/uapi/linux/pci_regs.h | 94 ++++++++++++++++++++----------------------- 4 files changed, 81 insertions(+), 87 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/cxl/core/pci.c b/drivers/cxl/core/pci.c index 5b023a0178a4..077b386e0c8d 100644 --- a/drivers/cxl/core/pci.c +++ b/drivers/cxl/core/pci.c @@ -86,12 +86,12 @@ static int cxl_dvsec_mem_range_valid(struct cxl_dev_state *cxlds, int id) i = 1; do { rc = pci_read_config_dword(pdev, - d + CXL_DVSEC_RANGE_SIZE_LOW(id), + d + PCI_DVSEC_CXL_RANGE_SIZE_LOW(id), &temp); if (rc) return rc; - valid = FIELD_GET(CXL_DVSEC_MEM_INFO_VALID, temp); + valid = FIELD_GET(PCI_DVSEC_CXL_MEM_INFO_VALID, temp); if (valid) break; msleep(1000); @@ -121,11 +121,11 @@ static int cxl_dvsec_mem_range_active(struct cxl_dev_state *cxlds, int id) /* Check MEM ACTIVE bit, up to 60s timeout by default */ for (i = media_ready_timeout; i; i--) { rc = pci_read_config_dword( - pdev, d + CXL_DVSEC_RANGE_SIZE_LOW(id), &temp); + pdev, d + PCI_DVSEC_CXL_RANGE_SIZE_LOW(id), &temp); if (rc) return rc; - active = FIELD_GET(CXL_DVSEC_MEM_ACTIVE, temp); + active = FIELD_GET(PCI_DVSEC_CXL_MEM_ACTIVE, temp); if (active) break; msleep(1000); @@ -154,11 +154,11 @@ int cxl_await_media_ready(struct cxl_dev_state *cxlds) u16 cap; rc = pci_read_config_word(pdev, - d + CXL_DVSEC_CAP_OFFSET, &cap); + d + PCI_DVSEC_CXL_CAP, &cap); if (rc) return rc; - hdm_count = FIELD_GET(CXL_DVSEC_HDM_COUNT_MASK, cap); + hdm_count = FIELD_GET(PCI_DVSEC_CXL_HDM_COUNT, cap); for (i = 0; i < hdm_count; i++) { rc = cxl_dvsec_mem_range_valid(cxlds, i); if (rc) @@ -186,16 +186,16 @@ static int cxl_set_mem_enable(struct cxl_dev_state *cxlds, u16 val) u16 ctrl; int rc; - rc = pci_read_config_word(pdev, d + CXL_DVSEC_CTRL_OFFSET, &ctrl); + rc = pci_read_config_word(pdev, d + PCI_DVSEC_CXL_CTRL, &ctrl); if (rc < 0) return rc; - if ((ctrl & CXL_DVSEC_MEM_ENABLE) == val) + if ((ctrl & PCI_DVSEC_CXL_MEM_ENABLE) == val) return 1; - ctrl &= ~CXL_DVSEC_MEM_ENABLE; + ctrl &= ~PCI_DVSEC_CXL_MEM_ENABLE; ctrl |= val; - rc = pci_write_config_word(pdev, d + CXL_DVSEC_CTRL_OFFSET, ctrl); + rc = pci_write_config_word(pdev, d + PCI_DVSEC_CXL_CTRL, ctrl); if (rc < 0) return rc; @@ -211,7 +211,7 @@ static int devm_cxl_enable_mem(struct device *host, struct cxl_dev_state *cxlds) { int rc; - rc = cxl_set_mem_enable(cxlds, CXL_DVSEC_MEM_ENABLE); + rc = cxl_set_mem_enable(cxlds, PCI_DVSEC_CXL_MEM_ENABLE); if (rc < 0) return rc; if (rc > 0) @@ -273,11 +273,11 @@ int cxl_dvsec_rr_decode(struct cxl_dev_state *cxlds, return -ENXIO; } - rc = pci_read_config_word(pdev, d + CXL_DVSEC_CAP_OFFSET, &cap); + rc = pci_read_config_word(pdev, d + PCI_DVSEC_CXL_CAP, &cap); if (rc) return rc; - if (!(cap & CXL_DVSEC_MEM_CAPABLE)) { + if (!(cap & PCI_DVSEC_CXL_MEM_CAPABLE)) { dev_dbg(dev, "Not MEM Capable\n"); return -ENXIO; } @@ -288,7 +288,7 @@ int cxl_dvsec_rr_decode(struct cxl_dev_state *cxlds, * driver is for a spec defined class code which must be CXL.mem * capable, there is no point in continuing to enable CXL.mem. */ - hdm_count = FIELD_GET(CXL_DVSEC_HDM_COUNT_MASK, cap); + hdm_count = FIELD_GET(PCI_DVSEC_CXL_HDM_COUNT, cap); if (!hdm_count || hdm_count > 2) return -EINVAL; @@ -297,11 +297,11 @@ int cxl_dvsec_rr_decode(struct cxl_dev_state *cxlds, * disabled, and they will remain moot after the HDM Decoder * capability is enabled. */ - rc = pci_read_config_word(pdev, d + CXL_DVSEC_CTRL_OFFSET, &ctrl); + rc = pci_read_config_word(pdev, d + PCI_DVSEC_CXL_CTRL, &ctrl); if (rc) return rc; - info->mem_enabled = FIELD_GET(CXL_DVSEC_MEM_ENABLE, ctrl); + info->mem_enabled = FIELD_GET(PCI_DVSEC_CXL_MEM_ENABLE, ctrl); if (!info->mem_enabled) return 0; @@ -314,35 +314,35 @@ int cxl_dvsec_rr_decode(struct cxl_dev_state *cxlds, return rc; rc = pci_read_config_dword( - pdev, d + CXL_DVSEC_RANGE_SIZE_HIGH(i), &temp); + pdev, d + PCI_DVSEC_CXL_RANGE_SIZE_HIGH(i), &temp); if (rc) return rc; size = (u64)temp << 32; rc = pci_read_config_dword( - pdev, d + CXL_DVSEC_RANGE_SIZE_LOW(i), &temp); + pdev, d + PCI_DVSEC_CXL_RANGE_SIZE_LOW(i), &temp); if (rc) return rc; - size |= temp & CXL_DVSEC_MEM_SIZE_LOW_MASK; + size |= temp & PCI_DVSEC_CXL_MEM_SIZE_LOW; if (!size) { continue; } rc = pci_read_config_dword( - pdev, d + CXL_DVSEC_RANGE_BASE_HIGH(i), &temp); + pdev, d + PCI_DVSEC_CXL_RANGE_BASE_HIGH(i), &temp); if (rc) return rc; base = (u64)temp << 32; rc = pci_read_config_dword( - pdev, d + CXL_DVSEC_RANGE_BASE_LOW(i), &temp); + pdev, d + PCI_DVSEC_CXL_RANGE_BASE_LOW(i), &temp); if (rc) return rc; - base |= temp & CXL_DVSEC_MEM_BASE_LOW_MASK; + base |= temp & PCI_DVSEC_CXL_MEM_BASE_LOW; info->dvsec_range[ranges++] = (struct range) { .start = base, @@ -1068,7 +1068,7 @@ u16 cxl_gpf_get_dvsec(struct device *dev) is_port = false; dvsec = pci_find_dvsec_capability(pdev, PCI_VENDOR_ID_CXL, - is_port ? CXL_DVSEC_PORT_GPF : CXL_DVSEC_DEVICE_GPF); + is_port ? PCI_DVSEC_CXL_PORT_GPF : PCI_DVSEC_CXL_DEVICE_GPF); if (!dvsec) dev_warn(dev, "%s GPF DVSEC not present\n", is_port ? "Port" : "Device"); @@ -1084,14 +1084,14 @@ static int update_gpf_port_dvsec(struct pci_dev *pdev, int dvsec, int phase) switch (phase) { case 1: - offset = CXL_DVSEC_PORT_GPF_PHASE_1_CONTROL_OFFSET; - base = CXL_DVSEC_PORT_GPF_PHASE_1_TMO_BASE_MASK; - scale = CXL_DVSEC_PORT_GPF_PHASE_1_TMO_SCALE_MASK; + offset = PCI_DVSEC_CXL_PORT_GPF_PHASE_1_CONTROL; + base = PCI_DVSEC_CXL_PORT_GPF_PHASE_1_TMO_BASE; + scale = PCI_DVSEC_CXL_PORT_GPF_PHASE_1_TMO_SCALE; break; case 2: - offset = CXL_DVSEC_PORT_GPF_PHASE_2_CONTROL_OFFSET; - base = CXL_DVSEC_PORT_GPF_PHASE_2_TMO_BASE_MASK; - scale = CXL_DVSEC_PORT_GPF_PHASE_2_TMO_SCALE_MASK; + offset = PCI_DVSEC_CXL_PORT_GPF_PHASE_2_CONTROL; + base = PCI_DVSEC_CXL_PORT_GPF_PHASE_2_TMO_BASE; + scale = PCI_DVSEC_CXL_PORT_GPF_PHASE_2_TMO_SCALE; break; default: return -EINVAL; diff --git a/drivers/cxl/core/regs.c b/drivers/cxl/core/regs.c index 5ca7b0eed568..a010b3214342 100644 --- a/drivers/cxl/core/regs.c +++ b/drivers/cxl/core/regs.c @@ -271,10 +271,10 @@ EXPORT_SYMBOL_NS_GPL(cxl_map_device_regs, "CXL"); static bool cxl_decode_regblock(struct pci_dev *pdev, u32 reg_lo, u32 reg_hi, struct cxl_register_map *map) { - u8 reg_type = FIELD_GET(CXL_DVSEC_REG_LOCATOR_BLOCK_ID_MASK, reg_lo); - int bar = FIELD_GET(CXL_DVSEC_REG_LOCATOR_BIR_MASK, reg_lo); + u8 reg_type = FIELD_GET(PCI_DVSEC_CXL_REG_LOCATOR_BLOCK_ID, reg_lo); + int bar = FIELD_GET(PCI_DVSEC_CXL_REG_LOCATOR_BIR, reg_lo); u64 offset = ((u64)reg_hi << 32) | - (reg_lo & CXL_DVSEC_REG_LOCATOR_BLOCK_OFF_LOW_MASK); + (reg_lo & PCI_DVSEC_CXL_REG_LOCATOR_BLOCK_OFF_LOW); if (offset > pci_resource_len(pdev, bar)) { dev_warn(&pdev->dev, @@ -311,15 +311,15 @@ static int __cxl_find_regblock_instance(struct pci_dev *pdev, enum cxl_regloc_ty }; regloc = pci_find_dvsec_capability(pdev, PCI_VENDOR_ID_CXL, - CXL_DVSEC_REG_LOCATOR); + PCI_DVSEC_CXL_REG_LOCATOR); if (!regloc) return -ENXIO; pci_read_config_dword(pdev, regloc + PCI_DVSEC_HEADER1, ®loc_size); - regloc_size = FIELD_GET(PCI_DVSEC_HEADER1_LENGTH_MASK, regloc_size); + regloc_size = PCI_DVSEC_HEADER1_LEN(regloc_size); - regloc += CXL_DVSEC_REG_LOCATOR_BLOCK1_OFFSET; - regblocks = (regloc_size - CXL_DVSEC_REG_LOCATOR_BLOCK1_OFFSET) / 8; + regloc += PCI_DVSEC_CXL_REG_LOCATOR_BLOCK1; + regblocks = (regloc_size - PCI_DVSEC_CXL_REG_LOCATOR_BLOCK1) / 8; for (i = 0; i < regblocks; i++, regloc += 8) { u32 reg_lo, reg_hi; diff --git a/drivers/cxl/pci.c b/drivers/cxl/pci.c index 0be4e508affe..b7f694bda913 100644 --- a/drivers/cxl/pci.c +++ b/drivers/cxl/pci.c @@ -933,7 +933,7 @@ static int cxl_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) cxlds->rcd = is_cxl_restricted(pdev); cxlds->serial = pci_get_dsn(pdev); cxlds->cxl_dvsec = pci_find_dvsec_capability( - pdev, PCI_VENDOR_ID_CXL, CXL_DVSEC_PCIE_DEVICE); + pdev, PCI_VENDOR_ID_CXL, PCI_DVSEC_CXL_DEVICE); if (!cxlds->cxl_dvsec) dev_warn(&pdev->dev, "Device DVSEC not present, skip CXL.mem init\n"); diff --git a/include/uapi/linux/pci_regs.h b/include/uapi/linux/pci_regs.h index 6c4b6f19b18e..662582bdccf0 100644 --- a/include/uapi/linux/pci_regs.h +++ b/include/uapi/linux/pci_regs.h @@ -1333,63 +1333,57 @@ #define PCI_IDE_SEL_ADDR_3(x) (28 + (x) * PCI_IDE_SEL_ADDR_BLOCK_SIZE) #define PCI_IDE_SEL_BLOCK_SIZE(nr_assoc) (20 + PCI_IDE_SEL_ADDR_BLOCK_SIZE * (nr_assoc)) -/* Compute Express Link (CXL r3.1, sec 8.1.5) */ -#define PCI_DVSEC_CXL_PORT 3 -#define PCI_DVSEC_CXL_PORT_CTL 0x0c -#define PCI_DVSEC_CXL_PORT_CTL_UNMASK_SBR 0x00000001 - /* - * Compute Express Link (CXL r3.2, sec 8.1) + * Compute Express Link (CXL r4.0, sec 8.1) * * Note that CXL DVSEC id 3 and 7 to be ignored when the CXL link state - * is "disconnected" (CXL r3.2, sec 9.12.3). Re-enumerate these + * is "disconnected" (CXL r4.0, sec 9.12.3). Re-enumerate these * registers on downstream link-up events. */ -#define PCI_DVSEC_HEADER1_LENGTH_MASK __GENMASK(31, 20) - -/* CXL 3.2 8.1.3: PCIe DVSEC for CXL Device */ -#define CXL_DVSEC_PCIE_DEVICE 0 -#define CXL_DVSEC_CAP_OFFSET 0xA -#define CXL_DVSEC_MEM_CAPABLE _BITUL(2) -#define CXL_DVSEC_HDM_COUNT_MASK __GENMASK(5, 4) -#define CXL_DVSEC_CTRL_OFFSET 0xC -#define CXL_DVSEC_MEM_ENABLE _BITUL(2) -#define CXL_DVSEC_RANGE_SIZE_HIGH(i) (0x18 + (i * 0x10)) -#define CXL_DVSEC_RANGE_SIZE_LOW(i) (0x1C + (i * 0x10)) -#define CXL_DVSEC_MEM_INFO_VALID _BITUL(0) -#define CXL_DVSEC_MEM_ACTIVE _BITUL(1) -#define CXL_DVSEC_MEM_SIZE_LOW_MASK __GENMASK(31, 28) -#define CXL_DVSEC_RANGE_BASE_HIGH(i) (0x20 + (i * 0x10)) -#define CXL_DVSEC_RANGE_BASE_LOW(i) (0x24 + (i * 0x10)) -#define CXL_DVSEC_MEM_BASE_LOW_MASK __GENMASK(31, 28) + +/* CXL r4.0, 8.1.3: PCIe DVSEC for CXL Device */ +#define PCI_DVSEC_CXL_DEVICE 0 +#define PCI_DVSEC_CXL_CAP 0xA +#define PCI_DVSEC_CXL_MEM_CAPABLE _BITUL(2) +#define PCI_DVSEC_CXL_HDM_COUNT __GENMASK(5, 4) +#define PCI_DVSEC_CXL_CTRL 0xC +#define PCI_DVSEC_CXL_MEM_ENABLE _BITUL(2) +#define PCI_DVSEC_CXL_RANGE_SIZE_HIGH(i) (0x18 + (i * 0x10)) +#define PCI_DVSEC_CXL_RANGE_SIZE_LOW(i) (0x1C + (i * 0x10)) +#define PCI_DVSEC_CXL_MEM_INFO_VALID _BITUL(0) +#define PCI_DVSEC_CXL_MEM_ACTIVE _BITUL(1) +#define PCI_DVSEC_CXL_MEM_SIZE_LOW __GENMASK(31, 28) +#define PCI_DVSEC_CXL_RANGE_BASE_HIGH(i) (0x20 + (i * 0x10)) +#define PCI_DVSEC_CXL_RANGE_BASE_LOW(i) (0x24 + (i * 0x10)) +#define PCI_DVSEC_CXL_MEM_BASE_LOW __GENMASK(31, 28) #define CXL_DVSEC_RANGE_MAX 2 -/* CXL 3.2 8.1.4: Non-CXL Function Map DVSEC */ -#define CXL_DVSEC_FUNCTION_MAP 2 - -/* CXL 3.2 8.1.5: Extensions DVSEC for Ports */ -#define CXL_DVSEC_PORT 3 -#define CXL_DVSEC_PORT_CTL 0x0c -#define CXL_DVSEC_PORT_CTL_UNMASK_SBR 0x00000001 - -/* CXL 3.2 8.1.6: GPF DVSEC for CXL Port */ -#define CXL_DVSEC_PORT_GPF 4 -#define CXL_DVSEC_PORT_GPF_PHASE_1_CONTROL_OFFSET 0x0C -#define CXL_DVSEC_PORT_GPF_PHASE_1_TMO_BASE_MASK __GENMASK(3, 0) -#define CXL_DVSEC_PORT_GPF_PHASE_1_TMO_SCALE_MASK __GENMASK(11, 8) -#define CXL_DVSEC_PORT_GPF_PHASE_2_CONTROL_OFFSET 0xE -#define CXL_DVSEC_PORT_GPF_PHASE_2_TMO_BASE_MASK __GENMASK(3, 0) -#define CXL_DVSEC_PORT_GPF_PHASE_2_TMO_SCALE_MASK __GENMASK(11, 8) - -/* CXL 3.2 8.1.7: GPF DVSEC for CXL Device */ -#define CXL_DVSEC_DEVICE_GPF 5 - -/* CXL 3.2 8.1.9: Register Locator DVSEC */ -#define CXL_DVSEC_REG_LOCATOR 8 -#define CXL_DVSEC_REG_LOCATOR_BLOCK1_OFFSET 0xC -#define CXL_DVSEC_REG_LOCATOR_BIR_MASK __GENMASK(2, 0) -#define CXL_DVSEC_REG_LOCATOR_BLOCK_ID_MASK __GENMASK(15, 8) -#define CXL_DVSEC_REG_LOCATOR_BLOCK_OFF_LOW_MASK __GENMASK(31, 16) +/* CXL r4.0, 8.1.4: Non-CXL Function Map DVSEC */ +#define PCI_DVSEC_CXL_FUNCTION_MAP 2 + +/* CXL r4.0, 8.1.5: Extensions DVSEC for Ports */ +#define PCI_DVSEC_CXL_PORT 3 +#define PCI_DVSEC_CXL_PORT_CTL 0x0c +#define PCI_DVSEC_CXL_PORT_CTL_UNMASK_SBR 0x00000001 + +/* CXL r4.0, 8.1.6: GPF DVSEC for CXL Port */ +#define PCI_DVSEC_CXL_PORT_GPF 4 +#define PCI_DVSEC_CXL_PORT_GPF_PHASE_1_CONTROL 0x0C +#define PCI_DVSEC_CXL_PORT_GPF_PHASE_1_TMO_BASE __GENMASK(3, 0) +#define PCI_DVSEC_CXL_PORT_GPF_PHASE_1_TMO_SCALE __GENMASK(11, 8) +#define PCI_DVSEC_CXL_PORT_GPF_PHASE_2_CONTROL 0xE +#define PCI_DVSEC_CXL_PORT_GPF_PHASE_2_TMO_BASE __GENMASK(3, 0) +#define PCI_DVSEC_CXL_PORT_GPF_PHASE_2_TMO_SCALE __GENMASK(11, 8) + +/* CXL r4.0, 8.1.7: GPF DVSEC for CXL Device */ +#define PCI_DVSEC_CXL_DEVICE_GPF 5 + +/* CXL r4.0, 8.1.9: Register Locator DVSEC */ +#define PCI_DVSEC_CXL_REG_LOCATOR 8 +#define PCI_DVSEC_CXL_REG_LOCATOR_BLOCK1 0xC +#define PCI_DVSEC_CXL_REG_LOCATOR_BIR __GENMASK(2, 0) +#define PCI_DVSEC_CXL_REG_LOCATOR_BLOCK_ID __GENMASK(15, 8) +#define PCI_DVSEC_CXL_REG_LOCATOR_BLOCK_OFF_LOW __GENMASK(31, 16) #endif /* LINUX_PCI_REGS_H */ -- cgit v1.2.3 From 7c29ba02210c6e4570cdce53813a1ae68fb6d049 Mon Sep 17 00:00:00 2001 From: Terry Bowman Date: Wed, 14 Jan 2026 12:20:24 -0600 Subject: PCI: Introduce pcie_is_cxl() CXL is a protocol that runs on top of PCIe electricals. Its error model also runs on top of the PCIe AER error model by standardizing "internal" errors as "CXL" errors. Linux has historically ignored internal errors. CXL protocol error handling is then a task of enhancing the PCIe AER core to understand that PCIe ports (upstream and downstream) and endpoints may throw internal errors that represent standard CXL protocol errors. The proposed method to make that determination is to teach 'struct pci_dev' to cache when its link has trained the CXL.mem and/or CXL.cache protocols and then treat all internal errors as CXL errors. A design goal is to not burden the PCIe AER core with CXL knowledge beyond just enough to forward error notifications to the CXL RAS core. The forwarded notification looks up a 'struct cxl_port' or 'struct cxl_dport' companion device to the PCI device. Introduce set_pcie_cxl() with logic checking for CXL.mem or CXL.cache status in the CXL Flex Bus DVSEC status register. The CXL Flex Bus DVSEC presence is used because it is required for all the CXL PCIe devices.[1] [1] CXL 3.1 Spec, 8.1.1 PCIe Designated Vendor-Specific Extended Capability (DVSEC) ID Assignment, Table 8-2 Signed-off-by: Terry Bowman Reviewed-by: Ira Weiny Reviewed-by: Kuppuswamy Sathyanarayanan Reviewed-by: Dave Jiang Reviewed-by: Jonathan Cameron Reviewed-by: Alejandro Lucero Reviewed-by: Ben Cheatham Reviewed-by: Dan Williams Acked-by: Bjorn Helgaas Link: https://patch.msgid.link/20260114182055.46029-4-terry.bowman@amd.com Signed-off-by: Dan Williams Signed-off-by: Dave Jiang --- drivers/pci/probe.c | 31 +++++++++++++++++++++++++++++++ include/linux/pci.h | 6 ++++++ include/uapi/linux/pci_regs.h | 6 ++++++ 3 files changed, 43 insertions(+) (limited to 'include/uapi/linux') diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 41183aed8f5d..bd7ce41d0c7a 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -1735,6 +1735,35 @@ static void set_pcie_thunderbolt(struct pci_dev *dev) dev->is_thunderbolt = 1; } +static void set_pcie_cxl(struct pci_dev *dev) +{ + struct pci_dev *bridge; + u16 dvsec, cap; + + if (!pci_is_pcie(dev)) + return; + + /* + * Update parent's CXL state because alternate protocol training + * may have changed + */ + bridge = pci_upstream_bridge(dev); + if (bridge) + set_pcie_cxl(bridge); + + dvsec = pci_find_dvsec_capability(dev, PCI_VENDOR_ID_CXL, + PCI_DVSEC_CXL_FLEXBUS_PORT); + if (!dvsec) + return; + + pci_read_config_word(dev, dvsec + PCI_DVSEC_CXL_FLEXBUS_PORT_STATUS, + &cap); + + dev->is_cxl = FIELD_GET(PCI_DVSEC_CXL_FLEXBUS_PORT_STATUS_CACHE, cap) || + FIELD_GET(PCI_DVSEC_CXL_FLEXBUS_PORT_STATUS_MEM, cap); + +} + static void set_pcie_untrusted(struct pci_dev *dev) { struct pci_dev *parent = pci_upstream_bridge(dev); @@ -2065,6 +2094,8 @@ int pci_setup_device(struct pci_dev *dev) /* Need to have dev->cfg_size ready */ set_pcie_thunderbolt(dev); + set_pcie_cxl(dev); + set_pcie_untrusted(dev); if (pci_is_pcie(dev)) diff --git a/include/linux/pci.h b/include/linux/pci.h index 864775651c6f..f8e8b3df794d 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -463,6 +463,7 @@ struct pci_dev { unsigned int is_pciehp:1; unsigned int shpc_managed:1; /* SHPC owned by shpchp */ unsigned int is_thunderbolt:1; /* Thunderbolt controller */ + unsigned int is_cxl:1; /* Compute Express Link (CXL) */ /* * Devices marked being untrusted are the ones that can potentially * execute DMA attacks and similar. They are typically connected @@ -791,6 +792,11 @@ static inline bool pci_is_display(struct pci_dev *pdev) return (pdev->class >> 16) == PCI_BASE_CLASS_DISPLAY; } +static inline bool pcie_is_cxl(struct pci_dev *pci_dev) +{ + return pci_dev->is_cxl; +} + #define for_each_pci_bridge(dev, bus) \ list_for_each_entry(dev, &bus->devices, bus_list) \ if (!pci_is_bridge(dev)) {} else diff --git a/include/uapi/linux/pci_regs.h b/include/uapi/linux/pci_regs.h index 662582bdccf0..b6622fd60fd9 100644 --- a/include/uapi/linux/pci_regs.h +++ b/include/uapi/linux/pci_regs.h @@ -1379,6 +1379,12 @@ /* CXL r4.0, 8.1.7: GPF DVSEC for CXL Device */ #define PCI_DVSEC_CXL_DEVICE_GPF 5 +/* CXL r4.0, 8.1.8: Flex Bus DVSEC */ +#define PCI_DVSEC_CXL_FLEXBUS_PORT 7 +#define PCI_DVSEC_CXL_FLEXBUS_PORT_STATUS 0xE +#define PCI_DVSEC_CXL_FLEXBUS_PORT_STATUS_CACHE _BITUL(0) +#define PCI_DVSEC_CXL_FLEXBUS_PORT_STATUS_MEM _BITUL(2) + /* CXL r4.0, 8.1.9: Register Locator DVSEC */ #define PCI_DVSEC_CXL_REG_LOCATOR 8 #define PCI_DVSEC_CXL_REG_LOCATOR_BLOCK1 0xC -- cgit v1.2.3 From 5247c034a67f5a93cc1faa15e9867eec5b22f38a Mon Sep 17 00:00:00 2001 From: Pavel Begunkov Date: Tue, 20 Jan 2026 20:47:40 +0000 Subject: io_uring: introduce non-circular SQ Outside of SQPOLL, normally SQ entries are consumed by the time the submission syscall returns. For those cases we don't need a circular buffer and the head/tail tracking, instead the kernel can assume that entries always start from the beginning of the SQ at index 0. This patch introduces a setup flag doing exactly that. It's a simpler and helps to keeps SQEs hot in cache. The feature is optional and enabled by setting IORING_SETUP_SQ_REWIND. The flag is rejected if passed together with SQPOLL as it'd require waiting for SQ before each submission. It also requires IORING_SETUP_NO_SQARRAY, which can be supported but it's unlikely there will be users, so leave more space for future optimisations. Signed-off-by: Pavel Begunkov Signed-off-by: Jens Axboe --- include/uapi/linux/io_uring.h | 12 ++++++++++++ io_uring/io_uring.c | 29 ++++++++++++++++++++++------- io_uring/io_uring.h | 3 ++- 3 files changed, 36 insertions(+), 8 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/io_uring.h b/include/uapi/linux/io_uring.h index b5b23c0d5283..475094c7a668 100644 --- a/include/uapi/linux/io_uring.h +++ b/include/uapi/linux/io_uring.h @@ -237,6 +237,18 @@ enum io_uring_sqe_flags_bit { */ #define IORING_SETUP_SQE_MIXED (1U << 19) +/* + * When set, io_uring ignores SQ head and tail and fetches SQEs to submit + * starting from index 0 instead from the index stored in the head pointer. + * IOW, the user should place all SQE at the beginning of the SQ memory + * before issuing a submission syscall. + * + * It requires IORING_SETUP_NO_SQARRAY and is incompatible with + * IORING_SETUP_SQPOLL. The user must also never change the SQ head and tail + * values and keep it set to 0. Any other value is undefined behaviour. + */ +#define IORING_SETUP_SQ_REWIND (1U << 20) + enum io_uring_op { IORING_OP_NOP, IORING_OP_READV, diff --git a/io_uring/io_uring.c b/io_uring/io_uring.c index a50459238bee..0f88ec74e55d 100644 --- a/io_uring/io_uring.c +++ b/io_uring/io_uring.c @@ -1945,12 +1945,16 @@ static void io_commit_sqring(struct io_ring_ctx *ctx) { struct io_rings *rings = ctx->rings; - /* - * Ensure any loads from the SQEs are done at this point, - * since once we write the new head, the application could - * write new data to them. - */ - smp_store_release(&rings->sq.head, ctx->cached_sq_head); + if (ctx->flags & IORING_SETUP_SQ_REWIND) { + ctx->cached_sq_head = 0; + } else { + /* + * Ensure any loads from the SQEs are done at this point, + * since once we write the new head, the application could + * write new data to them. + */ + smp_store_release(&rings->sq.head, ctx->cached_sq_head); + } } /* @@ -1996,10 +2000,15 @@ static bool io_get_sqe(struct io_ring_ctx *ctx, const struct io_uring_sqe **sqe) int io_submit_sqes(struct io_ring_ctx *ctx, unsigned int nr) __must_hold(&ctx->uring_lock) { - unsigned int entries = io_sqring_entries(ctx); + unsigned int entries; unsigned int left; int ret; + if (ctx->flags & IORING_SETUP_SQ_REWIND) + entries = ctx->sq_entries; + else + entries = io_sqring_entries(ctx); + entries = min(nr, entries); if (unlikely(!entries)) return 0; @@ -2728,6 +2737,12 @@ static int io_uring_sanitise_params(struct io_uring_params *p) if (flags & ~IORING_SETUP_FLAGS) return -EINVAL; + if (flags & IORING_SETUP_SQ_REWIND) { + if ((flags & IORING_SETUP_SQPOLL) || + !(flags & IORING_SETUP_NO_SQARRAY)) + return -EINVAL; + } + /* There is no way to mmap rings without a real fd */ if ((flags & IORING_SETUP_REGISTERED_FD_ONLY) && !(flags & IORING_SETUP_NO_MMAP)) diff --git a/io_uring/io_uring.h b/io_uring/io_uring.h index 29b8f90fdabf..acdc39b9f8d6 100644 --- a/io_uring/io_uring.h +++ b/io_uring/io_uring.h @@ -69,7 +69,8 @@ struct io_ctx_config { IORING_SETUP_NO_SQARRAY |\ IORING_SETUP_HYBRID_IOPOLL |\ IORING_SETUP_CQE_MIXED |\ - IORING_SETUP_SQE_MIXED) + IORING_SETUP_SQE_MIXED |\ + IORING_SETUP_SQ_REWIND) #define IORING_ENTER_FLAGS (IORING_ENTER_GETEVENTS |\ IORING_ENTER_SQ_WAKEUP |\ -- cgit v1.2.3 From e86f89ab24f5ec595879a01eebb5df84f5ed6d2b Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Fri, 16 Jan 2026 22:18:36 +0800 Subject: ublk: add new batch command UBLK_U_IO_PREP_IO_CMDS & UBLK_U_IO_COMMIT_IO_CMDS Add new command UBLK_U_IO_PREP_IO_CMDS, which is the batch version of UBLK_IO_FETCH_REQ. Add new command UBLK_U_IO_COMMIT_IO_CMDS, which is for committing io command result only, still the batch version. The new command header type is `struct ublk_batch_io`. This patch doesn't actually implement these commands yet, just validates the SQE fields. Reviewed-by: Caleb Sander Mateos Signed-off-by: Ming Lei Signed-off-by: Jens Axboe --- drivers/block/ublk_drv.c | 87 ++++++++++++++++++++++++++++++++++++++++++- include/uapi/linux/ublk_cmd.h | 49 ++++++++++++++++++++++++ 2 files changed, 135 insertions(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c index 0f9fcd16258b..22c7296d90f3 100644 --- a/drivers/block/ublk_drv.c +++ b/drivers/block/ublk_drv.c @@ -91,6 +91,11 @@ UBLK_PARAM_TYPE_DMA_ALIGN | UBLK_PARAM_TYPE_SEGMENT | \ UBLK_PARAM_TYPE_INTEGRITY) +#define UBLK_BATCH_F_ALL \ + (UBLK_BATCH_F_HAS_ZONE_LBA | \ + UBLK_BATCH_F_HAS_BUF_ADDR | \ + UBLK_BATCH_F_AUTO_BUF_REG_FALLBACK) + struct ublk_uring_cmd_pdu { /* * Store requests in same batch temporarily for queuing them to @@ -114,6 +119,13 @@ struct ublk_uring_cmd_pdu { u16 tag; }; +struct ublk_batch_io_data { + struct ublk_device *ub; + struct io_uring_cmd *cmd; + struct ublk_batch_io header; + unsigned int issue_flags; +}; + /* * io command is active: sqe cmd is received, and its cqe isn't done * @@ -2687,10 +2699,83 @@ static int ublk_ch_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags) return ublk_ch_uring_cmd_local(cmd, issue_flags); } +static int ublk_check_batch_cmd_flags(const struct ublk_batch_io *uc) +{ + unsigned elem_bytes = sizeof(struct ublk_elem_header); + + if (uc->flags & ~UBLK_BATCH_F_ALL) + return -EINVAL; + + /* UBLK_BATCH_F_AUTO_BUF_REG_FALLBACK requires buffer index */ + if ((uc->flags & UBLK_BATCH_F_AUTO_BUF_REG_FALLBACK) && + (uc->flags & UBLK_BATCH_F_HAS_BUF_ADDR)) + return -EINVAL; + + elem_bytes += (uc->flags & UBLK_BATCH_F_HAS_ZONE_LBA ? sizeof(u64) : 0) + + (uc->flags & UBLK_BATCH_F_HAS_BUF_ADDR ? sizeof(u64) : 0); + if (uc->elem_bytes != elem_bytes) + return -EINVAL; + return 0; +} + +static int ublk_check_batch_cmd(const struct ublk_batch_io_data *data) +{ + + const struct ublk_batch_io *uc = &data->header; + + if (uc->nr_elem > data->ub->dev_info.queue_depth) + return -E2BIG; + + if ((uc->flags & UBLK_BATCH_F_HAS_ZONE_LBA) && + !ublk_dev_is_zoned(data->ub)) + return -EINVAL; + + if ((uc->flags & UBLK_BATCH_F_HAS_BUF_ADDR) && + !ublk_dev_need_map_io(data->ub)) + return -EINVAL; + + if ((uc->flags & UBLK_BATCH_F_AUTO_BUF_REG_FALLBACK) && + !ublk_dev_support_auto_buf_reg(data->ub)) + return -EINVAL; + + return ublk_check_batch_cmd_flags(uc); +} + static int ublk_ch_batch_io_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags) { - return -EOPNOTSUPP; + const struct ublk_batch_io *uc = io_uring_sqe_cmd(cmd->sqe); + struct ublk_device *ub = cmd->file->private_data; + struct ublk_batch_io_data data = { + .ub = ub, + .cmd = cmd, + .header = (struct ublk_batch_io) { + .q_id = READ_ONCE(uc->q_id), + .flags = READ_ONCE(uc->flags), + .nr_elem = READ_ONCE(uc->nr_elem), + .elem_bytes = READ_ONCE(uc->elem_bytes), + }, + .issue_flags = issue_flags, + }; + u32 cmd_op = cmd->cmd_op; + int ret = -EINVAL; + + if (data.header.q_id >= ub->dev_info.nr_hw_queues) + goto out; + + switch (cmd_op) { + case UBLK_U_IO_PREP_IO_CMDS: + case UBLK_U_IO_COMMIT_IO_CMDS: + ret = ublk_check_batch_cmd(&data); + if (ret) + goto out; + ret = -EOPNOTSUPP; + break; + default: + ret = -EOPNOTSUPP; + } +out: + return ret; } static inline bool ublk_check_ubuf_dir(const struct request *req, diff --git a/include/uapi/linux/ublk_cmd.h b/include/uapi/linux/ublk_cmd.h index 90f47da4f435..0cc58e19d401 100644 --- a/include/uapi/linux/ublk_cmd.h +++ b/include/uapi/linux/ublk_cmd.h @@ -103,6 +103,10 @@ _IOWR('u', 0x23, struct ublksrv_io_cmd) #define UBLK_U_IO_UNREGISTER_IO_BUF \ _IOWR('u', 0x24, struct ublksrv_io_cmd) +#define UBLK_U_IO_PREP_IO_CMDS \ + _IOWR('u', 0x25, struct ublk_batch_io) +#define UBLK_U_IO_COMMIT_IO_CMDS \ + _IOWR('u', 0x26, struct ublk_batch_io) /* only ABORT means that no re-fetch */ #define UBLK_IO_RES_OK 0 @@ -544,6 +548,51 @@ struct ublksrv_io_cmd { }; }; +struct ublk_elem_header { + __u16 tag; /* IO tag */ + + /* + * Buffer index for incoming io command, only valid iff + * UBLK_F_AUTO_BUF_REG is set + */ + __u16 buf_index; + __s32 result; /* I/O completion result (commit only) */ +}; + +/* + * uring_cmd buffer structure for batch commands + * + * buffer includes multiple elements, which number is specified by + * `nr_elem`. Each element buffer is organized in the following order: + * + * struct ublk_elem_buffer { + * // Mandatory fields (8 bytes) + * struct ublk_elem_header header; + * + * // Optional fields (8 bytes each, included based on flags) + * + * // Buffer address (if UBLK_BATCH_F_HAS_BUF_ADDR) for copying data + * // between ublk request and ublk server buffer + * __u64 buf_addr; + * + * // returned Zone append LBA (if UBLK_BATCH_F_HAS_ZONE_LBA) + * __u64 zone_lba; + * } + * + * Used for `UBLK_U_IO_PREP_IO_CMDS` and `UBLK_U_IO_COMMIT_IO_CMDS` + */ +struct ublk_batch_io { + __u16 q_id; +#define UBLK_BATCH_F_HAS_ZONE_LBA (1 << 0) +#define UBLK_BATCH_F_HAS_BUF_ADDR (1 << 1) +#define UBLK_BATCH_F_AUTO_BUF_REG_FALLBACK (1 << 2) + __u16 flags; + __u16 nr_elem; + __u8 elem_bytes; + __u8 reserved; + __u64 reserved2; +}; + struct ublk_param_basic { #define UBLK_ATTR_READ_ONLY (1 << 0) #define UBLK_ATTR_ROTATIONAL (1 << 1) -- cgit v1.2.3 From b256795b3606e9a67c725dde8eaae91dd9d21de4 Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Fri, 16 Jan 2026 22:18:37 +0800 Subject: ublk: handle UBLK_U_IO_PREP_IO_CMDS This commit implements the handling of the UBLK_U_IO_PREP_IO_CMDS command, which allows userspace to prepare a batch of I/O requests. The core of this change is the `ublk_walk_cmd_buf` function, which iterates over the elements in the uring_cmd fixed buffer. For each element, it parses the I/O details, finds the corresponding `ublk_io` structure, and prepares it for future dispatch. Add per-io lock for protecting concurrent delivery and committing. Reviewed-by: Caleb Sander Mateos Signed-off-by: Ming Lei Signed-off-by: Jens Axboe --- drivers/block/ublk_drv.c | 191 +++++++++++++++++++++++++++++++++++++++++- include/uapi/linux/ublk_cmd.h | 5 ++ 2 files changed, 195 insertions(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c index 22c7296d90f3..a3840b3f1081 100644 --- a/drivers/block/ublk_drv.c +++ b/drivers/block/ublk_drv.c @@ -208,6 +208,7 @@ struct ublk_io { unsigned task_registered_buffers; void *buf_ctx_handle; + spinlock_t lock; } ____cacheline_aligned_in_smp; struct ublk_queue { @@ -280,6 +281,16 @@ static inline bool ublk_dev_support_batch_io(const struct ublk_device *ub) return false; } +static inline void ublk_io_lock(struct ublk_io *io) +{ + spin_lock(&io->lock); +} + +static inline void ublk_io_unlock(struct ublk_io *io) +{ + spin_unlock(&io->lock); +} + static inline struct ublksrv_io_desc * ublk_get_iod(const struct ublk_queue *ubq, unsigned tag) { @@ -2699,6 +2710,171 @@ static int ublk_ch_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags) return ublk_ch_uring_cmd_local(cmd, issue_flags); } +static inline __u64 ublk_batch_buf_addr(const struct ublk_batch_io *uc, + const struct ublk_elem_header *elem) +{ + const void *buf = elem; + + if (uc->flags & UBLK_BATCH_F_HAS_BUF_ADDR) + return *(const __u64 *)(buf + sizeof(*elem)); + return 0; +} + +static struct ublk_auto_buf_reg +ublk_batch_auto_buf_reg(const struct ublk_batch_io *uc, + const struct ublk_elem_header *elem) +{ + struct ublk_auto_buf_reg reg = { + .index = elem->buf_index, + .flags = (uc->flags & UBLK_BATCH_F_AUTO_BUF_REG_FALLBACK) ? + UBLK_AUTO_BUF_REG_FALLBACK : 0, + }; + + return reg; +} + +/* + * 48 can hold any type of buffer element(8, 16 and 24 bytes) because + * it is the least common multiple(LCM) of 8, 16 and 24 + */ +#define UBLK_CMD_BATCH_TMP_BUF_SZ (48 * 10) +struct ublk_batch_io_iter { + void __user *uaddr; + unsigned done, total; + unsigned char elem_bytes; + /* copy to this buffer from user space */ + unsigned char buf[UBLK_CMD_BATCH_TMP_BUF_SZ]; +}; + +static inline int +__ublk_walk_cmd_buf(struct ublk_queue *ubq, + struct ublk_batch_io_iter *iter, + const struct ublk_batch_io_data *data, + unsigned bytes, + int (*cb)(struct ublk_queue *q, + const struct ublk_batch_io_data *data, + const struct ublk_elem_header *elem)) +{ + unsigned int i; + int ret = 0; + + for (i = 0; i < bytes; i += iter->elem_bytes) { + const struct ublk_elem_header *elem = + (const struct ublk_elem_header *)&iter->buf[i]; + + if (unlikely(elem->tag >= data->ub->dev_info.queue_depth)) { + ret = -EINVAL; + break; + } + + ret = cb(ubq, data, elem); + if (unlikely(ret)) + break; + } + + iter->done += i; + return ret; +} + +static int ublk_walk_cmd_buf(struct ublk_batch_io_iter *iter, + const struct ublk_batch_io_data *data, + int (*cb)(struct ublk_queue *q, + const struct ublk_batch_io_data *data, + const struct ublk_elem_header *elem)) +{ + struct ublk_queue *ubq = ublk_get_queue(data->ub, data->header.q_id); + int ret = 0; + + while (iter->done < iter->total) { + unsigned int len = min(sizeof(iter->buf), iter->total - iter->done); + + if (copy_from_user(iter->buf, iter->uaddr + iter->done, len)) { + pr_warn("ublk%d: read batch cmd buffer failed\n", + data->ub->dev_info.dev_id); + return -EFAULT; + } + + ret = __ublk_walk_cmd_buf(ubq, iter, data, len, cb); + if (ret) + return ret; + } + return 0; +} + +static int ublk_batch_unprep_io(struct ublk_queue *ubq, + const struct ublk_batch_io_data *data, + const struct ublk_elem_header *elem) +{ + struct ublk_io *io = &ubq->ios[elem->tag]; + + data->ub->nr_io_ready--; + ublk_io_lock(io); + io->flags = 0; + ublk_io_unlock(io); + return 0; +} + +static void ublk_batch_revert_prep_cmd(struct ublk_batch_io_iter *iter, + const struct ublk_batch_io_data *data) +{ + int ret; + + /* Re-process only what we've already processed, starting from beginning */ + iter->total = iter->done; + iter->done = 0; + + ret = ublk_walk_cmd_buf(iter, data, ublk_batch_unprep_io); + WARN_ON_ONCE(ret); +} + +static int ublk_batch_prep_io(struct ublk_queue *ubq, + const struct ublk_batch_io_data *data, + const struct ublk_elem_header *elem) +{ + struct ublk_io *io = &ubq->ios[elem->tag]; + const struct ublk_batch_io *uc = &data->header; + union ublk_io_buf buf = { 0 }; + int ret; + + if (ublk_dev_support_auto_buf_reg(data->ub)) + buf.auto_reg = ublk_batch_auto_buf_reg(uc, elem); + else if (ublk_dev_need_map_io(data->ub)) { + buf.addr = ublk_batch_buf_addr(uc, elem); + + ret = ublk_check_fetch_buf(data->ub, buf.addr); + if (ret) + return ret; + } + + ublk_io_lock(io); + ret = __ublk_fetch(data->cmd, data->ub, io); + if (!ret) + io->buf = buf; + ublk_io_unlock(io); + + return ret; +} + +static int ublk_handle_batch_prep_cmd(const struct ublk_batch_io_data *data) +{ + const struct ublk_batch_io *uc = &data->header; + struct io_uring_cmd *cmd = data->cmd; + struct ublk_batch_io_iter iter = { + .uaddr = u64_to_user_ptr(READ_ONCE(cmd->sqe->addr)), + .total = uc->nr_elem * uc->elem_bytes, + .elem_bytes = uc->elem_bytes, + }; + int ret; + + mutex_lock(&data->ub->mutex); + ret = ublk_walk_cmd_buf(&iter, data, ublk_batch_prep_io); + + if (ret && iter.done) + ublk_batch_revert_prep_cmd(&iter, data); + mutex_unlock(&data->ub->mutex); + return ret; +} + static int ublk_check_batch_cmd_flags(const struct ublk_batch_io *uc) { unsigned elem_bytes = sizeof(struct ublk_elem_header); @@ -2765,6 +2941,11 @@ static int ublk_ch_batch_io_uring_cmd(struct io_uring_cmd *cmd, switch (cmd_op) { case UBLK_U_IO_PREP_IO_CMDS: + ret = ublk_check_batch_cmd(&data); + if (ret) + goto out; + ret = ublk_handle_batch_prep_cmd(&data); + break; case UBLK_U_IO_COMMIT_IO_CMDS: ret = ublk_check_batch_cmd(&data); if (ret) @@ -2952,7 +3133,7 @@ static int ublk_init_queue(struct ublk_device *ub, int q_id) struct ublk_queue *ubq; struct page *page; int numa_node; - int size; + int size, i; /* Determine NUMA node based on queue's CPU affinity */ numa_node = ublk_get_queue_numa_node(ub, q_id); @@ -2977,6 +3158,9 @@ static int ublk_init_queue(struct ublk_device *ub, int q_id) } ubq->io_cmd_buf = page_address(page); + for (i = 0; i < ubq->q_depth; i++) + spin_lock_init(&ubq->ios[i].lock); + ub->queues[q_id] = ubq; ubq->dev = ub; return 0; @@ -3220,6 +3404,11 @@ static int ublk_ctrl_start_dev(struct ublk_device *ub, return -EINVAL; mutex_lock(&ub->mutex); + /* device may become not ready in case of F_BATCH */ + if (!ublk_dev_ready(ub)) { + ret = -EINVAL; + goto out_unlock; + } if (ub->dev_info.state == UBLK_S_DEV_LIVE || test_bit(UB_STATE_USED, &ub->state)) { ret = -EEXIST; diff --git a/include/uapi/linux/ublk_cmd.h b/include/uapi/linux/ublk_cmd.h index 0cc58e19d401..1a3d4d33c1d1 100644 --- a/include/uapi/linux/ublk_cmd.h +++ b/include/uapi/linux/ublk_cmd.h @@ -103,6 +103,11 @@ _IOWR('u', 0x23, struct ublksrv_io_cmd) #define UBLK_U_IO_UNREGISTER_IO_BUF \ _IOWR('u', 0x24, struct ublksrv_io_cmd) + +/* + * return 0 if the command is run successfully, otherwise failure code + * is returned + */ #define UBLK_U_IO_PREP_IO_CMDS \ _IOWR('u', 0x25, struct ublk_batch_io) #define UBLK_U_IO_COMMIT_IO_CMDS \ -- cgit v1.2.3 From 1e500e106d5a82280db59dba06f0108085beba65 Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Fri, 16 Jan 2026 22:18:38 +0800 Subject: ublk: handle UBLK_U_IO_COMMIT_IO_CMDS Handle UBLK_U_IO_COMMIT_IO_CMDS by walking the uring_cmd fixed buffer: - read each element into one temp buffer in batch style - parse and apply each element for committing io result Reviewed-by: Caleb Sander Mateos Signed-off-by: Ming Lei Signed-off-by: Jens Axboe --- drivers/block/ublk_drv.c | 103 +++++++++++++++++++++++++++++++++++++++++- include/uapi/linux/ublk_cmd.h | 8 ++++ 2 files changed, 109 insertions(+), 2 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c index a3840b3f1081..162b46c74f16 100644 --- a/drivers/block/ublk_drv.c +++ b/drivers/block/ublk_drv.c @@ -2267,7 +2267,7 @@ static inline int ublk_set_auto_buf_reg(struct ublk_io *io, struct io_uring_cmd return 0; } -static int ublk_handle_auto_buf_reg(struct ublk_io *io, +static void ublk_clear_auto_buf_reg(struct ublk_io *io, struct io_uring_cmd *cmd, u16 *buf_idx) { @@ -2287,7 +2287,13 @@ static int ublk_handle_auto_buf_reg(struct ublk_io *io, if (io->buf_ctx_handle == io_uring_cmd_ctx_handle(cmd)) *buf_idx = io->buf.auto_reg.index; } +} +static int ublk_handle_auto_buf_reg(struct ublk_io *io, + struct io_uring_cmd *cmd, + u16 *buf_idx) +{ + ublk_clear_auto_buf_reg(io, cmd, buf_idx); return ublk_set_auto_buf_reg(io, cmd); } @@ -2720,6 +2726,17 @@ static inline __u64 ublk_batch_buf_addr(const struct ublk_batch_io *uc, return 0; } +static inline __u64 ublk_batch_zone_lba(const struct ublk_batch_io *uc, + const struct ublk_elem_header *elem) +{ + const void *buf = elem; + + if (uc->flags & UBLK_BATCH_F_HAS_ZONE_LBA) + return *(const __u64 *)(buf + sizeof(*elem) + + 8 * !!(uc->flags & UBLK_BATCH_F_HAS_BUF_ADDR)); + return -1; +} + static struct ublk_auto_buf_reg ublk_batch_auto_buf_reg(const struct ublk_batch_io *uc, const struct ublk_elem_header *elem) @@ -2875,6 +2892,84 @@ static int ublk_handle_batch_prep_cmd(const struct ublk_batch_io_data *data) return ret; } +static int ublk_batch_commit_io_check(const struct ublk_queue *ubq, + struct ublk_io *io, + union ublk_io_buf *buf) +{ + if (!(io->flags & UBLK_IO_FLAG_OWNED_BY_SRV)) + return -EBUSY; + + /* BATCH_IO doesn't support UBLK_F_NEED_GET_DATA */ + if (ublk_need_map_io(ubq) && !buf->addr) + return -EINVAL; + return 0; +} + +static int ublk_batch_commit_io(struct ublk_queue *ubq, + const struct ublk_batch_io_data *data, + const struct ublk_elem_header *elem) +{ + struct ublk_io *io = &ubq->ios[elem->tag]; + const struct ublk_batch_io *uc = &data->header; + u16 buf_idx = UBLK_INVALID_BUF_IDX; + union ublk_io_buf buf = { 0 }; + struct request *req = NULL; + bool auto_reg = false; + bool compl = false; + int ret; + + if (ublk_dev_support_auto_buf_reg(data->ub)) { + buf.auto_reg = ublk_batch_auto_buf_reg(uc, elem); + auto_reg = true; + } else if (ublk_dev_need_map_io(data->ub)) + buf.addr = ublk_batch_buf_addr(uc, elem); + + ublk_io_lock(io); + ret = ublk_batch_commit_io_check(ubq, io, &buf); + if (!ret) { + io->res = elem->result; + io->buf = buf; + req = ublk_fill_io_cmd(io, data->cmd); + + if (auto_reg) + ublk_clear_auto_buf_reg(io, data->cmd, &buf_idx); + compl = ublk_need_complete_req(data->ub, io); + } + ublk_io_unlock(io); + + if (unlikely(ret)) { + pr_warn_ratelimited("%s: dev %u queue %u io %u: commit failure %d\n", + __func__, data->ub->dev_info.dev_id, ubq->q_id, + elem->tag, ret); + return ret; + } + + /* can't touch 'ublk_io' any more */ + if (buf_idx != UBLK_INVALID_BUF_IDX) + io_buffer_unregister_bvec(data->cmd, buf_idx, data->issue_flags); + if (req_op(req) == REQ_OP_ZONE_APPEND) + req->__sector = ublk_batch_zone_lba(uc, elem); + if (compl) + __ublk_complete_rq(req, io, ublk_dev_need_map_io(data->ub)); + return 0; +} + +static int ublk_handle_batch_commit_cmd(const struct ublk_batch_io_data *data) +{ + const struct ublk_batch_io *uc = &data->header; + struct io_uring_cmd *cmd = data->cmd; + struct ublk_batch_io_iter iter = { + .uaddr = u64_to_user_ptr(READ_ONCE(cmd->sqe->addr)), + .total = uc->nr_elem * uc->elem_bytes, + .elem_bytes = uc->elem_bytes, + }; + int ret; + + ret = ublk_walk_cmd_buf(&iter, data, ublk_batch_commit_io); + + return iter.done == 0 ? ret : iter.done; +} + static int ublk_check_batch_cmd_flags(const struct ublk_batch_io *uc) { unsigned elem_bytes = sizeof(struct ublk_elem_header); @@ -2950,7 +3045,7 @@ static int ublk_ch_batch_io_uring_cmd(struct io_uring_cmd *cmd, ret = ublk_check_batch_cmd(&data); if (ret) goto out; - ret = -EOPNOTSUPP; + ret = ublk_handle_batch_commit_cmd(&data); break; default: ret = -EOPNOTSUPP; @@ -3659,6 +3754,10 @@ static int ublk_ctrl_add_dev(const struct ublksrv_ctrl_cmd *header) UBLK_F_AUTO_BUF_REG)) ub->dev_info.flags &= ~UBLK_F_NEED_GET_DATA; + /* UBLK_F_BATCH_IO doesn't support GET_DATA */ + if (ublk_dev_support_batch_io(ub)) + ub->dev_info.flags &= ~UBLK_F_NEED_GET_DATA; + /* * Zoned storage support requires reuse `ublksrv_io_cmd->addr` for * returning write_append_lba, which is only allowed in case of diff --git a/include/uapi/linux/ublk_cmd.h b/include/uapi/linux/ublk_cmd.h index 1a3d4d33c1d1..3894d676dd02 100644 --- a/include/uapi/linux/ublk_cmd.h +++ b/include/uapi/linux/ublk_cmd.h @@ -110,6 +110,14 @@ */ #define UBLK_U_IO_PREP_IO_CMDS \ _IOWR('u', 0x25, struct ublk_batch_io) +/* + * If failure code is returned, nothing in the command buffer is handled. + * Otherwise, the returned value means how many bytes in command buffer + * are handled actually, then number of handled IOs can be calculated with + * `elem_bytes` for each IO. IOs in the remained bytes are not committed, + * userspace has to check return value for dealing with partial committing + * correctly. + */ #define UBLK_U_IO_COMMIT_IO_CMDS \ _IOWR('u', 0x26, struct ublk_batch_io) -- cgit v1.2.3 From a4d88375539920b7401ead59d2f944ac23c668ea Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Fri, 16 Jan 2026 22:18:41 +0800 Subject: ublk: add UBLK_U_IO_FETCH_IO_CMDS for batch I/O processing Add UBLK_U_IO_FETCH_IO_CMDS command to enable efficient batch processing of I/O requests. This multishot uring_cmd allows the ublk server to fetch multiple I/O commands in a single operation, significantly reducing submission overhead compared to individual FETCH_REQ* commands. Key Design Features: 1. Multishot Operation: One UBLK_U_IO_FETCH_IO_CMDS can fetch many I/O commands, with the batch size limited by the provided buffer length. 2. Dynamic Load Balancing: Multiple fetch commands can be submitted simultaneously, but only one is active at any time. This enables efficient load distribution across multiple server task contexts. 3. Implicit State Management: The implementation uses three key variables to track state: - evts_fifo: Queue of request tags awaiting processing - fcmd_head: List of available fetch commands - active_fcmd: Currently active fetch command (NULL = none active) States are derived implicitly: - IDLE: No fetch commands available - READY: Fetch commands available, none active - ACTIVE: One fetch command processing events 4. Lockless Reader Optimization: The active fetch command can read from evts_fifo without locking (single reader guarantee), while writers (ublk_queue_rq/ublk_queue_rqs) use evts_lock protection. The memory barrier pairing plays key role for the single lockless reader optimization. Implementation Details: - ublk_queue_rq() and ublk_queue_rqs() save request tags to evts_fifo - __ublk_acquire_fcmd() selects an available fetch command when events arrive and no command is currently active - ublk_batch_dispatch() moves tags from evts_fifo to the fetch command's buffer and posts completion via io_uring_mshot_cmd_post_cqe() - State transitions are coordinated via evts_lock to maintain consistency Reviewed-by: Caleb Sander Mateos Signed-off-by: Ming Lei Signed-off-by: Jens Axboe --- drivers/block/ublk_drv.c | 394 +++++++++++++++++++++++++++++++++++++++++- include/uapi/linux/ublk_cmd.h | 7 + 2 files changed, 393 insertions(+), 8 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c index 1b5721c7a536..0a0210f9d417 100644 --- a/drivers/block/ublk_drv.c +++ b/drivers/block/ublk_drv.c @@ -99,6 +99,7 @@ /* ublk batch fetch uring_cmd */ struct ublk_batch_fetch_cmd { + struct list_head node; struct io_uring_cmd *cmd; unsigned short buf_group; }; @@ -123,7 +124,10 @@ struct ublk_uring_cmd_pdu { */ struct ublk_queue *ubq; - u16 tag; + union { + u16 tag; + struct ublk_batch_fetch_cmd *fcmd; /* batch io only */ + }; }; struct ublk_batch_io_data { @@ -245,10 +249,37 @@ struct ublk_queue { * Make sure just one reader for fetching request from task work * function to ublk server, so no need to grab the lock in reader * side. + * + * Batch I/O State Management: + * + * The batch I/O system uses implicit state management based on the + * combination of three key variables below. + * + * - IDLE: list_empty(&fcmd_head) && !active_fcmd + * No fetch commands available, events queue in evts_fifo + * + * - READY: !list_empty(&fcmd_head) && !active_fcmd + * Fetch commands available but none processing events + * + * - ACTIVE: active_fcmd + * One fetch command actively processing events from evts_fifo + * + * Key Invariants: + * - At most one active_fcmd at any time (single reader) + * - active_fcmd is always from fcmd_head list when non-NULL + * - evts_fifo can be read locklessly by the single active reader + * - All state transitions require evts_lock protection + * - Multiple writers to evts_fifo require lock protection */ struct { DECLARE_KFIFO_PTR(evts_fifo, unsigned short); spinlock_t evts_lock; + + /* List of fetch commands available to process events */ + struct list_head fcmd_head; + + /* Currently active fetch command (NULL = none active) */ + struct ublk_batch_fetch_cmd *active_fcmd; }____cacheline_aligned_in_smp; struct ublk_io ios[] __counted_by(q_depth); @@ -303,12 +334,20 @@ static void ublk_abort_queue(struct ublk_device *ub, struct ublk_queue *ubq); static inline struct request *__ublk_check_and_get_req(struct ublk_device *ub, u16 q_id, u16 tag, struct ublk_io *io); static inline unsigned int ublk_req_build_flags(struct request *req); +static void ublk_batch_dispatch(struct ublk_queue *ubq, + const struct ublk_batch_io_data *data, + struct ublk_batch_fetch_cmd *fcmd); static inline bool ublk_dev_support_batch_io(const struct ublk_device *ub) { return false; } +static inline bool ublk_support_batch_io(const struct ublk_queue *ubq) +{ + return false; +} + static inline void ublk_io_lock(struct ublk_io *io) { spin_lock(&io->lock); @@ -664,13 +703,45 @@ static wait_queue_head_t ublk_idr_wq; /* wait until one idr is freed */ static DEFINE_MUTEX(ublk_ctl_mutex); +static struct ublk_batch_fetch_cmd * +ublk_batch_alloc_fcmd(struct io_uring_cmd *cmd) +{ + struct ublk_batch_fetch_cmd *fcmd = kzalloc(sizeof(*fcmd), GFP_NOIO); + + if (fcmd) { + fcmd->cmd = cmd; + fcmd->buf_group = READ_ONCE(cmd->sqe->buf_index); + } + return fcmd; +} + +static void ublk_batch_free_fcmd(struct ublk_batch_fetch_cmd *fcmd) +{ + kfree(fcmd); +} + +static void __ublk_release_fcmd(struct ublk_queue *ubq) +{ + WRITE_ONCE(ubq->active_fcmd, NULL); +} -static void ublk_batch_deinit_fetch_buf(const struct ublk_batch_io_data *data, +/* + * Nothing can move on, so clear ->active_fcmd, and the caller should stop + * dispatching + */ +static void ublk_batch_deinit_fetch_buf(struct ublk_queue *ubq, + const struct ublk_batch_io_data *data, struct ublk_batch_fetch_cmd *fcmd, int res) { + spin_lock(&ubq->evts_lock); + list_del(&fcmd->node); + WARN_ON_ONCE(fcmd != ubq->active_fcmd); + __ublk_release_fcmd(ubq); + spin_unlock(&ubq->evts_lock); + io_uring_cmd_done(fcmd->cmd, res, data->issue_flags); - fcmd->cmd = NULL; + ublk_batch_free_fcmd(fcmd); } static int ublk_batch_fetch_post_cqe(struct ublk_batch_fetch_cmd *fcmd, @@ -1637,6 +1708,8 @@ static int __ublk_batch_dispatch(struct ublk_queue *ubq, bool needs_filter; int ret; + WARN_ON_ONCE(data->cmd != fcmd->cmd); + sel = io_uring_cmd_buffer_select(fcmd->cmd, fcmd->buf_group, &len, data->issue_flags); if (sel.val < 0) @@ -1700,21 +1773,93 @@ static int __ublk_batch_dispatch(struct ublk_queue *ubq, return ret; } -static __maybe_unused void +static struct ublk_batch_fetch_cmd *__ublk_acquire_fcmd( + struct ublk_queue *ubq) +{ + struct ublk_batch_fetch_cmd *fcmd; + + lockdep_assert_held(&ubq->evts_lock); + + /* + * Ordering updating ubq->evts_fifo and checking ubq->active_fcmd. + * + * The pair is the smp_mb() in ublk_batch_dispatch(). + * + * If ubq->active_fcmd is observed as non-NULL, the new added tags + * can be visisible in ublk_batch_dispatch() with the barrier pairing. + */ + smp_mb(); + if (READ_ONCE(ubq->active_fcmd)) { + fcmd = NULL; + } else { + fcmd = list_first_entry_or_null(&ubq->fcmd_head, + struct ublk_batch_fetch_cmd, node); + WRITE_ONCE(ubq->active_fcmd, fcmd); + } + return fcmd; +} + +static void ublk_batch_tw_cb(struct io_tw_req tw_req, io_tw_token_t tw) +{ + unsigned int issue_flags = IO_URING_CMD_TASK_WORK_ISSUE_FLAGS; + struct io_uring_cmd *cmd = io_uring_cmd_from_tw(tw_req); + struct ublk_uring_cmd_pdu *pdu = ublk_get_uring_cmd_pdu(cmd); + struct ublk_batch_fetch_cmd *fcmd = pdu->fcmd; + struct ublk_batch_io_data data = { + .ub = pdu->ubq->dev, + .cmd = fcmd->cmd, + .issue_flags = issue_flags, + }; + + WARN_ON_ONCE(pdu->ubq->active_fcmd != fcmd); + + ublk_batch_dispatch(pdu->ubq, &data, fcmd); +} + +static void ublk_batch_dispatch(struct ublk_queue *ubq, const struct ublk_batch_io_data *data, struct ublk_batch_fetch_cmd *fcmd) { + struct ublk_batch_fetch_cmd *new_fcmd; + unsigned tried = 0; int ret = 0; +again: while (!ublk_io_evts_empty(ubq)) { ret = __ublk_batch_dispatch(ubq, data, fcmd); if (ret <= 0) break; } - if (ret < 0) - ublk_batch_deinit_fetch_buf(data, fcmd, ret); + if (ret < 0) { + ublk_batch_deinit_fetch_buf(ubq, data, fcmd, ret); + return; + } + + __ublk_release_fcmd(ubq); + /* + * Order clearing ubq->active_fcmd from __ublk_release_fcmd() and + * checking ubq->evts_fifo. + * + * The pair is the smp_mb() in __ublk_acquire_fcmd(). + */ + smp_mb(); + if (likely(ublk_io_evts_empty(ubq))) + return; + + spin_lock(&ubq->evts_lock); + new_fcmd = __ublk_acquire_fcmd(ubq); + spin_unlock(&ubq->evts_lock); + + if (!new_fcmd) + return; + + /* Avoid lockup by allowing to handle at most 32 batches */ + if (new_fcmd == fcmd && tried++ < 32) + goto again; + + io_uring_cmd_complete_in_task(new_fcmd->cmd, ublk_batch_tw_cb); } static void ublk_cmd_tw_cb(struct io_tw_req tw_req, io_tw_token_t tw) @@ -1726,6 +1871,21 @@ static void ublk_cmd_tw_cb(struct io_tw_req tw_req, io_tw_token_t tw) ublk_dispatch_req(ubq, pdu->req); } +static void ublk_batch_queue_cmd(struct ublk_queue *ubq, struct request *rq, bool last) +{ + unsigned short tag = rq->tag; + struct ublk_batch_fetch_cmd *fcmd = NULL; + + spin_lock(&ubq->evts_lock); + kfifo_put(&ubq->evts_fifo, tag); + if (last) + fcmd = __ublk_acquire_fcmd(ubq); + spin_unlock(&ubq->evts_lock); + + if (fcmd) + io_uring_cmd_complete_in_task(fcmd->cmd, ublk_batch_tw_cb); +} + static void ublk_queue_cmd(struct ublk_queue *ubq, struct request *rq) { struct io_uring_cmd *cmd = ubq->ios[rq->tag].cmd; @@ -1836,7 +1996,10 @@ static blk_status_t ublk_queue_rq(struct blk_mq_hw_ctx *hctx, return BLK_STS_OK; } - ublk_queue_cmd(ubq, rq); + if (ublk_support_batch_io(ubq)) + ublk_batch_queue_cmd(ubq, rq, bd->last); + else + ublk_queue_cmd(ubq, rq); return BLK_STS_OK; } @@ -1848,6 +2011,19 @@ static inline bool ublk_belong_to_same_batch(const struct ublk_io *io, (io->task == io2->task); } +static void ublk_commit_rqs(struct blk_mq_hw_ctx *hctx) +{ + struct ublk_queue *ubq = hctx->driver_data; + struct ublk_batch_fetch_cmd *fcmd; + + spin_lock(&ubq->evts_lock); + fcmd = __ublk_acquire_fcmd(ubq); + spin_unlock(&ubq->evts_lock); + + if (fcmd) + io_uring_cmd_complete_in_task(fcmd->cmd, ublk_batch_tw_cb); +} + static void ublk_queue_rqs(struct rq_list *rqlist) { struct rq_list requeue_list = { }; @@ -1876,6 +2052,57 @@ static void ublk_queue_rqs(struct rq_list *rqlist) *rqlist = requeue_list; } +static void ublk_batch_queue_cmd_list(struct ublk_queue *ubq, struct rq_list *l) +{ + unsigned short tags[MAX_NR_TAG]; + struct ublk_batch_fetch_cmd *fcmd; + struct request *rq; + unsigned cnt = 0; + + spin_lock(&ubq->evts_lock); + rq_list_for_each(l, rq) { + tags[cnt++] = (unsigned short)rq->tag; + if (cnt >= MAX_NR_TAG) { + kfifo_in(&ubq->evts_fifo, tags, cnt); + cnt = 0; + } + } + if (cnt) + kfifo_in(&ubq->evts_fifo, tags, cnt); + fcmd = __ublk_acquire_fcmd(ubq); + spin_unlock(&ubq->evts_lock); + + rq_list_init(l); + if (fcmd) + io_uring_cmd_complete_in_task(fcmd->cmd, ublk_batch_tw_cb); +} + +static void ublk_batch_queue_rqs(struct rq_list *rqlist) +{ + struct rq_list requeue_list = { }; + struct rq_list submit_list = { }; + struct ublk_queue *ubq = NULL; + struct request *req; + + while ((req = rq_list_pop(rqlist))) { + struct ublk_queue *this_q = req->mq_hctx->driver_data; + + if (ublk_prep_req(this_q, req, true) != BLK_STS_OK) { + rq_list_add_tail(&requeue_list, req); + continue; + } + + if (ubq && this_q != ubq && !rq_list_empty(&submit_list)) + ublk_batch_queue_cmd_list(ubq, &submit_list); + ubq = this_q; + rq_list_add_tail(&submit_list, req); + } + + if (!rq_list_empty(&submit_list)) + ublk_batch_queue_cmd_list(ubq, &submit_list); + *rqlist = requeue_list; +} + static int ublk_init_hctx(struct blk_mq_hw_ctx *hctx, void *driver_data, unsigned int hctx_idx) { @@ -1893,6 +2120,14 @@ static const struct blk_mq_ops ublk_mq_ops = { .timeout = ublk_timeout, }; +static const struct blk_mq_ops ublk_batch_mq_ops = { + .commit_rqs = ublk_commit_rqs, + .queue_rq = ublk_queue_rq, + .queue_rqs = ublk_batch_queue_rqs, + .init_hctx = ublk_init_hctx, + .timeout = ublk_timeout, +}; + static void ublk_queue_reinit(struct ublk_device *ub, struct ublk_queue *ubq) { int i; @@ -2290,6 +2525,56 @@ static void ublk_cancel_cmd(struct ublk_queue *ubq, unsigned tag, io_uring_cmd_done(io->cmd, UBLK_IO_RES_ABORT, issue_flags); } +static void ublk_batch_cancel_cmd(struct ublk_queue *ubq, + struct ublk_batch_fetch_cmd *fcmd, + unsigned int issue_flags) +{ + bool done; + + spin_lock(&ubq->evts_lock); + done = (READ_ONCE(ubq->active_fcmd) != fcmd); + if (done) + list_del(&fcmd->node); + spin_unlock(&ubq->evts_lock); + + if (done) { + io_uring_cmd_done(fcmd->cmd, UBLK_IO_RES_ABORT, issue_flags); + ublk_batch_free_fcmd(fcmd); + } +} + +static void ublk_batch_cancel_queue(struct ublk_queue *ubq) +{ + struct ublk_batch_fetch_cmd *fcmd; + LIST_HEAD(fcmd_list); + + spin_lock(&ubq->evts_lock); + ubq->force_abort = true; + list_splice_init(&ubq->fcmd_head, &fcmd_list); + fcmd = READ_ONCE(ubq->active_fcmd); + if (fcmd) + list_move(&fcmd->node, &ubq->fcmd_head); + spin_unlock(&ubq->evts_lock); + + while (!list_empty(&fcmd_list)) { + fcmd = list_first_entry(&fcmd_list, + struct ublk_batch_fetch_cmd, node); + ublk_batch_cancel_cmd(ubq, fcmd, IO_URING_F_UNLOCKED); + } +} + +static void ublk_batch_cancel_fn(struct io_uring_cmd *cmd, + unsigned int issue_flags) +{ + struct ublk_uring_cmd_pdu *pdu = ublk_get_uring_cmd_pdu(cmd); + struct ublk_batch_fetch_cmd *fcmd = pdu->fcmd; + struct ublk_queue *ubq = pdu->ubq; + + ublk_start_cancel(ubq->dev); + + ublk_batch_cancel_cmd(ubq, fcmd, issue_flags); +} + /* * The ublk char device won't be closed when calling cancel fn, so both * ublk device and queue are guaranteed to be live @@ -2341,6 +2626,11 @@ static void ublk_cancel_queue(struct ublk_queue *ubq) { int i; + if (ublk_support_batch_io(ubq)) { + ublk_batch_cancel_queue(ubq); + return; + } + for (i = 0; i < ubq->q_depth; i++) ublk_cancel_cmd(ubq, i, IO_URING_F_UNLOCKED); } @@ -3246,6 +3536,79 @@ static int ublk_check_batch_cmd(const struct ublk_batch_io_data *data) return ublk_check_batch_cmd_flags(uc); } +static int ublk_batch_attach(struct ublk_queue *ubq, + struct ublk_batch_io_data *data, + struct ublk_batch_fetch_cmd *fcmd) +{ + struct ublk_batch_fetch_cmd *new_fcmd = NULL; + bool free = false; + struct ublk_uring_cmd_pdu *pdu = ublk_get_uring_cmd_pdu(data->cmd); + + spin_lock(&ubq->evts_lock); + if (unlikely(ubq->force_abort || ubq->canceling)) { + free = true; + } else { + list_add_tail(&fcmd->node, &ubq->fcmd_head); + new_fcmd = __ublk_acquire_fcmd(ubq); + } + spin_unlock(&ubq->evts_lock); + + if (unlikely(free)) { + ublk_batch_free_fcmd(fcmd); + return -ENODEV; + } + + pdu->ubq = ubq; + pdu->fcmd = fcmd; + io_uring_cmd_mark_cancelable(fcmd->cmd, data->issue_flags); + + if (!new_fcmd) + goto out; + + /* + * If the two fetch commands are originated from same io_ring_ctx, + * run batch dispatch directly. Otherwise, schedule task work for + * doing it. + */ + if (io_uring_cmd_ctx_handle(new_fcmd->cmd) == + io_uring_cmd_ctx_handle(fcmd->cmd)) { + data->cmd = new_fcmd->cmd; + ublk_batch_dispatch(ubq, data, new_fcmd); + } else { + io_uring_cmd_complete_in_task(new_fcmd->cmd, + ublk_batch_tw_cb); + } +out: + return -EIOCBQUEUED; +} + +static int ublk_handle_batch_fetch_cmd(struct ublk_batch_io_data *data) +{ + struct ublk_queue *ubq = ublk_get_queue(data->ub, data->header.q_id); + struct ublk_batch_fetch_cmd *fcmd = ublk_batch_alloc_fcmd(data->cmd); + + if (!fcmd) + return -ENOMEM; + + return ublk_batch_attach(ubq, data, fcmd); +} + +static int ublk_validate_batch_fetch_cmd(struct ublk_batch_io_data *data) +{ + const struct ublk_batch_io *uc = &data->header; + + if (!(data->cmd->flags & IORING_URING_CMD_MULTISHOT)) + return -EINVAL; + + if (uc->elem_bytes != sizeof(__u16)) + return -EINVAL; + + if (uc->flags != 0) + return -EINVAL; + + return 0; +} + static int ublk_ch_batch_io_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags) { @@ -3265,6 +3628,11 @@ static int ublk_ch_batch_io_uring_cmd(struct io_uring_cmd *cmd, u32 cmd_op = cmd->cmd_op; int ret = -EINVAL; + if (unlikely(issue_flags & IO_URING_F_CANCEL)) { + ublk_batch_cancel_fn(cmd, issue_flags); + return 0; + } + if (data.header.q_id >= ub->dev_info.nr_hw_queues) goto out; @@ -3281,6 +3649,12 @@ static int ublk_ch_batch_io_uring_cmd(struct io_uring_cmd *cmd, goto out; ret = ublk_handle_batch_commit_cmd(&data); break; + case UBLK_U_IO_FETCH_IO_CMDS: + ret = ublk_validate_batch_fetch_cmd(&data); + if (ret) + goto out; + ret = ublk_handle_batch_fetch_cmd(&data); + break; default: ret = -EOPNOTSUPP; } @@ -3503,6 +3877,7 @@ static int ublk_init_queue(struct ublk_device *ub, int q_id) ret = ublk_io_evts_init(ubq, ubq->q_depth, numa_node); if (ret) goto fail; + INIT_LIST_HEAD(&ubq->fcmd_head); } ub->queues[q_id] = ubq; ubq->dev = ub; @@ -3625,7 +4000,10 @@ static void ublk_align_max_io_size(struct ublk_device *ub) static int ublk_add_tag_set(struct ublk_device *ub) { - ub->tag_set.ops = &ublk_mq_ops; + if (ublk_dev_support_batch_io(ub)) + ub->tag_set.ops = &ublk_batch_mq_ops; + else + ub->tag_set.ops = &ublk_mq_ops; ub->tag_set.nr_hw_queues = ub->dev_info.nr_hw_queues; ub->tag_set.queue_depth = ub->dev_info.queue_depth; ub->tag_set.numa_node = NUMA_NO_NODE; diff --git a/include/uapi/linux/ublk_cmd.h b/include/uapi/linux/ublk_cmd.h index 3894d676dd02..70d8ebbf4326 100644 --- a/include/uapi/linux/ublk_cmd.h +++ b/include/uapi/linux/ublk_cmd.h @@ -121,6 +121,13 @@ #define UBLK_U_IO_COMMIT_IO_CMDS \ _IOWR('u', 0x26, struct ublk_batch_io) +/* + * Fetch io commands to provided buffer in multishot style, + * `IORING_URING_CMD_MULTISHOT` is required for this command. + */ +#define UBLK_U_IO_FETCH_IO_CMDS \ + _IOWR('u', 0x27, struct ublk_batch_io) + /* only ABORT means that no re-fetch */ #define UBLK_IO_RES_OK 0 #define UBLK_IO_RES_NEED_GET_DATA 1 -- cgit v1.2.3 From e2723e6ce6025026b6d79d9a00048386a69e00c3 Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Fri, 16 Jan 2026 22:18:44 +0800 Subject: ublk: add new feature UBLK_F_BATCH_IO Add new feature UBLK_F_BATCH_IO which replaces the following two per-io commands: - UBLK_U_IO_FETCH_REQ - UBLK_U_IO_COMMIT_AND_FETCH_REQ with three per-queue batch io uring_cmd: - UBLK_U_IO_PREP_IO_CMDS - UBLK_U_IO_COMMIT_IO_CMDS - UBLK_U_IO_FETCH_IO_CMDS Then ublk can deliver batch io commands to ublk server in single multishort uring_cmd, also allows to prepare & commit multiple commands in batch style via single uring_cmd, communication cost is reduced a lot. This feature also doesn't limit task context any more for all supported commands, so any allowed uring_cmd can be issued in any task context. ublk server implementation becomes much easier. Meantime load balance becomes much easier to support with this feature. The command `UBLK_U_IO_FETCH_IO_CMDS` can be issued from multiple task contexts, so each task can adjust this command's buffer length or number of inflight commands for controlling how much load is handled by current task. Later, priority parameter will be added to command `UBLK_U_IO_FETCH_IO_CMDS` for improving load balance support. UBLK_U_IO_NEED_GET_DATA isn't supported in batch io yet, but it may be enabled in future via its batch pair. Reviewed-by: Caleb Sander Mateos Signed-off-by: Ming Lei Signed-off-by: Jens Axboe --- drivers/block/ublk_drv.c | 60 +++++++++++++++++++++++++++++++++++++------ include/uapi/linux/ublk_cmd.h | 15 +++++++++++ 2 files changed, 67 insertions(+), 8 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c index 564cf44c238f..bec34b5ab5ab 100644 --- a/drivers/block/ublk_drv.c +++ b/drivers/block/ublk_drv.c @@ -79,7 +79,8 @@ | UBLK_F_PER_IO_DAEMON \ | UBLK_F_BUF_REG_OFF_DAEMON \ | (IS_ENABLED(CONFIG_BLK_DEV_INTEGRITY) ? UBLK_F_INTEGRITY : 0) \ - | UBLK_F_SAFE_STOP_DEV) + | UBLK_F_SAFE_STOP_DEV \ + | UBLK_F_BATCH_IO) #define UBLK_F_ALL_RECOVERY_FLAGS (UBLK_F_USER_RECOVERY \ | UBLK_F_USER_RECOVERY_REISSUE \ @@ -340,12 +341,12 @@ static void ublk_batch_dispatch(struct ublk_queue *ubq, static inline bool ublk_dev_support_batch_io(const struct ublk_device *ub) { - return false; + return ub->dev_info.flags & UBLK_F_BATCH_IO; } static inline bool ublk_support_batch_io(const struct ublk_queue *ubq) { - return false; + return ubq->flags & UBLK_F_BATCH_IO; } static inline void ublk_io_lock(struct ublk_io *io) @@ -3573,9 +3574,11 @@ static int ublk_check_batch_cmd_flags(const struct ublk_batch_io *uc) static int ublk_check_batch_cmd(const struct ublk_batch_io_data *data) { - const struct ublk_batch_io *uc = &data->header; + if (uc->q_id >= data->ub->dev_info.nr_hw_queues) + return -EINVAL; + if (uc->nr_elem > data->ub->dev_info.queue_depth) return -E2BIG; @@ -3655,6 +3658,9 @@ static int ublk_validate_batch_fetch_cmd(struct ublk_batch_io_data *data) { const struct ublk_batch_io *uc = &data->header; + if (uc->q_id >= data->ub->dev_info.nr_hw_queues) + return -EINVAL; + if (!(data->cmd->flags & IORING_URING_CMD_MULTISHOT)) return -EINVAL; @@ -3667,6 +3673,35 @@ static int ublk_validate_batch_fetch_cmd(struct ublk_batch_io_data *data) return 0; } +static int ublk_handle_non_batch_cmd(struct io_uring_cmd *cmd, + unsigned int issue_flags) +{ + const struct ublksrv_io_cmd *ub_cmd = io_uring_sqe_cmd(cmd->sqe); + struct ublk_device *ub = cmd->file->private_data; + unsigned tag = READ_ONCE(ub_cmd->tag); + unsigned q_id = READ_ONCE(ub_cmd->q_id); + unsigned index = READ_ONCE(ub_cmd->addr); + struct ublk_queue *ubq; + struct ublk_io *io; + + if (cmd->cmd_op == UBLK_U_IO_UNREGISTER_IO_BUF) + return ublk_unregister_io_buf(cmd, ub, index, issue_flags); + + if (q_id >= ub->dev_info.nr_hw_queues) + return -EINVAL; + + if (tag >= ub->dev_info.queue_depth) + return -EINVAL; + + if (cmd->cmd_op != UBLK_U_IO_REGISTER_IO_BUF) + return -EOPNOTSUPP; + + ubq = ublk_get_queue(ub, q_id); + io = &ubq->ios[tag]; + return ublk_register_io_buf(cmd, ub, q_id, tag, io, index, + issue_flags); +} + static int ublk_ch_batch_io_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags) { @@ -3691,9 +3726,6 @@ static int ublk_ch_batch_io_uring_cmd(struct io_uring_cmd *cmd, return 0; } - if (data.header.q_id >= ub->dev_info.nr_hw_queues) - goto out; - switch (cmd_op) { case UBLK_U_IO_PREP_IO_CMDS: ret = ublk_check_batch_cmd(&data); @@ -3714,7 +3746,8 @@ static int ublk_ch_batch_io_uring_cmd(struct io_uring_cmd *cmd, ret = ublk_handle_batch_fetch_cmd(&data); break; default: - ret = -EOPNOTSUPP; + ret = ublk_handle_non_batch_cmd(cmd, issue_flags); + break; } out: return ret; @@ -4437,6 +4470,10 @@ static int ublk_ctrl_add_dev(const struct ublksrv_ctrl_cmd *header) UBLK_F_BUF_REG_OFF_DAEMON | UBLK_F_SAFE_STOP_DEV; + /* So far, UBLK_F_PER_IO_DAEMON won't be exposed for BATCH_IO */ + if (ublk_dev_support_batch_io(ub)) + ub->dev_info.flags &= ~UBLK_F_PER_IO_DAEMON; + /* GET_DATA isn't needed any more with USER_COPY or ZERO COPY */ if (ub->dev_info.flags & (UBLK_F_USER_COPY | UBLK_F_SUPPORT_ZERO_COPY | UBLK_F_AUTO_BUF_REG)) @@ -4820,6 +4857,13 @@ static int ublk_wait_for_idle_io(struct ublk_device *ub, unsigned int elapsed = 0; int ret; + /* + * For UBLK_F_BATCH_IO ublk server can get notified with existing + * or new fetch command, so needn't wait any more + */ + if (ublk_dev_support_batch_io(ub)) + return 0; + while (elapsed < timeout_ms && !signal_pending(current)) { unsigned int queues_cancelable = 0; int i; diff --git a/include/uapi/linux/ublk_cmd.h b/include/uapi/linux/ublk_cmd.h index 70d8ebbf4326..743d31491387 100644 --- a/include/uapi/linux/ublk_cmd.h +++ b/include/uapi/linux/ublk_cmd.h @@ -340,6 +340,21 @@ */ #define UBLK_F_BUF_REG_OFF_DAEMON (1ULL << 14) +/* + * Support the following commands for delivering & committing io command + * in batch. + * + * - UBLK_U_IO_PREP_IO_CMDS + * - UBLK_U_IO_COMMIT_IO_CMDS + * - UBLK_U_IO_FETCH_IO_CMDS + * - UBLK_U_IO_REGISTER_IO_BUF + * - UBLK_U_IO_UNREGISTER_IO_BUF + * + * The existing UBLK_U_IO_FETCH_REQ, UBLK_U_IO_COMMIT_AND_FETCH_REQ and + * UBLK_U_IO_NEED_GET_DATA uring_cmd are not supported for this feature. + */ +#define UBLK_F_BATCH_IO (1ULL << 15) + /* * ublk device supports requests with integrity/metadata buffer. * Requires UBLK_F_USER_COPY. -- cgit v1.2.3 From fa9893fadbc245e179cb17f3c371c67471b5a8a8 Mon Sep 17 00:00:00 2001 From: Michael Roth Date: Fri, 9 Jan 2026 17:17:32 -0600 Subject: KVM: Introduce KVM_EXIT_SNP_REQ_CERTS for SNP certificate-fetching For SEV-SNP, the host can optionally provide a certificate table to the guest when it issues an attestation request to firmware (see GHCB 2.0 specification regarding "SNP Extended Guest Requests"). This certificate table can then be used to verify the endorsement key used by firmware to sign the attestation report. While it is possible for guests to obtain the certificates through other means, handling it via the host provides more flexibility in being able to keep the certificate data in sync with the endorsement key throughout host-side operations that might resulting in the endorsement key changing. In the case of KVM, userspace will be responsible for fetching the certificate table and keeping it in sync with any modifications to the endorsement key by other userspace management tools. Define a new KVM_EXIT_SNP_REQ_CERTS event where userspace is provided with the GPA of the buffer the guest has provided as part of the attestation request so that userspace can write the certificate data into it while relying on filesystem-based locking to keep the certificates up-to-date relative to the endorsement keys installed/utilized by firmware at the time the certificates are fetched. [Melody: Update the documentation scheme about how file locking is expected to happen.] Reviewed-by: Liam Merwick Tested-by: Liam Merwick Tested-by: Dionna Glaze Signed-off-by: Michael Roth Signed-off-by: Melody Wang Signed-off-by: Michael Roth Link: https://patch.msgid.link/20260109231732.1160759-2-michael.roth@amd.com Signed-off-by: Sean Christopherson --- Documentation/virt/kvm/api.rst | 44 ++++++++++++++++++++++++++++++ arch/x86/kvm/svm/sev.c | 62 ++++++++++++++++++++++++++++++++++++++---- arch/x86/kvm/svm/svm.h | 1 + include/uapi/linux/kvm.h | 9 ++++++ 4 files changed, 110 insertions(+), 6 deletions(-) (limited to 'include/uapi/linux') diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst index 01a3abef8abb..428d7d9cb4d6 100644 --- a/Documentation/virt/kvm/api.rst +++ b/Documentation/virt/kvm/api.rst @@ -7353,6 +7353,50 @@ Please note that the kernel is allowed to use the kvm_run structure as the primary storage for certain register types. Therefore, the kernel may use the values in kvm_run even if the corresponding bit in kvm_dirty_regs is not set. +:: + + /* KVM_EXIT_SNP_REQ_CERTS */ + struct kvm_exit_snp_req_certs { + __u64 gpa; + __u64 npages; + __u64 ret; + }; + +KVM_EXIT_SNP_REQ_CERTS indicates an SEV-SNP guest with certificate-fetching +enabled (see KVM_SEV_SNP_ENABLE_REQ_CERTS) has generated an Extended Guest +Request NAE #VMGEXIT (SNP_GUEST_REQUEST) with message type MSG_REPORT_REQ, +i.e. has requested an attestation report from firmware, and would like the +certificate data corresponding to the attestation report signature to be +provided by the hypervisor as part of the request. + +To allow for userspace to provide the certificate, the 'gpa' and 'npages' +are forwarded verbatim from the guest request (the RAX and RBX GHCB fields +respectively). 'ret' is not an "output" from KVM, and is always '0' on +exit. KVM verifies the 'gpa' is 4KiB aligned prior to exiting to userspace, +but otherwise the information from the guest isn't validated. + +Upon the next KVM_RUN, e.g. after userspace has serviced the request (or not), +KVM will complete the #VMGEXIT, using the 'ret' field to determine whether to +signal success or failure to the guest, and on failure, what reason code will +be communicated via SW_EXITINFO2. If 'ret' is set to an unsupported value (see +the table below), KVM_RUN will fail with -EINVAL. For a 'ret' of 'ENOSPC', KVM +also consumes the 'npages' field, i.e. userspace can use the field to inform +the guest of the number of pages needed to hold all the certificate data. + +The supported 'ret' values and their respective SW_EXITINFO2 encodings: + + ====== ============================================================= + 0 0x0, i.e. success. KVM will emit an SNP_GUEST_REQUEST command + to SNP firmware. + ENOSPC 0x0000000100000000, i.e. not enough guest pages to hold the + certificate table and certificate data. KVM will also set the + RBX field in the GHBC to 'npages'. + EAGAIN 0x0000000200000000, i.e. the host is busy and the guest should + retry the request. + EIO 0xffffffff00000000, for all other errors (this return code is + a KVM-defined hypervisor value, as allowed by the GHCB) + ====== ============================================================= + .. _cap_enable: diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c index f67525007089..9e6a78e448f2 100644 --- a/arch/x86/kvm/svm/sev.c +++ b/arch/x86/kvm/svm/sev.c @@ -41,6 +41,16 @@ #define GHCB_HV_FT_SUPPORTED (GHCB_HV_FT_SNP | GHCB_HV_FT_SNP_AP_CREATION) +/* + * The GHCB spec essentially states that all non-zero error codes other than + * those explicitly defined above should be treated as an error by the guest. + * Define a generic error to cover that case, and choose a value that is not + * likely to overlap with new explicit error codes should more be added to + * the GHCB spec later. KVM will use this to report generic errors when + * handling SNP guest requests. + */ +#define SNP_GUEST_VMM_ERR_GENERIC (~0U) + /* enable/disable SEV support */ static bool sev_enabled = true; module_param_named(sev, sev_enabled, bool, 0444); @@ -4139,6 +4149,36 @@ out_unlock: return ret; } +static int snp_req_certs_err(struct vcpu_svm *svm, u32 vmm_error) +{ + ghcb_set_sw_exit_info_2(svm->sev_es.ghcb, SNP_GUEST_ERR(vmm_error, 0)); + + return 1; /* resume guest */ +} + +static int snp_complete_req_certs(struct kvm_vcpu *vcpu) +{ + struct vcpu_svm *svm = to_svm(vcpu); + struct vmcb_control_area *control = &svm->vmcb->control; + + switch (READ_ONCE(vcpu->run->snp_req_certs.ret)) { + case 0: + return snp_handle_guest_req(svm, control->exit_info_1, + control->exit_info_2); + case ENOSPC: + vcpu->arch.regs[VCPU_REGS_RBX] = vcpu->run->snp_req_certs.npages; + return snp_req_certs_err(svm, SNP_GUEST_VMM_ERR_INVALID_LEN); + case EAGAIN: + return snp_req_certs_err(svm, SNP_GUEST_VMM_ERR_BUSY); + case EIO: + return snp_req_certs_err(svm, SNP_GUEST_VMM_ERR_GENERIC); + default: + break; + } + + return -EINVAL; +} + static int snp_handle_ext_guest_req(struct vcpu_svm *svm, gpa_t req_gpa, gpa_t resp_gpa) { struct kvm *kvm = svm->vcpu.kvm; @@ -4154,14 +4194,15 @@ static int snp_handle_ext_guest_req(struct vcpu_svm *svm, gpa_t req_gpa, gpa_t r /* * As per GHCB spec, requests of type MSG_REPORT_REQ also allow for * additional certificate data to be provided alongside the attestation - * report via the guest-provided data pages indicated by RAX/RBX. The - * certificate data is optional and requires additional KVM enablement - * to provide an interface for userspace to provide it, but KVM still - * needs to be able to handle extended guest requests either way. So - * provide a stub implementation that will always return an empty - * certificate table in the guest-provided data pages. + * report via the guest-provided data pages indicated by RAX/RBX. If + * userspace enables KVM_EXIT_SNP_REQ_CERTS, then exit to userspace + * to give userspace an opportunity to provide the certificate data + * before issuing/completing the attestation request. Otherwise, return + * an empty certificate table in the guest-provided data pages and + * handle the attestation request immediately. */ if (msg_type == SNP_MSG_REPORT_REQ) { + struct kvm_sev_info *sev = &to_kvm_svm(kvm)->sev_info; struct kvm_vcpu *vcpu = &svm->vcpu; u64 data_npages; gpa_t data_gpa; @@ -4175,6 +4216,15 @@ static int snp_handle_ext_guest_req(struct vcpu_svm *svm, gpa_t req_gpa, gpa_t r if (!PAGE_ALIGNED(data_gpa)) goto request_invalid; + if (sev->snp_certs_enabled) { + vcpu->run->exit_reason = KVM_EXIT_SNP_REQ_CERTS; + vcpu->run->snp_req_certs.gpa = data_gpa; + vcpu->run->snp_req_certs.npages = data_npages; + vcpu->run->snp_req_certs.ret = 0; + vcpu->arch.complete_userspace_io = snp_complete_req_certs; + return 0; + } + /* * As per GHCB spec (see "SNP Extended Guest Request"), the * certificate table is terminated by 24-bytes of zeroes. diff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h index 338fc4f5cc4c..ebd7b36b1ceb 100644 --- a/arch/x86/kvm/svm/svm.h +++ b/arch/x86/kvm/svm/svm.h @@ -115,6 +115,7 @@ struct kvm_sev_info { void *guest_resp_buf; /* Bounce buffer for SNP Guest Request output */ struct mutex guest_req_mutex; /* Must acquire before using bounce buffers */ cpumask_var_t have_run_cpus; /* CPUs that have done VMRUN for this VM. */ + bool snp_certs_enabled; /* SNP certificate-fetching support. */ }; struct kvm_svm { diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h index dddb781b0507..8cd107cdcf0b 100644 --- a/include/uapi/linux/kvm.h +++ b/include/uapi/linux/kvm.h @@ -135,6 +135,12 @@ struct kvm_xen_exit { } u; }; +struct kvm_exit_snp_req_certs { + __u64 gpa; + __u64 npages; + __u64 ret; +}; + #define KVM_S390_GET_SKEYS_NONE 1 #define KVM_S390_SKEYS_MAX 1048576 @@ -180,6 +186,7 @@ struct kvm_xen_exit { #define KVM_EXIT_MEMORY_FAULT 39 #define KVM_EXIT_TDX 40 #define KVM_EXIT_ARM_SEA 41 +#define KVM_EXIT_SNP_REQ_CERTS 42 /* For KVM_EXIT_INTERNAL_ERROR */ /* Emulate instruction failed. */ @@ -482,6 +489,8 @@ struct kvm_run { __u64 gva; __u64 gpa; } arm_sea; + /* KVM_EXIT_SNP_REQ_CERTS */ + struct kvm_exit_snp_req_certs snp_req_certs; /* Fix the size of the union. */ char padding[256]; }; -- cgit v1.2.3 From ba1b8c97b9a0414432382a11f144a8597f6f597e Mon Sep 17 00:00:00 2001 From: Paolo Abeni Date: Wed, 21 Jan 2026 17:11:30 +0100 Subject: geneve: add netlink support for GRO hint Allow configuring and dumping the new device option, and cache its value into the geneve socket itself. The new option is not tie to it any code yet. Signed-off-by: Paolo Abeni Link: https://patch.msgid.link/2295d4e4d1e919a3189425141bbc71c7850a2de0.1769011015.git.pabeni@redhat.com Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/rt-link.yaml | 3 +++ drivers/net/geneve.c | 29 +++++++++++++++++++++++++---- include/uapi/linux/if_link.h | 1 + 3 files changed, 29 insertions(+), 4 deletions(-) (limited to 'include/uapi/linux') diff --git a/Documentation/netlink/specs/rt-link.yaml b/Documentation/netlink/specs/rt-link.yaml index 6beeb6ee5adf..df4b56beb818 100644 --- a/Documentation/netlink/specs/rt-link.yaml +++ b/Documentation/netlink/specs/rt-link.yaml @@ -1914,6 +1914,9 @@ attribute-sets: name: port-range type: binary struct: ifla-geneve-port-range + - + name: gro-hint + type: flag - name: linkinfo-hsr-attrs name-prefix: ifla-hsr- diff --git a/drivers/net/geneve.c b/drivers/net/geneve.c index 64ea4b970376..8719ad66837e 100644 --- a/drivers/net/geneve.c +++ b/drivers/net/geneve.c @@ -56,6 +56,7 @@ struct geneve_config { bool collect_md; bool use_udp6_rx_checksums; bool ttl_inherit; + bool gro_hint; enum ifla_geneve_df df; bool inner_proto_inherit; u16 port_min; @@ -84,6 +85,7 @@ struct geneve_dev { struct geneve_sock { bool collect_md; + bool gro_hint; struct list_head list; struct socket *sock; struct rcu_head rcu; @@ -659,13 +661,15 @@ static void geneve_sock_release(struct geneve_dev *geneve) static struct geneve_sock *geneve_find_sock(struct geneve_net *gn, sa_family_t family, - __be16 dst_port) + __be16 dst_port, + bool gro_hint) { struct geneve_sock *gs; list_for_each_entry(gs, &gn->sock_list, list) { if (inet_sk(gs->sock->sk)->inet_sport == dst_port && - geneve_get_sk_family(gs) == family) { + geneve_get_sk_family(gs) == family && + gs->gro_hint == gro_hint) { return gs; } } @@ -676,12 +680,14 @@ static int geneve_sock_add(struct geneve_dev *geneve, bool ipv6) { struct net *net = geneve->net; struct geneve_net *gn = net_generic(net, geneve_net_id); + bool gro_hint = geneve->cfg.gro_hint; struct geneve_dev_node *node; struct geneve_sock *gs; __u8 vni[3]; __u32 hash; - gs = geneve_find_sock(gn, ipv6 ? AF_INET6 : AF_INET, geneve->cfg.info.key.tp_dst); + gs = geneve_find_sock(gn, ipv6 ? AF_INET6 : AF_INET, + geneve->cfg.info.key.tp_dst, gro_hint); if (gs) { gs->refcnt++; goto out; @@ -694,6 +700,7 @@ static int geneve_sock_add(struct geneve_dev *geneve, bool ipv6) out: gs->collect_md = geneve->cfg.collect_md; + gs->gro_hint = gro_hint; #if IS_ENABLED(CONFIG_IPV6) if (ipv6) { rcu_assign_pointer(geneve->sock6, gs); @@ -1257,6 +1264,7 @@ static const struct nla_policy geneve_policy[IFLA_GENEVE_MAX + 1] = { [IFLA_GENEVE_DF] = { .type = NLA_U8 }, [IFLA_GENEVE_INNER_PROTO_INHERIT] = { .type = NLA_FLAG }, [IFLA_GENEVE_PORT_RANGE] = NLA_POLICY_EXACT_LEN(sizeof(struct ifla_geneve_port_range)), + [IFLA_GENEVE_GRO_HINT] = { .type = NLA_FLAG }, }; static int geneve_validate(struct nlattr *tb[], struct nlattr *data[], @@ -1607,10 +1615,18 @@ static int geneve_nl2info(struct nlattr *tb[], struct nlattr *data[], cfg->inner_proto_inherit = true; } + if (data[IFLA_GENEVE_GRO_HINT]) { + if (changelink) { + attrtype = IFLA_GENEVE_GRO_HINT; + goto change_notsup; + } + cfg->gro_hint = true; + } + return 0; change_notsup: NL_SET_ERR_MSG_ATTR(extack, data[attrtype], - "Changing VNI, Port, endpoint IP address family, external, inner_proto_inherit, and UDP checksum attributes are not supported"); + "Changing VNI, Port, endpoint IP address family, external, inner_proto_inherit, gro_hint and UDP checksum attributes are not supported"); return -EOPNOTSUPP; } @@ -1793,6 +1809,7 @@ static size_t geneve_get_size(const struct net_device *dev) nla_total_size(sizeof(__u8)) + /* IFLA_GENEVE_TTL_INHERIT */ nla_total_size(0) + /* IFLA_GENEVE_INNER_PROTO_INHERIT */ nla_total_size(sizeof(struct ifla_geneve_port_range)) + /* IFLA_GENEVE_PORT_RANGE */ + nla_total_size(0) + /* IFLA_GENEVE_GRO_HINT */ 0; } @@ -1865,6 +1882,10 @@ static int geneve_fill_info(struct sk_buff *skb, const struct net_device *dev) if (nla_put(skb, IFLA_GENEVE_PORT_RANGE, sizeof(ports), &ports)) goto nla_put_failure; + if (geneve->cfg.gro_hint && + nla_put_flag(skb, IFLA_GENEVE_GRO_HINT)) + goto nla_put_failure; + return 0; nla_put_failure: diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h index 3b491d96e52e..e9b5f79e1ee1 100644 --- a/include/uapi/linux/if_link.h +++ b/include/uapi/linux/if_link.h @@ -1443,6 +1443,7 @@ enum { IFLA_GENEVE_DF, IFLA_GENEVE_INNER_PROTO_INHERIT, IFLA_GENEVE_PORT_RANGE, + IFLA_GENEVE_GRO_HINT, __IFLA_GENEVE_MAX }; #define IFLA_GENEVE_MAX (__IFLA_GENEVE_MAX - 1) -- cgit v1.2.3 From 795663b4d160ba652959f1a46381c5e8b1342a53 Mon Sep 17 00:00:00 2001 From: Pavel Begunkov Date: Sat, 24 Jan 2026 10:36:17 +0000 Subject: io_uring/zcrx: implement large rx buffer support There are network cards that support receive buffers larger than 4K, and that can be vastly beneficial for performance, and benchmarks for this patch showed up to 30% CPU util improvement for 32K vs 4K buffers. Allows zcrx users to specify the size in struct io_uring_zcrx_ifq_reg::rx_buf_len. If set to zero, zcrx will use a default value. zcrx will check and fail if the memory backing the area can't be split into physically contiguous chunks of the required size. It's more restrictive as it only needs dma addresses to be contig, but that's beyond this series. Signed-off-by: Pavel Begunkov [axboe: kill duplicate netdev_queues.h include] Signed-off-by: Jens Axboe --- include/uapi/linux/io_uring.h | 2 +- io_uring/zcrx.c | 38 +++++++++++++++++++++++++++++++++----- 2 files changed, 34 insertions(+), 6 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/io_uring.h b/include/uapi/linux/io_uring.h index b5b23c0d5283..3184f7e7f1f2 100644 --- a/include/uapi/linux/io_uring.h +++ b/include/uapi/linux/io_uring.h @@ -1082,7 +1082,7 @@ struct io_uring_zcrx_ifq_reg { struct io_uring_zcrx_offsets offsets; __u32 zcrx_id; - __u32 __resv2; + __u32 rx_buf_len; __u64 __resv[3]; }; diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c index b99cf2c6670a..8a9df72bc094 100644 --- a/io_uring/zcrx.c +++ b/io_uring/zcrx.c @@ -55,6 +55,18 @@ static inline struct page *io_zcrx_iov_page(const struct net_iov *niov) return area->mem.pages[net_iov_idx(niov) << niov_pages_shift]; } +static int io_area_max_shift(struct io_zcrx_mem *mem) +{ + struct sg_table *sgt = mem->sgt; + struct scatterlist *sg; + unsigned shift = -1U; + unsigned i; + + for_each_sgtable_dma_sg(sgt, sg, i) + shift = min(shift, __ffs(sg->length)); + return shift; +} + static int io_populate_area_dma(struct io_zcrx_ifq *ifq, struct io_zcrx_area *area) { @@ -416,12 +428,21 @@ static int io_zcrx_append_area(struct io_zcrx_ifq *ifq, } static int io_zcrx_create_area(struct io_zcrx_ifq *ifq, - struct io_uring_zcrx_area_reg *area_reg) + struct io_uring_zcrx_area_reg *area_reg, + struct io_uring_zcrx_ifq_reg *reg) { + int buf_size_shift = PAGE_SHIFT; struct io_zcrx_area *area; unsigned nr_iovs; int i, ret; + if (reg->rx_buf_len) { + if (!is_power_of_2(reg->rx_buf_len) || + reg->rx_buf_len < PAGE_SIZE) + return -EINVAL; + buf_size_shift = ilog2(reg->rx_buf_len); + } + ret = -ENOMEM; area = kzalloc(sizeof(*area), GFP_KERNEL); if (!area) @@ -432,7 +453,12 @@ static int io_zcrx_create_area(struct io_zcrx_ifq *ifq, if (ret) goto err; - ifq->niov_shift = PAGE_SHIFT; + if (buf_size_shift > io_area_max_shift(&area->mem)) { + ret = -ERANGE; + goto err; + } + + ifq->niov_shift = buf_size_shift; nr_iovs = area->mem.size >> ifq->niov_shift; area->nia.num_niovs = nr_iovs; @@ -742,8 +768,7 @@ int io_register_zcrx_ifq(struct io_ring_ctx *ctx, return -EINVAL; if (copy_from_user(®, arg, sizeof(reg))) return -EFAULT; - if (!mem_is_zero(®.__resv, sizeof(reg.__resv)) || - reg.__resv2 || reg.zcrx_id) + if (!mem_is_zero(®.__resv, sizeof(reg.__resv)) || reg.zcrx_id) return -EINVAL; if (reg.flags & ZCRX_REG_IMPORT) return import_zcrx(ctx, arg, ®); @@ -800,10 +825,11 @@ int io_register_zcrx_ifq(struct io_ring_ctx *ctx, } get_device(ifq->dev); - ret = io_zcrx_create_area(ifq, &area); + ret = io_zcrx_create_area(ifq, &area, ®); if (ret) goto netdev_put_unlock; + mp_param.rx_page_size = 1U << ifq->niov_shift; mp_param.mp_ops = &io_uring_pp_zc_ops; mp_param.mp_priv = ifq; ret = __net_mp_open_rxq(ifq->netdev, reg.if_rxq, &mp_param, NULL); @@ -821,6 +847,8 @@ int io_register_zcrx_ifq(struct io_ring_ctx *ctx, goto err; } + reg.rx_buf_len = 1U << ifq->niov_shift; + if (copy_to_user(arg, ®, sizeof(reg)) || copy_to_user(u64_to_user_ptr(reg.region_ptr), &rd, sizeof(rd)) || copy_to_user(u64_to_user_ptr(reg.area_ptr), &area, sizeof(area))) { -- cgit v1.2.3 From 2d419c44658f75e7655794341a95c0687830f3df Mon Sep 17 00:00:00 2001 From: Menglong Dong Date: Sat, 24 Jan 2026 14:19:56 +0800 Subject: bpf: add fsession support The fsession is something that similar to kprobe session. It allow to attach a single BPF program to both the entry and the exit of the target functions. Introduce the struct bpf_fsession_link, which allows to add the link to both the fentry and fexit progs_hlist of the trampoline. Signed-off-by: Menglong Dong Co-developed-by: Leon Hwang Signed-off-by: Leon Hwang Link: https://lore.kernel.org/r/20260124062008.8657-2-dongml2@chinatelecom.cn Signed-off-by: Alexei Starovoitov --- include/linux/bpf.h | 19 ++++++++ include/uapi/linux/bpf.h | 1 + kernel/bpf/btf.c | 2 + kernel/bpf/syscall.c | 18 +++++++- kernel/bpf/trampoline.c | 53 ++++++++++++++++++---- kernel/bpf/verifier.c | 12 +++-- net/bpf/test_run.c | 1 + net/core/bpf_sk_storage.c | 1 + tools/include/uapi/linux/bpf.h | 1 + .../selftests/bpf/prog_tests/tracing_failure.c | 2 +- 10 files changed, 97 insertions(+), 13 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/linux/bpf.h b/include/linux/bpf.h index 5936f8e2996f..41228b0add52 100644 --- a/include/linux/bpf.h +++ b/include/linux/bpf.h @@ -1309,6 +1309,7 @@ enum bpf_tramp_prog_type { BPF_TRAMP_MODIFY_RETURN, BPF_TRAMP_MAX, BPF_TRAMP_REPLACE, /* more than MAX */ + BPF_TRAMP_FSESSION, }; struct bpf_tramp_image { @@ -1875,6 +1876,11 @@ struct bpf_tracing_link { struct bpf_prog *tgt_prog; }; +struct bpf_fsession_link { + struct bpf_tracing_link link; + struct bpf_tramp_link fexit; +}; + struct bpf_raw_tp_link { struct bpf_link link; struct bpf_raw_event_map *btp; @@ -2169,6 +2175,19 @@ static inline void bpf_struct_ops_desc_release(struct bpf_struct_ops_desc *st_op #endif +static inline int bpf_fsession_cnt(struct bpf_tramp_links *links) +{ + struct bpf_tramp_links fentries = links[BPF_TRAMP_FENTRY]; + int cnt = 0; + + for (int i = 0; i < links[BPF_TRAMP_FENTRY].nr_links; i++) { + if (fentries.links[i]->link.prog->expected_attach_type == BPF_TRACE_FSESSION) + cnt++; + } + + return cnt; +} + int bpf_prog_ctx_arg_info_init(struct bpf_prog *prog, const struct bpf_ctx_arg_aux *info, u32 cnt); diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index 2a2ade4be60f..44e7dbc278e3 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -1145,6 +1145,7 @@ enum bpf_attach_type { BPF_NETKIT_PEER, BPF_TRACE_KPROBE_SESSION, BPF_TRACE_UPROBE_SESSION, + BPF_TRACE_FSESSION, __MAX_BPF_ATTACH_TYPE }; diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c index d10b3404260f..8959f3bc1e92 100644 --- a/kernel/bpf/btf.c +++ b/kernel/bpf/btf.c @@ -6219,6 +6219,7 @@ static int btf_validate_prog_ctx_type(struct bpf_verifier_log *log, const struct case BPF_TRACE_FENTRY: case BPF_TRACE_FEXIT: case BPF_MODIFY_RETURN: + case BPF_TRACE_FSESSION: /* allow u64* as ctx */ if (btf_is_int(t) && t->size == 8) return 0; @@ -6820,6 +6821,7 @@ bool btf_ctx_access(int off, int size, enum bpf_access_type type, fallthrough; case BPF_LSM_CGROUP: case BPF_TRACE_FEXIT: + case BPF_TRACE_FSESSION: /* When LSM programs are attached to void LSM hooks * they use FEXIT trampolines and when attached to * int LSM hooks, they use MODIFY_RETURN trampolines. diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c index 3c5c03d43f5f..b9184545c3fd 100644 --- a/kernel/bpf/syscall.c +++ b/kernel/bpf/syscall.c @@ -3577,6 +3577,7 @@ static int bpf_tracing_prog_attach(struct bpf_prog *prog, case BPF_PROG_TYPE_TRACING: if (prog->expected_attach_type != BPF_TRACE_FENTRY && prog->expected_attach_type != BPF_TRACE_FEXIT && + prog->expected_attach_type != BPF_TRACE_FSESSION && prog->expected_attach_type != BPF_MODIFY_RETURN) { err = -EINVAL; goto out_put_prog; @@ -3626,7 +3627,21 @@ static int bpf_tracing_prog_attach(struct bpf_prog *prog, key = bpf_trampoline_compute_key(tgt_prog, NULL, btf_id); } - link = kzalloc(sizeof(*link), GFP_USER); + if (prog->expected_attach_type == BPF_TRACE_FSESSION) { + struct bpf_fsession_link *fslink; + + fslink = kzalloc(sizeof(*fslink), GFP_USER); + if (fslink) { + bpf_link_init(&fslink->fexit.link, BPF_LINK_TYPE_TRACING, + &bpf_tracing_link_lops, prog, attach_type); + fslink->fexit.cookie = bpf_cookie; + link = &fslink->link; + } else { + link = NULL; + } + } else { + link = kzalloc(sizeof(*link), GFP_USER); + } if (!link) { err = -ENOMEM; goto out_put_prog; @@ -4350,6 +4365,7 @@ attach_type_to_prog_type(enum bpf_attach_type attach_type) case BPF_TRACE_RAW_TP: case BPF_TRACE_FENTRY: case BPF_TRACE_FEXIT: + case BPF_TRACE_FSESSION: case BPF_MODIFY_RETURN: return BPF_PROG_TYPE_TRACING; case BPF_LSM_MAC: diff --git a/kernel/bpf/trampoline.c b/kernel/bpf/trampoline.c index 2a125d063e62..edf9da43762d 100644 --- a/kernel/bpf/trampoline.c +++ b/kernel/bpf/trampoline.c @@ -109,10 +109,17 @@ bool bpf_prog_has_trampoline(const struct bpf_prog *prog) enum bpf_attach_type eatype = prog->expected_attach_type; enum bpf_prog_type ptype = prog->type; - return (ptype == BPF_PROG_TYPE_TRACING && - (eatype == BPF_TRACE_FENTRY || eatype == BPF_TRACE_FEXIT || - eatype == BPF_MODIFY_RETURN)) || - (ptype == BPF_PROG_TYPE_LSM && eatype == BPF_LSM_MAC); + switch (ptype) { + case BPF_PROG_TYPE_TRACING: + if (eatype == BPF_TRACE_FENTRY || eatype == BPF_TRACE_FEXIT || + eatype == BPF_MODIFY_RETURN || eatype == BPF_TRACE_FSESSION) + return true; + return false; + case BPF_PROG_TYPE_LSM: + return eatype == BPF_LSM_MAC; + default: + return false; + } } void bpf_image_ksym_init(void *data, unsigned int size, struct bpf_ksym *ksym) @@ -559,6 +566,8 @@ static enum bpf_tramp_prog_type bpf_attach_type_to_tramp(struct bpf_prog *prog) return BPF_TRAMP_MODIFY_RETURN; case BPF_TRACE_FEXIT: return BPF_TRAMP_FEXIT; + case BPF_TRACE_FSESSION: + return BPF_TRAMP_FSESSION; case BPF_LSM_MAC: if (!prog->aux->attach_func_proto->type) /* The function returns void, we cannot modify its @@ -594,8 +603,10 @@ static int __bpf_trampoline_link_prog(struct bpf_tramp_link *link, struct bpf_trampoline *tr, struct bpf_prog *tgt_prog) { + struct bpf_fsession_link *fslink = NULL; enum bpf_tramp_prog_type kind; struct bpf_tramp_link *link_exiting; + struct hlist_head *prog_list; int err = 0; int cnt = 0, i; @@ -621,24 +632,43 @@ static int __bpf_trampoline_link_prog(struct bpf_tramp_link *link, BPF_MOD_JUMP, NULL, link->link.prog->bpf_func); } + if (kind == BPF_TRAMP_FSESSION) { + prog_list = &tr->progs_hlist[BPF_TRAMP_FENTRY]; + cnt++; + } else { + prog_list = &tr->progs_hlist[kind]; + } if (cnt >= BPF_MAX_TRAMP_LINKS) return -E2BIG; if (!hlist_unhashed(&link->tramp_hlist)) /* prog already linked */ return -EBUSY; - hlist_for_each_entry(link_exiting, &tr->progs_hlist[kind], tramp_hlist) { + hlist_for_each_entry(link_exiting, prog_list, tramp_hlist) { if (link_exiting->link.prog != link->link.prog) continue; /* prog already linked */ return -EBUSY; } - hlist_add_head(&link->tramp_hlist, &tr->progs_hlist[kind]); - tr->progs_cnt[kind]++; + hlist_add_head(&link->tramp_hlist, prog_list); + if (kind == BPF_TRAMP_FSESSION) { + tr->progs_cnt[BPF_TRAMP_FENTRY]++; + fslink = container_of(link, struct bpf_fsession_link, link.link); + hlist_add_head(&fslink->fexit.tramp_hlist, &tr->progs_hlist[BPF_TRAMP_FEXIT]); + tr->progs_cnt[BPF_TRAMP_FEXIT]++; + } else { + tr->progs_cnt[kind]++; + } err = bpf_trampoline_update(tr, true /* lock_direct_mutex */); if (err) { hlist_del_init(&link->tramp_hlist); - tr->progs_cnt[kind]--; + if (kind == BPF_TRAMP_FSESSION) { + tr->progs_cnt[BPF_TRAMP_FENTRY]--; + hlist_del_init(&fslink->fexit.tramp_hlist); + tr->progs_cnt[BPF_TRAMP_FEXIT]--; + } else { + tr->progs_cnt[kind]--; + } } return err; } @@ -672,6 +702,13 @@ static int __bpf_trampoline_unlink_prog(struct bpf_tramp_link *link, guard(mutex)(&tgt_prog->aux->ext_mutex); tgt_prog->aux->is_extended = false; return err; + } else if (kind == BPF_TRAMP_FSESSION) { + struct bpf_fsession_link *fslink = + container_of(link, struct bpf_fsession_link, link.link); + + hlist_del_init(&fslink->fexit.tramp_hlist); + tr->progs_cnt[BPF_TRAMP_FEXIT]--; + kind = BPF_TRAMP_FENTRY; } hlist_del_init(&link->tramp_hlist); tr->progs_cnt[kind]--; diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index c7f5234d5fd2..41bbed6418b5 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -17848,6 +17848,7 @@ static int check_return_code(struct bpf_verifier_env *env, int regno, const char switch (env->prog->expected_attach_type) { case BPF_TRACE_FENTRY: case BPF_TRACE_FEXIT: + case BPF_TRACE_FSESSION: range = retval_range(0, 0); break; case BPF_TRACE_RAW_TP: @@ -23774,6 +23775,7 @@ patch_map_ops_generic: if (prog_type == BPF_PROG_TYPE_TRACING && insn->imm == BPF_FUNC_get_func_ret) { if (eatype == BPF_TRACE_FEXIT || + eatype == BPF_TRACE_FSESSION || eatype == BPF_MODIFY_RETURN) { /* Load nr_args from ctx - 8 */ insn_buf[0] = BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_1, -8); @@ -24725,7 +24727,8 @@ int bpf_check_attach_target(struct bpf_verifier_log *log, if (tgt_prog->type == BPF_PROG_TYPE_TRACING && prog_extension && (tgt_prog->expected_attach_type == BPF_TRACE_FENTRY || - tgt_prog->expected_attach_type == BPF_TRACE_FEXIT)) { + tgt_prog->expected_attach_type == BPF_TRACE_FEXIT || + tgt_prog->expected_attach_type == BPF_TRACE_FSESSION)) { /* Program extensions can extend all program types * except fentry/fexit. The reason is the following. * The fentry/fexit programs are used for performance @@ -24740,7 +24743,7 @@ int bpf_check_attach_target(struct bpf_verifier_log *log, * beyond reasonable stack size. Hence extending fentry * is not allowed. */ - bpf_log(log, "Cannot extend fentry/fexit\n"); + bpf_log(log, "Cannot extend fentry/fexit/fsession\n"); return -EINVAL; } } else { @@ -24824,6 +24827,7 @@ int bpf_check_attach_target(struct bpf_verifier_log *log, case BPF_LSM_CGROUP: case BPF_TRACE_FENTRY: case BPF_TRACE_FEXIT: + case BPF_TRACE_FSESSION: if (!btf_type_is_func(t)) { bpf_log(log, "attach_btf_id %u is not a function\n", btf_id); @@ -24990,6 +24994,7 @@ static bool can_be_sleepable(struct bpf_prog *prog) case BPF_TRACE_FEXIT: case BPF_MODIFY_RETURN: case BPF_TRACE_ITER: + case BPF_TRACE_FSESSION: return true; default: return false; @@ -25071,9 +25076,10 @@ static int check_attach_btf_id(struct bpf_verifier_env *env) tgt_info.tgt_name); return -EINVAL; } else if ((prog->expected_attach_type == BPF_TRACE_FEXIT || + prog->expected_attach_type == BPF_TRACE_FSESSION || prog->expected_attach_type == BPF_MODIFY_RETURN) && btf_id_set_contains(&noreturn_deny, btf_id)) { - verbose(env, "Attaching fexit/fmod_ret to __noreturn function '%s' is rejected.\n", + verbose(env, "Attaching fexit/fsession/fmod_ret to __noreturn function '%s' is rejected.\n", tgt_info.tgt_name); return -EINVAL; } diff --git a/net/bpf/test_run.c b/net/bpf/test_run.c index 26cfcfdc45eb..178c4738e63b 100644 --- a/net/bpf/test_run.c +++ b/net/bpf/test_run.c @@ -685,6 +685,7 @@ int bpf_prog_test_run_tracing(struct bpf_prog *prog, switch (prog->expected_attach_type) { case BPF_TRACE_FENTRY: case BPF_TRACE_FEXIT: + case BPF_TRACE_FSESSION: if (bpf_fentry_test1(1) != 2 || bpf_fentry_test2(2, 3) != 5 || bpf_fentry_test3(4, 5, 6) != 15 || diff --git a/net/core/bpf_sk_storage.c b/net/core/bpf_sk_storage.c index 850dd736ccd1..de111818f3a0 100644 --- a/net/core/bpf_sk_storage.c +++ b/net/core/bpf_sk_storage.c @@ -365,6 +365,7 @@ static bool bpf_sk_storage_tracing_allowed(const struct bpf_prog *prog) return true; case BPF_TRACE_FENTRY: case BPF_TRACE_FEXIT: + case BPF_TRACE_FSESSION: return !!strncmp(prog->aux->attach_func_name, "bpf_sk_storage", strlen("bpf_sk_storage")); default: diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h index b816bc53d2e1..3ca7d76e05f0 100644 --- a/tools/include/uapi/linux/bpf.h +++ b/tools/include/uapi/linux/bpf.h @@ -1145,6 +1145,7 @@ enum bpf_attach_type { BPF_NETKIT_PEER, BPF_TRACE_KPROBE_SESSION, BPF_TRACE_UPROBE_SESSION, + BPF_TRACE_FSESSION, __MAX_BPF_ATTACH_TYPE }; diff --git a/tools/testing/selftests/bpf/prog_tests/tracing_failure.c b/tools/testing/selftests/bpf/prog_tests/tracing_failure.c index 10e231965589..f9f9e1cb87bf 100644 --- a/tools/testing/selftests/bpf/prog_tests/tracing_failure.c +++ b/tools/testing/selftests/bpf/prog_tests/tracing_failure.c @@ -73,7 +73,7 @@ static void test_tracing_deny(void) static void test_fexit_noreturns(void) { test_tracing_fail_prog("fexit_noreturns", - "Attaching fexit/fmod_ret to __noreturn function 'do_exit' is rejected."); + "Attaching fexit/fsession/fmod_ret to __noreturn function 'do_exit' is rejected."); } void test_tracing_failure(void) -- cgit v1.2.3 From 0ac903d1bfdce8ff40657c2b7d996947b72b6645 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 9 Dec 2025 19:28:50 -0500 Subject: NFS: NFSERR_INVAL is not defined by NFSv2 A documenting comment in include/uapi/linux/nfs.h claims incorrectly that NFSv2 defines NFSERR_INVAL. There is no such definition in either RFC 1094 or https://pubs.opengroup.org/onlinepubs/9629799/chap7.htm NFS3ERR_INVAL is introduced in RFC 1813. NFSD returns NFSERR_INVAL for PROC_GETACL, which has no specification (yet). However, nfsd_map_status() maps nfserr_symlink and nfserr_wrong_type to nfserr_inval, which does not align with RFC 1094. This logic was introduced only recently by commit 438f81e0e92a ("nfsd: move error choice for incorrect object types to version-specific code."). Given that we have no INVAL or SERVERFAULT status in NFSv2, probably the only choice is NFSERR_IO. Fixes: 438f81e0e92a ("nfsd: move error choice for incorrect object types to version-specific code.") Reviewed-by: NeilBrown Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/nfsd/nfs2acl.c | 2 +- fs/nfsd/nfsproc.c | 2 +- include/uapi/linux/nfs.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'include/uapi/linux') diff --git a/fs/nfsd/nfs2acl.c b/fs/nfsd/nfs2acl.c index 5fb202acb0fd..0ac538c76180 100644 --- a/fs/nfsd/nfs2acl.c +++ b/fs/nfsd/nfs2acl.c @@ -45,7 +45,7 @@ static __be32 nfsacld_proc_getacl(struct svc_rqst *rqstp) inode = d_inode(fh->fh_dentry); if (argp->mask & ~NFS_ACL_MASK) { - resp->status = nfserr_inval; + resp->status = nfserr_io; goto out; } resp->mask = argp->mask; diff --git a/fs/nfsd/nfsproc.c b/fs/nfsd/nfsproc.c index 481e789a7697..8873033d1e82 100644 --- a/fs/nfsd/nfsproc.c +++ b/fs/nfsd/nfsproc.c @@ -33,7 +33,7 @@ static __be32 nfsd_map_status(__be32 status) break; case nfserr_symlink: case nfserr_wrong_type: - status = nfserr_inval; + status = nfserr_io; break; } return status; diff --git a/include/uapi/linux/nfs.h b/include/uapi/linux/nfs.h index 71c7196d3281..e629c4953534 100644 --- a/include/uapi/linux/nfs.h +++ b/include/uapi/linux/nfs.h @@ -55,7 +55,7 @@ NFSERR_NODEV = 19, /* v2 v3 v4 */ NFSERR_NOTDIR = 20, /* v2 v3 v4 */ NFSERR_ISDIR = 21, /* v2 v3 v4 */ - NFSERR_INVAL = 22, /* v2 v3 v4 */ + NFSERR_INVAL = 22, /* v3 v4 */ NFSERR_FBIG = 27, /* v2 v3 v4 */ NFSERR_NOSPC = 28, /* v2 v3 v4 */ NFSERR_ROFS = 30, /* v2 v3 v4 */ -- cgit v1.2.3 From 1965bbb8f3c72e5f1972b5eeb6f19a36664a676d Mon Sep 17 00:00:00 2001 From: Thomas Weißschuh Date: Mon, 22 Dec 2025 08:55:10 +0100 Subject: ipc/shm: uapi: remove dependency on libc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Using libc types and headers from the UAPI headers is problematic as it introduces a dependency on a full C toolchain. shm.h does not even use any symbols from the libc header as the usage of getpagesize() was removed a decade ago in commit 060028bac94b ("ipc/shm.c: increase the defaults for SHMALL, SHMMAX") Drop the unnecessary inclusion. Link: https://lkml.kernel.org/r/20251222-uapi-shm-v1-1-270bb7f75d97@linutronix.de Signed-off-by: Thomas Weißschuh Cc: Arnd Bergmann Signed-off-by: Andrew Morton --- include/uapi/linux/shm.h | 3 --- 1 file changed, 3 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/shm.h b/include/uapi/linux/shm.h index 8d1f17a4e08e..7269f9f402e3 100644 --- a/include/uapi/linux/shm.h +++ b/include/uapi/linux/shm.h @@ -5,9 +5,6 @@ #include #include #include -#ifndef __KERNEL__ -#include -#endif /* * SHMMNI, SHMMAX and SHMALL are default upper limits which can be -- cgit v1.2.3 From 3d702678f57edc524f73a7865382ae304269f590 Mon Sep 17 00:00:00 2001 From: Jinjiang Tu Date: Tue, 23 Dec 2025 19:05:23 +0800 Subject: mm/mempolicy: fix mpol_rebind_nodemask() for MPOL_F_NUMA_BALANCING commit bda420b98505 ("numa balancing: migrate on fault among multiple bound nodes") adds new flag MPOL_F_NUMA_BALANCING to enable NUMA balancing for MPOL_BIND memory policy. When the cpuset of tasks changes, the mempolicy of the task is rebound by mpol_rebind_nodemask(). When MPOL_F_STATIC_NODES and MPOL_F_RELATIVE_NODES are both not set, the behaviour of rebinding should be same whenever MPOL_F_NUMA_BALANCING is set or not. So, when an application calls set_mempolicy() with MPOL_F_NUMA_BALANCING set but both MPOL_F_STATIC_NODES and MPOL_F_RELATIVE_NODES cleared, mempolicy.w.cpuset_mems_allowed should be set to cpuset_current_mems_allowed nodemask. However, in current implementation, mpol_store_user_nodemask() wrongly returns true, causing mempolicy->w.user_nodemask to be incorrectly set to the user-specified nodemask. Later, when the cpuset of the application changes, mpol_rebind_nodemask() ends up rebinding based on the user-specified nodemask rather than the cpuset_mems_allowed nodemask as intended. I can reproduce with the following steps in qemu with 4 NUMA nodes: 1. echo '+cpuset' > /sys/fs/cgroup/cgroup.subtree_control 2. mkdir /sys/fs/cgroup/test 3. ./reproducer & 4. cat /proc/$pid/numa_maps, the task is bound to NUMA 1 5. echo $pid > /sys/fs/cgroup/test/cgroup.procs 6. cat /proc/$pid/numa_maps, the task is bound to NUMA 0 now. The reproducer code: int main() { struct bitmask *bmp; int ret; bmp = numa_parse_nodestring("1"); ret = set_mempolicy(MPOL_BIND | MPOL_F_NUMA_BALANCING, bmp->maskp, bmp->size + 1); if (ret < 0) { perror("Failed to call set_mempolicy"); exit(-1); } while (1); return 0; } If I call set_mempolicy() without MPOL_F_NUMA_BALANCING in the reproducer code. After step 5, the task is still bound to NUMA 1. To fix this, only set mempolicy->w.user_nodemask to the user-specified nodemask if MPOL_F_STATIC_NODES or MPOL_F_RELATIVE_NODES is present. Link: https://lkml.kernel.org/r/20260120011018.1256654-1-tujinjiang@huawei.com Link: https://lkml.kernel.org/r/20251223110523.1161421-1-tujinjiang@huawei.com Fixes: bda420b98505 ("numa balancing: migrate on fault among multiple bound nodes") Signed-off-by: Jinjiang Tu Reviewed-by: Gregory Price Reviewed-by: Huang Ying Acked-by: David Hildenbrand (Red Hat) Cc: Alistair Popple Cc: Byungchul Park Cc: Joshua Hahn Cc: Kefeng Wang Cc: Mathew Brost Cc: Mel Gorman Cc: Rakie Kim Cc: Zi Yan Signed-off-by: Andrew Morton --- include/uapi/linux/mempolicy.h | 3 +++ mm/mempolicy.c | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/mempolicy.h b/include/uapi/linux/mempolicy.h index 8fbbe613611a..6c962d866e86 100644 --- a/include/uapi/linux/mempolicy.h +++ b/include/uapi/linux/mempolicy.h @@ -39,6 +39,9 @@ enum { #define MPOL_MODE_FLAGS \ (MPOL_F_STATIC_NODES | MPOL_F_RELATIVE_NODES | MPOL_F_NUMA_BALANCING) +/* Whether the nodemask is specified by users */ +#define MPOL_USER_NODEMASK_FLAGS (MPOL_F_STATIC_NODES | MPOL_F_RELATIVE_NODES) + /* Flags for get_mempolicy */ #define MPOL_F_NODE (1<<0) /* return next IL mode instead of node mask */ #define MPOL_F_ADDR (1<<1) /* look up vma using address */ diff --git a/mm/mempolicy.c b/mm/mempolicy.c index 68a98ba57882..76da50425712 100644 --- a/mm/mempolicy.c +++ b/mm/mempolicy.c @@ -365,7 +365,7 @@ static const struct mempolicy_operations { static inline int mpol_store_user_nodemask(const struct mempolicy *pol) { - return pol->flags & MPOL_MODE_FLAGS; + return pol->flags & MPOL_USER_NODEMASK_FLAGS; } static void mpol_relative_nodemask(nodemask_t *ret, const nodemask_t *orig, -- cgit v1.2.3 From 86c6b6e4d187652d718915e15cf126f98e24e955 Mon Sep 17 00:00:00 2001 From: Avraham Stern Date: Sun, 11 Jan 2026 19:03:48 +0200 Subject: wifi: nl80211/cfg80211: add new FTM capabilities Add new capabilities to the PMSR FTM capabilities list. The new capabilities include 6 GHz support, supported number of spatial streams and supported number of LTF repetitions. Signed-off-by: Avraham Stern Tested-by: Miriam Rachel Korenblit Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260111190221.bf43785c18f6.Ic98cf9790ddee84bf88e5720b93c46c23af3c96c@changeid Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 20 +++++++++++++++++++- include/uapi/linux/nl80211.h | 29 +++++++++++++++++++++++++++++ net/wireless/nl80211.c | 23 +++++++++++++++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 6d8e35a0dde4..8153b6aaa998 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -5643,6 +5643,17 @@ cfg80211_get_iftype_ext_capa(struct wiphy *wiphy, enum nl80211_iftype type); * not limited) * @ftm.trigger_based: trigger based ranging measurement is supported * @ftm.non_trigger_based: non trigger based ranging measurement is supported + * @ftm.support_6ghz: supports ranging in 6 GHz band + * @ftm.max_tx_ltf_rep: maximum number of TX LTF repetitions supported (0 means + * only one LTF, no repetitions) + * @ftm.max_rx_ltf_rep: maximum number of RX LTF repetitions supported (0 means + * only one LTF, no repetitions) + * @ftm.max_tx_sts: maximum number of TX STS supported (zero based) + * @ftm.max_rx_sts: maximum number of RX STS supported (zero based) + * @ftm.max_total_ltf_tx: maximum total number of LTFs that can be transmitted + * (0 means unknown) + * @ftm.max_total_ltf_rx: maximum total number of LTFs that can be received + * (0 means unknown) */ struct cfg80211_pmsr_capabilities { unsigned int max_peers; @@ -5660,7 +5671,14 @@ struct cfg80211_pmsr_capabilities { request_lci:1, request_civicloc:1, trigger_based:1, - non_trigger_based:1; + non_trigger_based:1, + support_6ghz:1; + u8 max_tx_ltf_rep; + u8 max_rx_ltf_rep; + u8 max_tx_sts; + u8 max_rx_sts; + u8 max_total_ltf_tx; + u8 max_total_ltf_rx; } ftm; }; diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index b0f050e36fa4..200703c8b2c1 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -7790,6 +7790,28 @@ enum nl80211_peer_measurement_attrs { * trigger based ranging measurement is supported * @NL80211_PMSR_FTM_CAPA_ATTR_NON_TRIGGER_BASED: flag attribute indicating * if non-trigger-based ranging measurement is supported + * @NL80211_PMSR_FTM_CAPA_ATTR_6GHZ_SUPPORT: flag attribute indicating if + * ranging on the 6 GHz band is supported + * @NL80211_PMSR_FTM_CAPA_ATTR_MAX_TX_LTF_REP: u32 attribute indicating + * the maximum number of LTF repetitions the device can transmit in the + * preamble of the ranging NDP (zero means only one LTF, no repetitions) + * @NL80211_PMSR_FTM_CAPA_ATTR_MAX_RX_LTF_REP: u32 attribute indicating + * the maximum number of LTF repetitions the device can receive in the + * preamble of the ranging NDP (zero means only one LTF, no repetitions) + * @NL80211_PMSR_FTM_CAPA_ATTR_MAX_TX_STS: u32 attribute indicating + * the maximum number of space-time streams supported for ranging NDP TX + * (zero-based) + * @NL80211_PMSR_FTM_CAPA_ATTR_MAX_RX_STS: u32 attribute indicating + * the maximum number of space-time streams supported for ranging NDP RX + * (zero-based) + * @NL80211_PMSR_FTM_CAPA_ATTR_MAX_TOTAL_LTF_TX: u32 attribute indicating the + * maximum total number of LTFs the device can transmit. The total number + * of LTFs is (number of LTF repetitions) * (number of space-time streams). + * This limits the allowed combinations of LTF repetitions and STS. + * @NL80211_PMSR_FTM_CAPA_ATTR_MAX_TOTAL_LTF_RX: u32 attribute indicating the + * maximum total number of LTFs the device can receive. The total number + * of LTFs is (number of LTF repetitions) * (number of space-time streams). + * This limits the allowed combinations of LTF repetitions and STS. * * @NUM_NL80211_PMSR_FTM_CAPA_ATTR: internal * @NL80211_PMSR_FTM_CAPA_ATTR_MAX: highest attribute number @@ -7807,6 +7829,13 @@ enum nl80211_peer_measurement_ftm_capa { NL80211_PMSR_FTM_CAPA_ATTR_MAX_FTMS_PER_BURST, NL80211_PMSR_FTM_CAPA_ATTR_TRIGGER_BASED, NL80211_PMSR_FTM_CAPA_ATTR_NON_TRIGGER_BASED, + NL80211_PMSR_FTM_CAPA_ATTR_6GHZ_SUPPORT, + NL80211_PMSR_FTM_CAPA_ATTR_MAX_TX_LTF_REP, + NL80211_PMSR_FTM_CAPA_ATTR_MAX_RX_LTF_REP, + NL80211_PMSR_FTM_CAPA_ATTR_MAX_TX_STS, + NL80211_PMSR_FTM_CAPA_ATTR_MAX_RX_STS, + NL80211_PMSR_FTM_CAPA_ATTR_MAX_TOTAL_LTF_TX, + NL80211_PMSR_FTM_CAPA_ATTR_MAX_TOTAL_LTF_RX, /* keep last */ NUM_NL80211_PMSR_FTM_CAPA_ATTR, diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 56cc5ed33ea3..74ea922a5e8a 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -2313,6 +2313,29 @@ nl80211_send_pmsr_ftm_capa(const struct cfg80211_pmsr_capabilities *cap, if (cap->ftm.non_trigger_based && nla_put_flag(msg, NL80211_PMSR_FTM_CAPA_ATTR_NON_TRIGGER_BASED)) return -ENOBUFS; + if (cap->ftm.support_6ghz && + nla_put_flag(msg, NL80211_PMSR_FTM_CAPA_ATTR_6GHZ_SUPPORT)) + return -ENOBUFS; + if (nla_put_u32(msg, NL80211_PMSR_FTM_CAPA_ATTR_MAX_TX_LTF_REP, + cap->ftm.max_tx_ltf_rep)) + return -ENOBUFS; + if (nla_put_u32(msg, NL80211_PMSR_FTM_CAPA_ATTR_MAX_RX_LTF_REP, + cap->ftm.max_rx_ltf_rep)) + return -ENOBUFS; + if (nla_put_u32(msg, NL80211_PMSR_FTM_CAPA_ATTR_MAX_TX_STS, + cap->ftm.max_tx_sts)) + return -ENOBUFS; + if (nla_put_u32(msg, NL80211_PMSR_FTM_CAPA_ATTR_MAX_RX_STS, + cap->ftm.max_rx_sts)) + return -ENOBUFS; + if (cap->ftm.max_total_ltf_tx > 0 && + nla_put_u32(msg, NL80211_PMSR_FTM_CAPA_ATTR_MAX_TOTAL_LTF_TX, + cap->ftm.max_total_ltf_tx)) + return -ENOBUFS; + if (cap->ftm.max_total_ltf_rx > 0 && + nla_put_u32(msg, NL80211_PMSR_FTM_CAPA_ATTR_MAX_TOTAL_LTF_RX, + cap->ftm.max_total_ltf_rx)) + return -ENOBUFS; nla_nest_end(msg, ftm); return 0; -- cgit v1.2.3 From 853ce6943c385be2f6cccf371080e592f2e08b0f Mon Sep 17 00:00:00 2001 From: Avraham Stern Date: Sun, 11 Jan 2026 19:03:49 +0200 Subject: wifi: nl80211/cfg80211: clarify periodic FTM parameters for non-EDCA based ranging Periodic FTM request attributes are defined based on the periodic parameters used in EDCA-based ranging negotiation. However, non-EDCA based ranging (trigger-based/non-trigger-based) does not include periodic parameters in the negotiation protocol, even though upper layers may still request periodic measurements. Clarify the semantics of periodic ranging attributes when used with non-EDCA based ranging. Signed-off-by: Avraham Stern Reviewed-by: Johannes Berg Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260111190221.b89cb3f68e1a.I7a9d8c6d1c66c77f1b43120a841101c96c3f19ad@changeid Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 4 +++- include/uapi/linux/nl80211.h | 7 +++++-- net/wireless/pmsr.c | 11 ++++++----- 3 files changed, 14 insertions(+), 8 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 8153b6aaa998..8a81adbf3723 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -4295,7 +4295,9 @@ struct cfg80211_pmsr_result { * @burst_period: burst period to use * @asap: indicates to use ASAP mode * @num_bursts_exp: number of bursts exponent - * @burst_duration: burst duration + * @burst_duration: burst duration. If @trigger_based or @non_trigger_based is + * set, this is the burst duration in milliseconds, and zero means the + * device should pick an appropriate value based on @ftms_per_burst. * @ftms_per_burst: number of FTMs per burst * @ftmr_retries: number of retries for FTM request * @request_lci: request LCI information diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index 200703c8b2c1..71219445f5c7 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -7851,12 +7851,15 @@ enum nl80211_peer_measurement_ftm_capa { * &enum nl80211_preamble), optional for DMG (u32) * @NL80211_PMSR_FTM_REQ_ATTR_NUM_BURSTS_EXP: number of bursts exponent as in * 802.11-2016 9.4.2.168 "Fine Timing Measurement Parameters element" - * (u8, 0-15, optional with default 15 i.e. "no preference") + * (u8, 0-15, optional with default 15 i.e. "no preference". No limit for + * non-EDCA ranging) * @NL80211_PMSR_FTM_REQ_ATTR_BURST_PERIOD: interval between bursts in units * of 100ms (u16, optional with default 0) * @NL80211_PMSR_FTM_REQ_ATTR_BURST_DURATION: burst duration, as in 802.11-2016 * Table 9-257 "Burst Duration field encoding" (u8, 0-15, optional with - * default 15 i.e. "no preference") + * default 15 i.e. "no preference"). For non-EDCA ranging, this is the + * burst duration in milliseconds (optional with default 0, i.e. let the + * device decide). * @NL80211_PMSR_FTM_REQ_ATTR_FTMS_PER_BURST: number of successful FTM frames * requested per burst * (u8, 0-31, optional with default 0 i.e. "no preference") diff --git a/net/wireless/pmsr.c b/net/wireless/pmsr.c index a117f5093ca2..795683a81303 100644 --- a/net/wireless/pmsr.c +++ b/net/wireless/pmsr.c @@ -85,11 +85,6 @@ static int pmsr_parse_ftm(struct cfg80211_registered_device *rdev, return -EINVAL; } - out->ftm.burst_duration = 15; - if (tb[NL80211_PMSR_FTM_REQ_ATTR_BURST_DURATION]) - out->ftm.burst_duration = - nla_get_u8(tb[NL80211_PMSR_FTM_REQ_ATTR_BURST_DURATION]); - out->ftm.ftms_per_burst = 0; if (tb[NL80211_PMSR_FTM_REQ_ATTR_FTMS_PER_BURST]) out->ftm.ftms_per_burst = @@ -164,6 +159,12 @@ static int pmsr_parse_ftm(struct cfg80211_registered_device *rdev, return -EINVAL; } + if (tb[NL80211_PMSR_FTM_REQ_ATTR_BURST_DURATION]) + out->ftm.burst_duration = + nla_get_u8(tb[NL80211_PMSR_FTM_REQ_ATTR_BURST_DURATION]); + else if (!out->ftm.non_trigger_based && !out->ftm.trigger_based) + out->ftm.burst_duration = 15; + out->ftm.lmr_feedback = !!tb[NL80211_PMSR_FTM_REQ_ATTR_LMR_FEEDBACK]; if (!out->ftm.trigger_based && !out->ftm.non_trigger_based && -- cgit v1.2.3 From cfd46d1c6f4bf232c5630b1cf5c8b317d38101c5 Mon Sep 17 00:00:00 2001 From: Avraham Stern Date: Sun, 11 Jan 2026 19:03:50 +0200 Subject: wifi: nl80211/cfg80211: add negotiated burst period to FTM result The FTM result includes some of the periodic measurement negotiated parameters (like the burst duration and number of bursts), but it doesn't include the burst period. Add it to the FTM result notification. Signed-off-by: Avraham Stern Reviewed-by: Johannes Berg Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260111190221.e0778f86edef.I3c98c1933eb639963bc3ffdef81a8788b59f2188@changeid Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 2 ++ include/uapi/linux/nl80211.h | 3 +++ net/wireless/pmsr.c | 1 + 3 files changed, 6 insertions(+) (limited to 'include/uapi/linux') diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 8a81adbf3723..535fd95b0d83 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -4192,6 +4192,7 @@ struct cfg80211_ftm_responder_stats { * @num_bursts_exp: actual number of bursts exponent negotiated * @burst_duration: actual burst duration negotiated * @ftms_per_burst: actual FTMs per burst negotiated + * @burst_period: actual burst period negotiated in units of 100ms * @lci_len: length of LCI information (if present) * @civicloc_len: length of civic location information (if present) * @lci: LCI data (may be %NULL) @@ -4233,6 +4234,7 @@ struct cfg80211_pmsr_ftm_result { u8 num_bursts_exp; u8 burst_duration; u8 ftms_per_burst; + u16 burst_period; s32 rssi_avg; s32 rssi_spread; struct rate_info tx_rate, rx_rate; diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index 71219445f5c7..8910b709bfb1 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -7992,6 +7992,8 @@ enum nl80211_peer_measurement_ftm_failure_reasons { * 9.4.2.22.1) starting with the Measurement Token, with Measurement * Type 11. * @NL80211_PMSR_FTM_RESP_ATTR_PAD: ignore, for u64/s64 padding only + * @NL80211_PMSR_FTM_RESP_ATTR_BURST_PERIOD: actual burst period used by + * the responder (similar to request, u16) * * @NUM_NL80211_PMSR_FTM_RESP_ATTR: internal * @NL80211_PMSR_FTM_RESP_ATTR_MAX: highest attribute number @@ -8020,6 +8022,7 @@ enum nl80211_peer_measurement_ftm_resp { NL80211_PMSR_FTM_RESP_ATTR_LCI, NL80211_PMSR_FTM_RESP_ATTR_CIVICLOC, NL80211_PMSR_FTM_RESP_ATTR_PAD, + NL80211_PMSR_FTM_RESP_ATTR_BURST_PERIOD, /* keep last */ NUM_NL80211_PMSR_FTM_RESP_ATTR, diff --git a/net/wireless/pmsr.c b/net/wireless/pmsr.c index 795683a81303..d5077d320098 100644 --- a/net/wireless/pmsr.c +++ b/net/wireless/pmsr.c @@ -454,6 +454,7 @@ static int nl80211_pmsr_send_ftm_res(struct sk_buff *msg, PUT(u8, NUM_BURSTS_EXP, num_bursts_exp); PUT(u8, BURST_DURATION, burst_duration); PUT(u8, FTMS_PER_BURST, ftms_per_burst); + PUT(u16, BURST_PERIOD, burst_period); PUTOPT(s32, RSSI_AVG, rssi_avg); PUTOPT(s32, RSSI_SPREAD, rssi_spread); if (res->ftm.tx_rate_valid && -- cgit v1.2.3 From 853800c746d38486673ef67f461b660a01d52716 Mon Sep 17 00:00:00 2001 From: Avraham Stern Date: Sun, 11 Jan 2026 19:03:51 +0200 Subject: wifi: nl80211/cfg80211: support operating as RSTA in PMSR FTM request Add an option to operate as the RSTA in an FTM measurement request. When requested, the device will dwell on the requested channel until the peer starts the FTM negotiation. This option is only valid for trigger-based/non trigger-based measurement with LMR feedback which will allow the RSTA to receive the results of the measurement. Signed-off-by: Avraham Stern Reviewed-by: Johannes Berg Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260111190221.1f95fc0afab4.Iae2d32783b8e7c4a29089fec0f4c6bce94d303cc@changeid Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 7 ++++++- include/uapi/linux/nl80211.h | 12 ++++++++++++ net/wireless/nl80211.c | 4 ++++ net/wireless/pmsr.c | 15 +++++++++++++++ 4 files changed, 37 insertions(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 535fd95b0d83..ac7df439bd24 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -4312,6 +4312,8 @@ struct cfg80211_pmsr_result { * EDCA based ranging will be used. * @lmr_feedback: negotiate for I2R LMR feedback. Only valid if either * @trigger_based or @non_trigger_based is set. + * @rsta: Operate as the RSTA in the measurement. Only valid if @lmr_feedback + * and either @trigger_based or @non_trigger_based is set. * @bss_color: the bss color of the responder. Optional. Set to zero to * indicate the driver should set the BSS color. Only valid if * @non_trigger_based or @trigger_based is set. @@ -4327,7 +4329,8 @@ struct cfg80211_pmsr_ftm_request_peer { request_civicloc:1, trigger_based:1, non_trigger_based:1, - lmr_feedback:1; + lmr_feedback:1, + rsta:1; u8 num_bursts_exp; u8 burst_duration; u8 ftms_per_burst; @@ -5658,6 +5661,7 @@ cfg80211_get_iftype_ext_capa(struct wiphy *wiphy, enum nl80211_iftype type); * (0 means unknown) * @ftm.max_total_ltf_rx: maximum total number of LTFs that can be received * (0 means unknown) + * @ftm.support_rsta: supports operating as RSTA in PMSR FTM request */ struct cfg80211_pmsr_capabilities { unsigned int max_peers; @@ -5683,6 +5687,7 @@ struct cfg80211_pmsr_capabilities { u8 max_rx_sts; u8 max_total_ltf_tx; u8 max_total_ltf_rx; + u8 support_rsta:1; } ftm; }; diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index 8910b709bfb1..54ddbd9a5459 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -7812,6 +7812,8 @@ enum nl80211_peer_measurement_attrs { * maximum total number of LTFs the device can receive. The total number * of LTFs is (number of LTF repetitions) * (number of space-time streams). * This limits the allowed combinations of LTF repetitions and STS. + * @NL80211_PMSR_FTM_CAPA_ATTR_RSTA_SUPPORT: flag attribute indicating the + * device supports operating as the RSTA in PMSR FTM request * * @NUM_NL80211_PMSR_FTM_CAPA_ATTR: internal * @NL80211_PMSR_FTM_CAPA_ATTR_MAX: highest attribute number @@ -7836,6 +7838,7 @@ enum nl80211_peer_measurement_ftm_capa { NL80211_PMSR_FTM_CAPA_ATTR_MAX_RX_STS, NL80211_PMSR_FTM_CAPA_ATTR_MAX_TOTAL_LTF_TX, NL80211_PMSR_FTM_CAPA_ATTR_MAX_TOTAL_LTF_RX, + NL80211_PMSR_FTM_CAPA_ATTR_RSTA_SUPPORT, /* keep last */ NUM_NL80211_PMSR_FTM_CAPA_ATTR, @@ -7888,6 +7891,14 @@ enum nl80211_peer_measurement_ftm_capa { * @NL80211_PMSR_FTM_REQ_ATTR_BSS_COLOR: optional. The BSS color of the * responder. Only valid if %NL80211_PMSR_FTM_REQ_ATTR_NON_TRIGGER_BASED * or %NL80211_PMSR_FTM_REQ_ATTR_TRIGGER_BASED is set. + * @NL80211_PMSR_FTM_REQ_ATTR_RSTA: optional. Request to perform the measurement + * as the RSTA (flag). When set, the device is expected to dwell on the + * channel specified in %NL80211_PMSR_PEER_ATTR_CHAN until it receives the + * FTM request from the peer or the timeout specified by + * %NL80211_ATTR_TIMEOUT has expired. + * Only valid if %NL80211_PMSR_FTM_REQ_ATTR_LMR_FEEDBACK is set (so the + * RSTA will have the measurement results to report back in the FTM + * response). * * @NUM_NL80211_PMSR_FTM_REQ_ATTR: internal * @NL80211_PMSR_FTM_REQ_ATTR_MAX: highest attribute number @@ -7908,6 +7919,7 @@ enum nl80211_peer_measurement_ftm_req { NL80211_PMSR_FTM_REQ_ATTR_NON_TRIGGER_BASED, NL80211_PMSR_FTM_REQ_ATTR_LMR_FEEDBACK, NL80211_PMSR_FTM_REQ_ATTR_BSS_COLOR, + NL80211_PMSR_FTM_REQ_ATTR_RSTA, /* keep last */ NUM_NL80211_PMSR_FTM_REQ_ATTR, diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 74ea922a5e8a..85e30fda4c46 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -361,6 +361,7 @@ nl80211_pmsr_ftm_req_attr_policy[NL80211_PMSR_FTM_REQ_ATTR_MAX + 1] = { [NL80211_PMSR_FTM_REQ_ATTR_NON_TRIGGER_BASED] = { .type = NLA_FLAG }, [NL80211_PMSR_FTM_REQ_ATTR_LMR_FEEDBACK] = { .type = NLA_FLAG }, [NL80211_PMSR_FTM_REQ_ATTR_BSS_COLOR] = { .type = NLA_U8 }, + [NL80211_PMSR_FTM_REQ_ATTR_RSTA] = { .type = NLA_FLAG }, }; static const struct nla_policy @@ -2336,6 +2337,9 @@ nl80211_send_pmsr_ftm_capa(const struct cfg80211_pmsr_capabilities *cap, nla_put_u32(msg, NL80211_PMSR_FTM_CAPA_ATTR_MAX_TOTAL_LTF_RX, cap->ftm.max_total_ltf_rx)) return -ENOBUFS; + if (cap->ftm.support_rsta && + nla_put_flag(msg, NL80211_PMSR_FTM_CAPA_ATTR_RSTA_SUPPORT)) + return -ENOBUFS; nla_nest_end(msg, ftm); return 0; diff --git a/net/wireless/pmsr.c b/net/wireless/pmsr.c index d5077d320098..60e1e31c2185 100644 --- a/net/wireless/pmsr.c +++ b/net/wireless/pmsr.c @@ -187,6 +187,21 @@ static int pmsr_parse_ftm(struct cfg80211_registered_device *rdev, nla_get_u8(tb[NL80211_PMSR_FTM_REQ_ATTR_BSS_COLOR]); } + out->ftm.rsta = !!tb[NL80211_PMSR_FTM_REQ_ATTR_RSTA]; + if (out->ftm.rsta && !capa->ftm.support_rsta) { + NL_SET_ERR_MSG_ATTR(info->extack, + tb[NL80211_PMSR_FTM_REQ_ATTR_RSTA], + "FTM: RSTA not supported by device"); + return -EOPNOTSUPP; + } + + if (out->ftm.rsta && !out->ftm.lmr_feedback) { + NL_SET_ERR_MSG_ATTR(info->extack, + tb[NL80211_PMSR_FTM_REQ_ATTR_RSTA], + "FTM: RSTA set without LMR feedback"); + return -EINVAL; + } + return 0; } -- cgit v1.2.3 From 752b807028e63f1473b84eb1350e131eca5e5249 Mon Sep 17 00:00:00 2001 From: Matt Bobrowski Date: Tue, 27 Jan 2026 08:51:10 +0000 Subject: bpf: add new BPF_CGROUP_ITER_CHILDREN control option Currently, the BPF cgroup iterator supports walking descendants in either pre-order (BPF_CGROUP_ITER_DESCENDANTS_PRE) or post-order (BPF_CGROUP_ITER_DESCENDANTS_POST). These modes perform an exhaustive depth-first search (DFS) of the hierarchy. In scenarios where a BPF program may need to inspect only the direct children of a given parent cgroup, a full DFS is unnecessarily expensive. This patch introduces a new BPF cgroup iterator control option, BPF_CGROUP_ITER_CHILDREN. This control option restricts the traversal to the immediate children of a specified parent cgroup, allowing for more targeted and efficient iteration, particularly when exhaustive depth-first search (DFS) traversal is not required. Signed-off-by: Matt Bobrowski Link: https://lore.kernel.org/r/20260127085112.3608687-1-mattbobrowski@google.com Signed-off-by: Alexei Starovoitov --- include/uapi/linux/bpf.h | 8 ++++++++ kernel/bpf/cgroup_iter.c | 26 +++++++++++++++++++++----- tools/include/uapi/linux/bpf.h | 8 ++++++++ 3 files changed, 37 insertions(+), 5 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index 44e7dbc278e3..c8d400b7680a 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -119,6 +119,14 @@ enum bpf_cgroup_iter_order { BPF_CGROUP_ITER_DESCENDANTS_PRE, /* walk descendants in pre-order. */ BPF_CGROUP_ITER_DESCENDANTS_POST, /* walk descendants in post-order. */ BPF_CGROUP_ITER_ANCESTORS_UP, /* walk ancestors upward. */ + /* + * Walks the immediate children of the specified parent + * cgroup_subsys_state. Unlike BPF_CGROUP_ITER_DESCENDANTS_PRE, + * BPF_CGROUP_ITER_DESCENDANTS_POST, and BPF_CGROUP_ITER_ANCESTORS_UP + * the iterator does not include the specified parent as one of the + * returned iterator elements. + */ + BPF_CGROUP_ITER_CHILDREN, }; union bpf_iter_link_info { diff --git a/kernel/bpf/cgroup_iter.c b/kernel/bpf/cgroup_iter.c index f04a468cf6a7..fd51fe3d92cc 100644 --- a/kernel/bpf/cgroup_iter.c +++ b/kernel/bpf/cgroup_iter.c @@ -8,12 +8,13 @@ #include "../cgroup/cgroup-internal.h" /* cgroup_mutex and cgroup_is_dead */ -/* cgroup_iter provides four modes of traversal to the cgroup hierarchy. +/* cgroup_iter provides five modes of traversal to the cgroup hierarchy. * * 1. Walk the descendants of a cgroup in pre-order. * 2. Walk the descendants of a cgroup in post-order. * 3. Walk the ancestors of a cgroup. * 4. Show the given cgroup only. + * 5. Walk the children of a given parent cgroup. * * For walking descendants, cgroup_iter can walk in either pre-order or * post-order. For walking ancestors, the iter walks up from a cgroup to @@ -78,6 +79,8 @@ static void *cgroup_iter_seq_start(struct seq_file *seq, loff_t *pos) return css_next_descendant_pre(NULL, p->start_css); else if (p->order == BPF_CGROUP_ITER_DESCENDANTS_POST) return css_next_descendant_post(NULL, p->start_css); + else if (p->order == BPF_CGROUP_ITER_CHILDREN) + return css_next_child(NULL, p->start_css); else /* BPF_CGROUP_ITER_SELF_ONLY and BPF_CGROUP_ITER_ANCESTORS_UP */ return p->start_css; } @@ -113,6 +116,8 @@ static void *cgroup_iter_seq_next(struct seq_file *seq, void *v, loff_t *pos) return css_next_descendant_post(curr, p->start_css); else if (p->order == BPF_CGROUP_ITER_ANCESTORS_UP) return curr->parent; + else if (p->order == BPF_CGROUP_ITER_CHILDREN) + return css_next_child(curr, p->start_css); else /* BPF_CGROUP_ITER_SELF_ONLY */ return NULL; } @@ -200,11 +205,16 @@ static int bpf_iter_attach_cgroup(struct bpf_prog *prog, int order = linfo->cgroup.order; struct cgroup *cgrp; - if (order != BPF_CGROUP_ITER_DESCENDANTS_PRE && - order != BPF_CGROUP_ITER_DESCENDANTS_POST && - order != BPF_CGROUP_ITER_ANCESTORS_UP && - order != BPF_CGROUP_ITER_SELF_ONLY) + switch (order) { + case BPF_CGROUP_ITER_DESCENDANTS_PRE: + case BPF_CGROUP_ITER_DESCENDANTS_POST: + case BPF_CGROUP_ITER_ANCESTORS_UP: + case BPF_CGROUP_ITER_SELF_ONLY: + case BPF_CGROUP_ITER_CHILDREN: + break; + default: return -EINVAL; + } if (fd && id) return -EINVAL; @@ -257,6 +267,8 @@ show_order: seq_puts(seq, "order: descendants_post\n"); else if (aux->cgroup.order == BPF_CGROUP_ITER_ANCESTORS_UP) seq_puts(seq, "order: ancestors_up\n"); + else if (aux->cgroup.order == BPF_CGROUP_ITER_CHILDREN) + seq_puts(seq, "order: children\n"); else /* BPF_CGROUP_ITER_SELF_ONLY */ seq_puts(seq, "order: self_only\n"); } @@ -320,6 +332,7 @@ __bpf_kfunc int bpf_iter_css_new(struct bpf_iter_css *it, case BPF_CGROUP_ITER_DESCENDANTS_PRE: case BPF_CGROUP_ITER_DESCENDANTS_POST: case BPF_CGROUP_ITER_ANCESTORS_UP: + case BPF_CGROUP_ITER_CHILDREN: break; default: return -EINVAL; @@ -345,6 +358,9 @@ __bpf_kfunc struct cgroup_subsys_state *bpf_iter_css_next(struct bpf_iter_css *i case BPF_CGROUP_ITER_DESCENDANTS_POST: kit->pos = css_next_descendant_post(kit->pos, kit->start); break; + case BPF_CGROUP_ITER_CHILDREN: + kit->pos = css_next_child(kit->pos, kit->start); + break; case BPF_CGROUP_ITER_ANCESTORS_UP: kit->pos = kit->pos ? kit->pos->parent : kit->start; } diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h index 3ca7d76e05f0..5e38b4887de6 100644 --- a/tools/include/uapi/linux/bpf.h +++ b/tools/include/uapi/linux/bpf.h @@ -119,6 +119,14 @@ enum bpf_cgroup_iter_order { BPF_CGROUP_ITER_DESCENDANTS_PRE, /* walk descendants in pre-order. */ BPF_CGROUP_ITER_DESCENDANTS_POST, /* walk descendants in post-order. */ BPF_CGROUP_ITER_ANCESTORS_UP, /* walk ancestors upward. */ + /* + * Walks the immediate children of the specified parent + * cgroup_subsys_state. Unlike BPF_CGROUP_ITER_DESCENDANTS_PRE, + * BPF_CGROUP_ITER_DESCENDANTS_POST, and BPF_CGROUP_ITER_ANCESTORS_UP + * the iterator does not include the specified parent as one of the + * returned iterator elements. + */ + BPF_CGROUP_ITER_CHILDREN, }; union bpf_iter_link_info { -- cgit v1.2.3 From d42eb05e60fea31de49897d63a1d73f933303bd4 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Thu, 15 Jan 2026 08:24:02 -0700 Subject: io_uring: add support for BPF filtering for opcode restrictions Add support for loading classic BPF programs with io_uring to provide fine-grained filtering of SQE operations. Unlike IORING_REGISTER_RESTRICTIONS which only allows bitmap-based allow/deny of opcodes, BPF filters can inspect request attributes and make dynamic decisions. The filter is registered via IORING_REGISTER_BPF_FILTER with a struct io_uring_bpf: struct io_uring_bpf_filter { __u32 opcode; /* io_uring opcode to filter */ __u32 flags; __u32 filter_len; /* number of BPF instructions */ __u32 resv; __u64 filter_ptr; /* pointer to BPF filter */ __u64 resv2[5]; }; enum { IO_URING_BPF_CMD_FILTER = 1, }; struct io_uring_bpf { __u16 cmd_type; /* IO_URING_BPF_* values */ __u16 cmd_flags; /* none so far */ __u32 resv; union { struct io_uring_bpf_filter filter; }; }; and the filters get supplied a struct io_uring_bpf_ctx: struct io_uring_bpf_ctx { __u64 user_data; __u8 opcode; __u8 sqe_flags; __u8 pdu_size; __u8 pad[5]; }; where it's possible to filter on opcode and sqe_flags, with pdu_size indicating how much extra data is being passed in beyond the pad field. This will used for specific finer grained filtering inside an opcode. An example of that for sockets is in one of the following patches. Anything the opcode supports can end up in this struct, populated by the opcode itself, and hence can be filtered for. Filters have the following semantics: - Return 1 to allow the request - Return 0 to deny the request with -EACCES - Multiple filters can be stacked per opcode. All filters must return 1 for the opcode to be allowed. - Filters are evaluated in registration order (most recent first) The implementation uses classic BPF (cBPF) rather than eBPF for as that's required for containers, and since they can be used by any user in the system. Signed-off-by: Jens Axboe --- include/linux/io_uring_types.h | 9 + include/uapi/linux/io_uring.h | 3 + include/uapi/linux/io_uring/bpf_filter.h | 50 +++++ io_uring/Kconfig | 5 + io_uring/Makefile | 1 + io_uring/bpf_filter.c | 321 +++++++++++++++++++++++++++++++ io_uring/bpf_filter.h | 42 ++++ io_uring/io_uring.c | 8 + io_uring/register.c | 8 + 9 files changed, 447 insertions(+) create mode 100644 include/uapi/linux/io_uring/bpf_filter.h create mode 100644 io_uring/bpf_filter.c create mode 100644 io_uring/bpf_filter.h (limited to 'include/uapi/linux') diff --git a/include/linux/io_uring_types.h b/include/linux/io_uring_types.h index dc6bd6940a0d..74bf98362876 100644 --- a/include/linux/io_uring_types.h +++ b/include/linux/io_uring_types.h @@ -219,9 +219,18 @@ struct io_rings { struct io_uring_cqe cqes[] ____cacheline_aligned_in_smp; }; +struct io_bpf_filter; +struct io_bpf_filters { + refcount_t refs; /* ref for ->bpf_filters */ + spinlock_t lock; /* protects ->bpf_filters modifications */ + struct io_bpf_filter __rcu **filters; + struct rcu_head rcu_head; +}; + struct io_restriction { DECLARE_BITMAP(register_op, IORING_REGISTER_LAST); DECLARE_BITMAP(sqe_op, IORING_OP_LAST); + struct io_bpf_filters *bpf_filters; u8 sqe_flags_allowed; u8 sqe_flags_required; /* IORING_OP_* restrictions exist */ diff --git a/include/uapi/linux/io_uring.h b/include/uapi/linux/io_uring.h index b5b23c0d5283..94669b77fee8 100644 --- a/include/uapi/linux/io_uring.h +++ b/include/uapi/linux/io_uring.h @@ -700,6 +700,9 @@ enum io_uring_register_op { /* auxiliary zcrx configuration, see enum zcrx_ctrl_op */ IORING_REGISTER_ZCRX_CTRL = 36, + /* register bpf filtering programs */ + IORING_REGISTER_BPF_FILTER = 37, + /* this goes last */ IORING_REGISTER_LAST, diff --git a/include/uapi/linux/io_uring/bpf_filter.h b/include/uapi/linux/io_uring/bpf_filter.h new file mode 100644 index 000000000000..2d4d0e5743e4 --- /dev/null +++ b/include/uapi/linux/io_uring/bpf_filter.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: (GPL-2.0 WITH Linux-syscall-note) OR MIT */ +/* + * Header file for the io_uring BPF filters. + */ +#ifndef LINUX_IO_URING_BPF_FILTER_H +#define LINUX_IO_URING_BPF_FILTER_H + +#include + +/* + * Struct passed to filters. + */ +struct io_uring_bpf_ctx { + __u64 user_data; + __u8 opcode; + __u8 sqe_flags; + __u8 pdu_size; /* size of aux data for filter */ + __u8 pad[5]; +}; + +enum { + /* + * If set, any currently unset opcode will have a deny filter attached + */ + IO_URING_BPF_FILTER_DENY_REST = 1, +}; + +struct io_uring_bpf_filter { + __u32 opcode; /* io_uring opcode to filter */ + __u32 flags; + __u32 filter_len; /* number of BPF instructions */ + __u32 resv; + __u64 filter_ptr; /* pointer to BPF filter */ + __u64 resv2[5]; +}; + +enum { + IO_URING_BPF_CMD_FILTER = 1, +}; + +struct io_uring_bpf { + __u16 cmd_type; /* IO_URING_BPF_* values */ + __u16 cmd_flags; /* none so far */ + __u32 resv; + union { + struct io_uring_bpf_filter filter; + }; +}; + +#endif diff --git a/io_uring/Kconfig b/io_uring/Kconfig index 4b949c42c0bf..a7ae23cf1035 100644 --- a/io_uring/Kconfig +++ b/io_uring/Kconfig @@ -9,3 +9,8 @@ config IO_URING_ZCRX depends on PAGE_POOL depends on INET depends on NET_RX_BUSY_POLL + +config IO_URING_BPF + def_bool y + depends on BPF + depends on NET diff --git a/io_uring/Makefile b/io_uring/Makefile index bf9eff88427a..931f9156132a 100644 --- a/io_uring/Makefile +++ b/io_uring/Makefile @@ -24,3 +24,4 @@ obj-$(CONFIG_NET_RX_BUSY_POLL) += napi.o obj-$(CONFIG_NET) += net.o cmd_net.o obj-$(CONFIG_PROC_FS) += fdinfo.o obj-$(CONFIG_IO_URING_MOCK_FILE) += mock_file.o +obj-$(CONFIG_IO_URING_BPF) += bpf_filter.o diff --git a/io_uring/bpf_filter.c b/io_uring/bpf_filter.c new file mode 100644 index 000000000000..5207226d72ea --- /dev/null +++ b/io_uring/bpf_filter.c @@ -0,0 +1,321 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * BPF filter support for io_uring. Supports SQE opcodes for now. + */ +#include +#include +#include +#include +#include +#include + +#include "io_uring.h" +#include "bpf_filter.h" +#include "net.h" + +struct io_bpf_filter { + struct bpf_prog *prog; + struct io_bpf_filter *next; +}; + +/* Deny if this is set as the filter */ +static const struct io_bpf_filter dummy_filter; + +static void io_uring_populate_bpf_ctx(struct io_uring_bpf_ctx *bctx, + struct io_kiocb *req) +{ + bctx->opcode = req->opcode; + bctx->sqe_flags = (__force int) req->flags & SQE_VALID_FLAGS; + bctx->user_data = req->cqe.user_data; + /* clear residual, anything from pdu_size and below */ + memset((void *) bctx + offsetof(struct io_uring_bpf_ctx, pdu_size), 0, + sizeof(*bctx) - offsetof(struct io_uring_bpf_ctx, pdu_size)); +} + +/* + * Run registered filters for a given opcode. For filters, a return of 0 denies + * execution of the request, a return of 1 allows it. If any filter for an + * opcode returns 0, filter processing is stopped, and the request is denied. + * This also stops the processing of filters. + * + * __io_uring_run_bpf_filters() returns 0 on success, allow running the + * request, and -EACCES when a request is denied. + */ +int __io_uring_run_bpf_filters(struct io_restriction *res, struct io_kiocb *req) +{ + struct io_bpf_filter *filter; + struct io_uring_bpf_ctx bpf_ctx; + int ret; + + /* Fast check for existence of filters outside of RCU */ + if (!rcu_access_pointer(res->bpf_filters->filters[req->opcode])) + return 0; + + /* + * req->opcode has already been validated to be within the range + * of what we expect, io_init_req() does this. + */ + guard(rcu)(); + filter = rcu_dereference(res->bpf_filters->filters[req->opcode]); + if (!filter) + return 0; + else if (filter == &dummy_filter) + return -EACCES; + + io_uring_populate_bpf_ctx(&bpf_ctx, req); + + /* + * Iterate registered filters. The opcode is allowed IFF all filters + * return 1. If any filter returns denied, opcode will be denied. + */ + do { + if (filter == &dummy_filter) + return -EACCES; + ret = bpf_prog_run(filter->prog, &bpf_ctx); + if (!ret) + return -EACCES; + filter = filter->next; + } while (filter); + + return 0; +} + +static void io_free_bpf_filters(struct rcu_head *head) +{ + struct io_bpf_filter __rcu **filter; + struct io_bpf_filters *filters; + int i; + + filters = container_of(head, struct io_bpf_filters, rcu_head); + scoped_guard(spinlock, &filters->lock) { + filter = filters->filters; + if (!filter) + return; + } + + for (i = 0; i < IORING_OP_LAST; i++) { + struct io_bpf_filter *f; + + rcu_read_lock(); + f = rcu_dereference(filter[i]); + while (f) { + struct io_bpf_filter *next = f->next; + + /* + * Even if stacked, dummy filter will always be last + * as it can only get installed into an empty spot. + */ + if (f == &dummy_filter) + break; + bpf_prog_destroy(f->prog); + kfree(f); + f = next; + } + rcu_read_unlock(); + } + kfree(filters->filters); + kfree(filters); +} + +static void __io_put_bpf_filters(struct io_bpf_filters *filters) +{ + if (refcount_dec_and_test(&filters->refs)) + call_rcu(&filters->rcu_head, io_free_bpf_filters); +} + +void io_put_bpf_filters(struct io_restriction *res) +{ + if (res->bpf_filters) + __io_put_bpf_filters(res->bpf_filters); +} + +static struct io_bpf_filters *io_new_bpf_filters(void) +{ + struct io_bpf_filters *filters __free(kfree) = NULL; + + filters = kzalloc(sizeof(*filters), GFP_KERNEL_ACCOUNT); + if (!filters) + return ERR_PTR(-ENOMEM); + + filters->filters = kcalloc(IORING_OP_LAST, + sizeof(struct io_bpf_filter *), + GFP_KERNEL_ACCOUNT); + if (!filters->filters) + return ERR_PTR(-ENOMEM); + + refcount_set(&filters->refs, 1); + spin_lock_init(&filters->lock); + return no_free_ptr(filters); +} + +/* + * Validate classic BPF filter instructions. Only allow a safe subset of + * operations - no packet data access, just context field loads and basic + * ALU/jump operations. + */ +static int io_uring_check_cbpf_filter(struct sock_filter *filter, + unsigned int flen) +{ + int pc; + + for (pc = 0; pc < flen; pc++) { + struct sock_filter *ftest = &filter[pc]; + u16 code = ftest->code; + u32 k = ftest->k; + + switch (code) { + case BPF_LD | BPF_W | BPF_ABS: + ftest->code = BPF_LDX | BPF_W | BPF_ABS; + /* 32-bit aligned and not out of bounds. */ + if (k >= sizeof(struct io_uring_bpf_ctx) || k & 3) + return -EINVAL; + continue; + case BPF_LD | BPF_W | BPF_LEN: + ftest->code = BPF_LD | BPF_IMM; + ftest->k = sizeof(struct io_uring_bpf_ctx); + continue; + case BPF_LDX | BPF_W | BPF_LEN: + ftest->code = BPF_LDX | BPF_IMM; + ftest->k = sizeof(struct io_uring_bpf_ctx); + continue; + /* Explicitly include allowed calls. */ + case BPF_RET | BPF_K: + case BPF_RET | BPF_A: + case BPF_ALU | BPF_ADD | BPF_K: + case BPF_ALU | BPF_ADD | BPF_X: + case BPF_ALU | BPF_SUB | BPF_K: + case BPF_ALU | BPF_SUB | BPF_X: + case BPF_ALU | BPF_MUL | BPF_K: + case BPF_ALU | BPF_MUL | BPF_X: + case BPF_ALU | BPF_DIV | BPF_K: + case BPF_ALU | BPF_DIV | BPF_X: + case BPF_ALU | BPF_AND | BPF_K: + case BPF_ALU | BPF_AND | BPF_X: + case BPF_ALU | BPF_OR | BPF_K: + case BPF_ALU | BPF_OR | BPF_X: + case BPF_ALU | BPF_XOR | BPF_K: + case BPF_ALU | BPF_XOR | BPF_X: + case BPF_ALU | BPF_LSH | BPF_K: + case BPF_ALU | BPF_LSH | BPF_X: + case BPF_ALU | BPF_RSH | BPF_K: + case BPF_ALU | BPF_RSH | BPF_X: + case BPF_ALU | BPF_NEG: + case BPF_LD | BPF_IMM: + case BPF_LDX | BPF_IMM: + case BPF_MISC | BPF_TAX: + case BPF_MISC | BPF_TXA: + case BPF_LD | BPF_MEM: + case BPF_LDX | BPF_MEM: + case BPF_ST: + case BPF_STX: + case BPF_JMP | BPF_JA: + case BPF_JMP | BPF_JEQ | BPF_K: + case BPF_JMP | BPF_JEQ | BPF_X: + case BPF_JMP | BPF_JGE | BPF_K: + case BPF_JMP | BPF_JGE | BPF_X: + case BPF_JMP | BPF_JGT | BPF_K: + case BPF_JMP | BPF_JGT | BPF_X: + case BPF_JMP | BPF_JSET | BPF_K: + case BPF_JMP | BPF_JSET | BPF_X: + continue; + default: + return -EINVAL; + } + } + return 0; +} + +#define IO_URING_BPF_FILTER_FLAGS IO_URING_BPF_FILTER_DENY_REST + +int io_register_bpf_filter(struct io_restriction *res, + struct io_uring_bpf __user *arg) +{ + struct io_bpf_filter *filter, *old_filter; + struct io_bpf_filters *filters; + struct io_uring_bpf reg; + struct bpf_prog *prog; + struct sock_fprog fprog; + int ret; + + if (copy_from_user(®, arg, sizeof(reg))) + return -EFAULT; + if (reg.cmd_type != IO_URING_BPF_CMD_FILTER) + return -EINVAL; + if (reg.cmd_flags || reg.resv) + return -EINVAL; + + if (reg.filter.opcode >= IORING_OP_LAST) + return -EINVAL; + if (reg.filter.flags & ~IO_URING_BPF_FILTER_FLAGS) + return -EINVAL; + if (reg.filter.resv) + return -EINVAL; + if (!mem_is_zero(reg.filter.resv2, sizeof(reg.filter.resv2))) + return -EINVAL; + if (!reg.filter.filter_len || reg.filter.filter_len > BPF_MAXINSNS) + return -EINVAL; + + fprog.len = reg.filter.filter_len; + fprog.filter = u64_to_user_ptr(reg.filter.filter_ptr); + + ret = bpf_prog_create_from_user(&prog, &fprog, + io_uring_check_cbpf_filter, false); + if (ret) + return ret; + + /* + * No existing filters, allocate set. + */ + filters = res->bpf_filters; + if (!filters) { + filters = io_new_bpf_filters(); + if (IS_ERR(filters)) { + ret = PTR_ERR(filters); + goto err_prog; + } + } + + filter = kzalloc(sizeof(*filter), GFP_KERNEL_ACCOUNT); + if (!filter) { + ret = -ENOMEM; + goto err; + } + filter->prog = prog; + res->bpf_filters = filters; + + /* + * Insert filter - if the current opcode already has a filter + * attached, add to the set. + */ + rcu_read_lock(); + spin_lock_bh(&filters->lock); + old_filter = rcu_dereference(filters->filters[reg.filter.opcode]); + if (old_filter) + filter->next = old_filter; + rcu_assign_pointer(filters->filters[reg.filter.opcode], filter); + + /* + * If IO_URING_BPF_FILTER_DENY_REST is set, fill any unregistered + * opcode with the dummy filter. That will cause them to be denied. + */ + if (reg.filter.flags & IO_URING_BPF_FILTER_DENY_REST) { + for (int i = 0; i < IORING_OP_LAST; i++) { + if (i == reg.filter.opcode) + continue; + old_filter = rcu_dereference(filters->filters[i]); + if (old_filter) + continue; + rcu_assign_pointer(filters->filters[i], &dummy_filter); + } + } + + spin_unlock_bh(&filters->lock); + rcu_read_unlock(); + return 0; +err: + if (filters != res->bpf_filters) + __io_put_bpf_filters(filters); +err_prog: + bpf_prog_destroy(prog); + return ret; +} diff --git a/io_uring/bpf_filter.h b/io_uring/bpf_filter.h new file mode 100644 index 000000000000..27eae9705473 --- /dev/null +++ b/io_uring/bpf_filter.h @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef IO_URING_BPF_FILTER_H +#define IO_URING_BPF_FILTER_H + +#include + +#ifdef CONFIG_IO_URING_BPF + +int __io_uring_run_bpf_filters(struct io_restriction *res, struct io_kiocb *req); + +int io_register_bpf_filter(struct io_restriction *res, + struct io_uring_bpf __user *arg); + +void io_put_bpf_filters(struct io_restriction *res); + +static inline int io_uring_run_bpf_filters(struct io_restriction *res, + struct io_kiocb *req) +{ + if (res->bpf_filters) + return __io_uring_run_bpf_filters(res, req); + + return 0; +} + +#else + +static inline int io_register_bpf_filter(struct io_restriction *res, + struct io_uring_bpf __user *arg) +{ + return -EINVAL; +} +static inline int io_uring_run_bpf_filters(struct io_restriction *res, + struct io_kiocb *req) +{ + return 0; +} +static inline void io_put_bpf_filters(struct io_restriction *res) +{ +} +#endif /* CONFIG_IO_URING_BPF */ + +#endif diff --git a/io_uring/io_uring.c b/io_uring/io_uring.c index a50459238bee..9b9794dfc27a 100644 --- a/io_uring/io_uring.c +++ b/io_uring/io_uring.c @@ -94,6 +94,7 @@ #include "alloc_cache.h" #include "eventfd.h" #include "wait.h" +#include "bpf_filter.h" #define SQE_COMMON_FLAGS (IOSQE_FIXED_FILE | IOSQE_IO_LINK | \ IOSQE_IO_HARDLINK | IOSQE_ASYNC) @@ -1874,6 +1875,12 @@ static inline int io_submit_sqe(struct io_ring_ctx *ctx, struct io_kiocb *req, if (unlikely(ret)) return io_submit_fail_init(sqe, req, ret); + if (unlikely(ctx->restrictions.bpf_filters)) { + ret = io_uring_run_bpf_filters(&ctx->restrictions, req); + if (ret) + return io_submit_fail_init(sqe, req, ret); + } + trace_io_uring_submit_req(req); /* @@ -2161,6 +2168,7 @@ static __cold void io_ring_ctx_free(struct io_ring_ctx *ctx) percpu_ref_exit(&ctx->refs); free_uid(ctx->user); io_req_caches_free(ctx); + io_put_bpf_filters(&ctx->restrictions); WARN_ON_ONCE(ctx->nr_req_allocated); diff --git a/io_uring/register.c b/io_uring/register.c index 8551f13920dc..30957c2cb5eb 100644 --- a/io_uring/register.c +++ b/io_uring/register.c @@ -33,6 +33,7 @@ #include "memmap.h" #include "zcrx.h" #include "query.h" +#include "bpf_filter.h" #define IORING_MAX_RESTRICTIONS (IORING_RESTRICTION_LAST + \ IORING_REGISTER_LAST + IORING_OP_LAST) @@ -830,6 +831,13 @@ static int __io_uring_register(struct io_ring_ctx *ctx, unsigned opcode, case IORING_REGISTER_ZCRX_CTRL: ret = io_zcrx_ctrl(ctx, arg, nr_args); break; + case IORING_REGISTER_BPF_FILTER: + ret = -EINVAL; + + if (nr_args != 1) + break; + ret = io_register_bpf_filter(&ctx->restrictions, arg); + break; default: ret = -EINVAL; break; -- cgit v1.2.3 From cff1c26b4223820431129696b45525e5928e6409 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Fri, 16 Jan 2026 14:50:05 -0700 Subject: io_uring/net: allow filtering on IORING_OP_SOCKET data Example population method for the BPF based opcode filtering. This exposes the socket family, type, and protocol to a registered BPF filter. This in turn enables the filter to make decisions based on what was passed in to the IORING_OP_SOCKET request type. Signed-off-by: Jens Axboe --- include/uapi/linux/io_uring/bpf_filter.h | 7 +++++++ io_uring/bpf_filter.c | 11 +++++++++++ io_uring/net.c | 9 +++++++++ io_uring/net.h | 6 ++++++ 4 files changed, 33 insertions(+) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/io_uring/bpf_filter.h b/include/uapi/linux/io_uring/bpf_filter.h index 2d4d0e5743e4..4dbc89bbbf10 100644 --- a/include/uapi/linux/io_uring/bpf_filter.h +++ b/include/uapi/linux/io_uring/bpf_filter.h @@ -16,6 +16,13 @@ struct io_uring_bpf_ctx { __u8 sqe_flags; __u8 pdu_size; /* size of aux data for filter */ __u8 pad[5]; + union { + struct { + __u32 family; + __u32 type; + __u32 protocol; + } socket; + }; }; enum { diff --git a/io_uring/bpf_filter.c b/io_uring/bpf_filter.c index 5207226d72ea..889fa915fa54 100644 --- a/io_uring/bpf_filter.c +++ b/io_uring/bpf_filter.c @@ -30,6 +30,17 @@ static void io_uring_populate_bpf_ctx(struct io_uring_bpf_ctx *bctx, /* clear residual, anything from pdu_size and below */ memset((void *) bctx + offsetof(struct io_uring_bpf_ctx, pdu_size), 0, sizeof(*bctx) - offsetof(struct io_uring_bpf_ctx, pdu_size)); + + /* + * Opcodes can provide a handler fo populating more data into bctx, + * for filters to use. + */ + switch (req->opcode) { + case IORING_OP_SOCKET: + bctx->pdu_size = sizeof(bctx->socket); + io_socket_bpf_populate(bctx, req); + break; + } } /* diff --git a/io_uring/net.c b/io_uring/net.c index 519ea055b761..4fcba36bd0bb 100644 --- a/io_uring/net.c +++ b/io_uring/net.c @@ -1699,6 +1699,15 @@ retry: return IOU_COMPLETE; } +void io_socket_bpf_populate(struct io_uring_bpf_ctx *bctx, struct io_kiocb *req) +{ + struct io_socket *sock = io_kiocb_to_cmd(req, struct io_socket); + + bctx->socket.family = sock->domain; + bctx->socket.type = sock->type; + bctx->socket.protocol = sock->protocol; +} + int io_socket_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe) { struct io_socket *sock = io_kiocb_to_cmd(req, struct io_socket); diff --git a/io_uring/net.h b/io_uring/net.h index 43e5ce5416b7..a862960a3bb9 100644 --- a/io_uring/net.h +++ b/io_uring/net.h @@ -3,6 +3,7 @@ #include #include #include +#include struct io_async_msghdr { #if defined(CONFIG_NET) @@ -44,6 +45,7 @@ int io_accept(struct io_kiocb *req, unsigned int issue_flags); int io_socket_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe); int io_socket(struct io_kiocb *req, unsigned int issue_flags); +void io_socket_bpf_populate(struct io_uring_bpf_ctx *bctx, struct io_kiocb *req); int io_connect_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe); int io_connect(struct io_kiocb *req, unsigned int issue_flags); @@ -64,4 +66,8 @@ void io_netmsg_cache_free(const void *entry); static inline void io_netmsg_cache_free(const void *entry) { } +static inline void io_socket_bpf_populate(struct io_uring_bpf_ctx *bctx, + struct io_kiocb *req) +{ +} #endif -- cgit v1.2.3 From 8768770cf5d76d177fa2200e6957a372e61e06b5 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Mon, 19 Jan 2026 15:59:54 -0700 Subject: io_uring/bpf_filter: allow filtering on contents of struct open_how This adds custom filtering for IORING_OP_OPENAT and IORING_OP_OPENAT2, where the open_how flags, mode, and resolve can be checked by filters. Signed-off-by: Jens Axboe --- include/uapi/linux/io_uring/bpf_filter.h | 5 +++++ io_uring/bpf_filter.c | 6 ++++++ io_uring/openclose.c | 9 +++++++++ io_uring/openclose.h | 3 +++ 4 files changed, 23 insertions(+) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/io_uring/bpf_filter.h b/include/uapi/linux/io_uring/bpf_filter.h index 4dbc89bbbf10..220351b81bc0 100644 --- a/include/uapi/linux/io_uring/bpf_filter.h +++ b/include/uapi/linux/io_uring/bpf_filter.h @@ -22,6 +22,11 @@ struct io_uring_bpf_ctx { __u32 type; __u32 protocol; } socket; + struct { + __u64 flags; + __u64 mode; + __u64 resolve; + } open; }; }; diff --git a/io_uring/bpf_filter.c b/io_uring/bpf_filter.c index 889fa915fa54..ff723ec44828 100644 --- a/io_uring/bpf_filter.c +++ b/io_uring/bpf_filter.c @@ -12,6 +12,7 @@ #include "io_uring.h" #include "bpf_filter.h" #include "net.h" +#include "openclose.h" struct io_bpf_filter { struct bpf_prog *prog; @@ -40,6 +41,11 @@ static void io_uring_populate_bpf_ctx(struct io_uring_bpf_ctx *bctx, bctx->pdu_size = sizeof(bctx->socket); io_socket_bpf_populate(bctx, req); break; + case IORING_OP_OPENAT: + case IORING_OP_OPENAT2: + bctx->pdu_size = sizeof(bctx->open); + io_openat_bpf_populate(bctx, req); + break; } } diff --git a/io_uring/openclose.c b/io_uring/openclose.c index 15dde9bd6ff6..31c687adf873 100644 --- a/io_uring/openclose.c +++ b/io_uring/openclose.c @@ -85,6 +85,15 @@ static int __io_openat_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe return 0; } +void io_openat_bpf_populate(struct io_uring_bpf_ctx *bctx, struct io_kiocb *req) +{ + struct io_open *open = io_kiocb_to_cmd(req, struct io_open); + + bctx->open.flags = open->how.flags; + bctx->open.mode = open->how.mode; + bctx->open.resolve = open->how.resolve; +} + int io_openat_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe) { struct io_open *open = io_kiocb_to_cmd(req, struct io_open); diff --git a/io_uring/openclose.h b/io_uring/openclose.h index 4ca2a9935abc..566739920658 100644 --- a/io_uring/openclose.h +++ b/io_uring/openclose.h @@ -1,11 +1,14 @@ // SPDX-License-Identifier: GPL-2.0 +#include "bpf_filter.h" + int __io_close_fixed(struct io_ring_ctx *ctx, unsigned int issue_flags, unsigned int offset); int io_openat_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe); int io_openat(struct io_kiocb *req, unsigned int issue_flags); void io_open_cleanup(struct io_kiocb *req); +void io_openat_bpf_populate(struct io_uring_bpf_ctx *bctx, struct io_kiocb *req); int io_openat2_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe); int io_openat2(struct io_kiocb *req, unsigned int issue_flags); -- cgit v1.2.3 From cad3337bb6c3a2ba2307d6a9061e752e15681d2b Mon Sep 17 00:00:00 2001 From: Ilpo Järvinen Date: Fri, 19 Dec 2025 19:40:33 +0200 Subject: PCI: Add dword #defines for Bus Number + Secondary Latency Timer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit uapi/linux/pci_regs.h defines Primary/Secondary/Subordinate Bus Numbers and Secondary Latency Timer (PCIe r7.0, sec. 7.5.1.3) as byte register offsets, but in practice the code may read/write the entire dword. In the lack of #defines to handle the dword fields, the code ends up using literals which are not as easy to read. Add dword field masks for the Bus Number and Secondary Latency Timer fields and use them in probe.c. Signed-off-by: Ilpo Järvinen [bhelgaas: squash new #defines and uses together] Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20251219174036.16738-21-ilpo.jarvinen@linux.intel.com Link: https://patch.msgid.link/20251219174036.16738-22-ilpo.jarvinen@linux.intel.com --- drivers/pci/probe.c | 25 +++++++++++++------------ include/uapi/linux/pci_regs.h | 5 +++++ 2 files changed, 18 insertions(+), 12 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index ed4d26833640..53ec1879fb99 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -524,8 +524,8 @@ static void pci_read_bridge_windows(struct pci_dev *bridge) pci_read_config_dword(bridge, PCI_PRIMARY_BUS, &buses); res.flags = IORESOURCE_BUS; - res.start = (buses >> 8) & 0xff; - res.end = (buses >> 16) & 0xff; + res.start = FIELD_GET(PCI_SECONDARY_BUS_MASK, buses); + res.end = FIELD_GET(PCI_SUBORDINATE_BUS_MASK, buses); pci_info(bridge, "PCI bridge to %pR%s\n", &res, bridge->transparent ? " (subtractive decode)" : ""); @@ -1393,9 +1393,9 @@ static int pci_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev, pm_runtime_get_sync(&dev->dev); pci_read_config_dword(dev, PCI_PRIMARY_BUS, &buses); - primary = buses & 0xFF; - secondary = (buses >> 8) & 0xFF; - subordinate = (buses >> 16) & 0xFF; + primary = FIELD_GET(PCI_PRIMARY_BUS_MASK, buses); + secondary = FIELD_GET(PCI_SECONDARY_BUS_MASK, buses); + subordinate = FIELD_GET(PCI_SUBORDINATE_BUS_MASK, buses); pci_dbg(dev, "scanning [bus %02x-%02x] behind bridge, pass %d\n", secondary, subordinate, pass); @@ -1476,7 +1476,7 @@ static int pci_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev, * ranges. */ pci_write_config_dword(dev, PCI_PRIMARY_BUS, - buses & ~0xffffff); + buses & PCI_SEC_LATENCY_TIMER_MASK); goto out; } @@ -1507,18 +1507,19 @@ static int pci_scan_bridge_extend(struct pci_bus *bus, struct pci_dev *dev, if (available_buses) available_buses--; - buses = (buses & 0xff000000) - | ((unsigned int)(child->primary) << 0) - | ((unsigned int)(child->busn_res.start) << 8) - | ((unsigned int)(child->busn_res.end) << 16); + buses = (buses & PCI_SEC_LATENCY_TIMER_MASK) | + FIELD_PREP(PCI_PRIMARY_BUS_MASK, child->primary) | + FIELD_PREP(PCI_SECONDARY_BUS_MASK, child->busn_res.start) | + FIELD_PREP(PCI_SUBORDINATE_BUS_MASK, child->busn_res.end); /* * yenta.c forces a secondary latency timer of 176. * Copy that behaviour here. */ if (is_cardbus) { - buses &= ~0xff000000; - buses |= CARDBUS_LATENCY_TIMER << 24; + buses &= ~PCI_SEC_LATENCY_TIMER_MASK; + buses |= FIELD_PREP(PCI_SEC_LATENCY_TIMER_MASK, + CARDBUS_LATENCY_TIMER); } /* We need to blast all three values with a single write */ diff --git a/include/uapi/linux/pci_regs.h b/include/uapi/linux/pci_regs.h index 3add74ae2594..8be55ece2a21 100644 --- a/include/uapi/linux/pci_regs.h +++ b/include/uapi/linux/pci_regs.h @@ -132,6 +132,11 @@ #define PCI_SECONDARY_BUS 0x19 /* Secondary bus number */ #define PCI_SUBORDINATE_BUS 0x1a /* Highest bus number behind the bridge */ #define PCI_SEC_LATENCY_TIMER 0x1b /* Latency timer for secondary interface */ +/* Masks for dword-sized processing of Bus Number and Sec Latency Timer fields */ +#define PCI_PRIMARY_BUS_MASK 0x000000ff +#define PCI_SECONDARY_BUS_MASK 0x0000ff00 +#define PCI_SUBORDINATE_BUS_MASK 0x00ff0000 +#define PCI_SEC_LATENCY_TIMER_MASK 0xff000000 #define PCI_IO_BASE 0x1c /* I/O range behind the bridge */ #define PCI_IO_LIMIT 0x1d #define PCI_IO_RANGE_TYPE_MASK 0x0fUL /* I/O bridging type */ -- cgit v1.2.3 From e698127eb7249d6c70fa8f2bdba469c0e54f2e2b Mon Sep 17 00:00:00 2001 From: Jonathan Kim Date: Wed, 23 Jul 2025 10:07:28 -0400 Subject: drm/amdkfd: add extended capabilities to device snapshot Add additional capabilities reporting. Signed-off-by: Jonathan Kim Reviewed-by: James Zhu Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdkfd/kfd_debug.c | 1 + include/uapi/linux/kfd_ioctl.h | 2 ++ 2 files changed, 3 insertions(+) (limited to 'include/uapi/linux') diff --git a/drivers/gpu/drm/amd/amdkfd/kfd_debug.c b/drivers/gpu/drm/amd/amdkfd/kfd_debug.c index 27176b2dc714..8f8a0975f1a7 100644 --- a/drivers/gpu/drm/amd/amdkfd/kfd_debug.c +++ b/drivers/gpu/drm/amd/amdkfd/kfd_debug.c @@ -1108,6 +1108,7 @@ int kfd_dbg_trap_device_snapshot(struct kfd_process *target, device_info.num_xcc = NUM_XCC(pdd->dev->xcc_mask); device_info.capability = topo_dev->node_props.capability; device_info.debug_prop = topo_dev->node_props.debug_prop; + device_info.capability2 = topo_dev->node_props.capability2; if (exception_clear_mask) pdd->exception_status &= ~exception_clear_mask; diff --git a/include/uapi/linux/kfd_ioctl.h b/include/uapi/linux/kfd_ioctl.h index 047bcb1cc078..e72359370857 100644 --- a/include/uapi/linux/kfd_ioctl.h +++ b/include/uapi/linux/kfd_ioctl.h @@ -149,6 +149,8 @@ struct kfd_dbg_device_info_entry { __u32 num_xcc; __u32 capability; __u32 debug_prop; + __u32 capability2; + __u32 pad; }; /* For kfd_ioctl_set_memory_policy_args.default_policy and alternate_policy */ -- cgit v1.2.3 From d8316b837c2ca5f92e781fa1575095c0132ae3c1 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Tue, 6 Jan 2026 13:59:50 -0500 Subject: nfsd: add controls to set the minimum number of threads per pool Add a new "min_threads" variable to the nfsd_net, along with the corresponding netlink interface, to set that value from userland. Pass that value to svc_set_pool_threads() and svc_set_num_threads(). Signed-off-by: Jeff Layton Signed-off-by: Chuck Lever --- Documentation/netlink/specs/nfsd.yaml | 5 +++++ fs/nfsd/netlink.c | 5 +++-- fs/nfsd/netns.h | 6 ++++++ fs/nfsd/nfsctl.c | 6 ++++++ fs/nfsd/nfssvc.c | 4 ++-- fs/nfsd/trace.h | 19 +++++++++++++++++++ include/uapi/linux/nfsd_netlink.h | 1 + 7 files changed, 42 insertions(+), 4 deletions(-) (limited to 'include/uapi/linux') diff --git a/Documentation/netlink/specs/nfsd.yaml b/Documentation/netlink/specs/nfsd.yaml index 100363029e82..badb2fe57c98 100644 --- a/Documentation/netlink/specs/nfsd.yaml +++ b/Documentation/netlink/specs/nfsd.yaml @@ -78,6 +78,9 @@ attribute-sets: - name: scope type: string + - + name: min-threads + type: u32 - name: version attributes: @@ -159,6 +162,7 @@ operations: - gracetime - leasetime - scope + - min-threads - name: threads-get doc: get the number of running threads @@ -170,6 +174,7 @@ operations: - gracetime - leasetime - scope + - min-threads - name: version-set doc: set nfs enabled versions diff --git a/fs/nfsd/netlink.c b/fs/nfsd/netlink.c index ac51a44e1065..887525964451 100644 --- a/fs/nfsd/netlink.c +++ b/fs/nfsd/netlink.c @@ -24,11 +24,12 @@ const struct nla_policy nfsd_version_nl_policy[NFSD_A_VERSION_ENABLED + 1] = { }; /* NFSD_CMD_THREADS_SET - do */ -static const struct nla_policy nfsd_threads_set_nl_policy[NFSD_A_SERVER_SCOPE + 1] = { +static const struct nla_policy nfsd_threads_set_nl_policy[NFSD_A_SERVER_MIN_THREADS + 1] = { [NFSD_A_SERVER_THREADS] = { .type = NLA_U32, }, [NFSD_A_SERVER_GRACETIME] = { .type = NLA_U32, }, [NFSD_A_SERVER_LEASETIME] = { .type = NLA_U32, }, [NFSD_A_SERVER_SCOPE] = { .type = NLA_NUL_STRING, }, + [NFSD_A_SERVER_MIN_THREADS] = { .type = NLA_U32, }, }; /* NFSD_CMD_VERSION_SET - do */ @@ -57,7 +58,7 @@ static const struct genl_split_ops nfsd_nl_ops[] = { .cmd = NFSD_CMD_THREADS_SET, .doit = nfsd_nl_threads_set_doit, .policy = nfsd_threads_set_nl_policy, - .maxattr = NFSD_A_SERVER_SCOPE, + .maxattr = NFSD_A_SERVER_MIN_THREADS, .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, }, { diff --git a/fs/nfsd/netns.h b/fs/nfsd/netns.h index d83c68872c4c..9fa600602658 100644 --- a/fs/nfsd/netns.h +++ b/fs/nfsd/netns.h @@ -129,6 +129,12 @@ struct nfsd_net { seqlock_t writeverf_lock; unsigned char writeverf[8]; + /* + * Minimum number of threads to run per pool. If 0 then the + * min == max requested number of threads. + */ + unsigned int min_threads; + u32 clientid_base; u32 clientid_counter; u32 clverifier_counter; diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c index 084fc517e9e1..7a58e54760be 100644 --- a/fs/nfsd/nfsctl.c +++ b/fs/nfsd/nfsctl.c @@ -1642,6 +1642,10 @@ int nfsd_nl_threads_set_doit(struct sk_buff *skb, struct genl_info *info) scope = nla_data(attr); } + attr = info->attrs[NFSD_A_SERVER_MIN_THREADS]; + if (attr) + nn->min_threads = nla_get_u32(attr); + ret = nfsd_svc(nrpools, nthreads, net, get_current_cred(), scope); if (ret > 0) ret = 0; @@ -1681,6 +1685,8 @@ int nfsd_nl_threads_get_doit(struct sk_buff *skb, struct genl_info *info) nn->nfsd4_grace) || nla_put_u32(skb, NFSD_A_SERVER_LEASETIME, nn->nfsd4_lease) || + nla_put_u32(skb, NFSD_A_SERVER_MIN_THREADS, + nn->min_threads) || nla_put_string(skb, NFSD_A_SERVER_SCOPE, nn->nfsd_name); if (err) diff --git a/fs/nfsd/nfssvc.c b/fs/nfsd/nfssvc.c index 1e2570e3c754..0887ee601d3c 100644 --- a/fs/nfsd/nfssvc.c +++ b/fs/nfsd/nfssvc.c @@ -690,7 +690,7 @@ int nfsd_set_nrthreads(int n, int *nthreads, struct net *net) /* Special case: When n == 1, distribute threads equally among pools. */ if (n == 1) - return svc_set_num_threads(nn->nfsd_serv, 0, nthreads[0]); + return svc_set_num_threads(nn->nfsd_serv, nn->min_threads, nthreads[0]); if (n > nn->nfsd_serv->sv_nrpools) n = nn->nfsd_serv->sv_nrpools; @@ -718,7 +718,7 @@ int nfsd_set_nrthreads(int n, int *nthreads, struct net *net) for (i = 0; i < n; i++) { err = svc_set_pool_threads(nn->nfsd_serv, &nn->nfsd_serv->sv_pools[i], - 0, nthreads[i]); + nn->min_threads, nthreads[i]); if (err) goto out; } diff --git a/fs/nfsd/trace.h b/fs/nfsd/trace.h index 8885fd9bead9..d1d0b0dd0545 100644 --- a/fs/nfsd/trace.h +++ b/fs/nfsd/trace.h @@ -2164,6 +2164,25 @@ TRACE_EVENT(nfsd_ctl_maxblksize, ) ); +TRACE_EVENT(nfsd_ctl_minthreads, + TP_PROTO( + const struct net *net, + int minthreads + ), + TP_ARGS(net, minthreads), + TP_STRUCT__entry( + __field(unsigned int, netns_ino) + __field(int, minthreads) + ), + TP_fast_assign( + __entry->netns_ino = net->ns.inum; + __entry->minthreads = minthreads + ), + TP_printk("minthreads=%d", + __entry->minthreads + ) +); + TRACE_EVENT(nfsd_ctl_time, TP_PROTO( const struct net *net, diff --git a/include/uapi/linux/nfsd_netlink.h b/include/uapi/linux/nfsd_netlink.h index e157e2009ea8..e9efbc9e63d8 100644 --- a/include/uapi/linux/nfsd_netlink.h +++ b/include/uapi/linux/nfsd_netlink.h @@ -35,6 +35,7 @@ enum { NFSD_A_SERVER_GRACETIME, NFSD_A_SERVER_LEASETIME, NFSD_A_SERVER_SCOPE, + NFSD_A_SERVER_MIN_THREADS, __NFSD_A_SERVER_MAX, NFSD_A_SERVER_MAX = (__NFSD_A_SERVER_MAX - 1) -- cgit v1.2.3 From a006ed4ecd4905b69402980ad7d4e5f31bf44953 Mon Sep 17 00:00:00 2001 From: Eugenio Pérez Date: Mon, 19 Jan 2026 15:32:55 +0100 Subject: vduse: add v1 API definition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows the kernel to detect whether the userspace VDUSE device supports the VQ group and ASID features. VDUSE devices that don't set the V1 API will not receive the new messages, and vdpa device will be created with only one vq group and asid. The next patches implement the new feature incrementally, only enabling the VDUSE device to set the V1 API version by the end of the series. Acked-by: Jason Wang Reviewed-by: Xie Yongji Signed-off-by: Eugenio Pérez Signed-off-by: Michael S. Tsirkin Message-Id: <20260119143306.1818855-3-eperezma@redhat.com> --- include/uapi/linux/vduse.h | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/vduse.h b/include/uapi/linux/vduse.h index 10ad71aa00d6..ccb92a1efce0 100644 --- a/include/uapi/linux/vduse.h +++ b/include/uapi/linux/vduse.h @@ -10,6 +10,10 @@ #define VDUSE_API_VERSION 0 +/* VQ groups and ASID support */ + +#define VDUSE_API_VERSION_1 1 + /* * Get the version of VDUSE API that kernel supported (VDUSE_API_VERSION). * This is used for future extension. -- cgit v1.2.3 From 9350a09afd086771b0612c7b7c9583e8a1568135 Mon Sep 17 00:00:00 2001 From: Eugenio Pérez Date: Mon, 19 Jan 2026 15:32:56 +0100 Subject: vduse: add vq group support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows separate the different virtqueues in groups that shares the same address space. Asking the VDUSE device for the groups of the vq at the beginning as they're needed for the DMA API. Allocating 3 vq groups as net is the device that need the most groups: * Dataplane (guest passthrough) * CVQ * Shadowed vrings. Future versions of the series can include dynamic allocation of the groups array so VDUSE can declare more groups. Acked-by: Jason Wang Reviewed-by: Xie Yongji Signed-off-by: Eugenio Pérez Signed-off-by: Michael S. Tsirkin Message-Id: <20260119143306.1818855-4-eperezma@redhat.com> --- drivers/vdpa/vdpa_user/vduse_dev.c | 47 ++++++++++++++++++++++++++++++++++---- include/uapi/linux/vduse.h | 12 +++++++--- 2 files changed, 51 insertions(+), 8 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/vdpa/vdpa_user/vduse_dev.c b/drivers/vdpa/vdpa_user/vduse_dev.c index ae357d014564..5bffc25a266e 100644 --- a/drivers/vdpa/vdpa_user/vduse_dev.c +++ b/drivers/vdpa/vdpa_user/vduse_dev.c @@ -39,6 +39,7 @@ #define DRV_LICENSE "GPL v2" #define VDUSE_DEV_MAX (1U << MINORBITS) +#define VDUSE_DEV_MAX_GROUPS 0xffff #define VDUSE_MAX_BOUNCE_SIZE (1024 * 1024 * 1024) #define VDUSE_MIN_BOUNCE_SIZE (1024 * 1024) #define VDUSE_BOUNCE_SIZE (64 * 1024 * 1024) @@ -58,6 +59,7 @@ struct vduse_virtqueue { struct vdpa_vq_state state; bool ready; bool kicked; + u32 group; spinlock_t kick_lock; spinlock_t irq_lock; struct eventfd_ctx *kickfd; @@ -114,6 +116,7 @@ struct vduse_dev { u8 status; u32 vq_num; u32 vq_align; + u32 ngroups; struct vduse_umem *umem; struct mutex mem_lock; unsigned int bounce_size; @@ -592,6 +595,16 @@ static int vduse_vdpa_set_vq_state(struct vdpa_device *vdpa, u16 idx, return 0; } +static u32 vduse_get_vq_group(struct vdpa_device *vdpa, u16 idx) +{ + struct vduse_dev *dev = vdpa_to_vduse(vdpa); + + if (dev->api_version < VDUSE_API_VERSION_1) + return 0; + + return dev->vqs[idx]->group; +} + static int vduse_vdpa_get_vq_state(struct vdpa_device *vdpa, u16 idx, struct vdpa_vq_state *state) { @@ -789,6 +802,7 @@ static const struct vdpa_config_ops vduse_vdpa_config_ops = { .set_vq_cb = vduse_vdpa_set_vq_cb, .set_vq_num = vduse_vdpa_set_vq_num, .get_vq_size = vduse_vdpa_get_vq_size, + .get_vq_group = vduse_get_vq_group, .set_vq_ready = vduse_vdpa_set_vq_ready, .get_vq_ready = vduse_vdpa_get_vq_ready, .set_vq_state = vduse_vdpa_set_vq_state, @@ -1252,12 +1266,24 @@ static long vduse_dev_ioctl(struct file *file, unsigned int cmd, if (config.index >= dev->vq_num) break; - if (!is_mem_zero((const char *)config.reserved, - sizeof(config.reserved))) + if (dev->api_version < VDUSE_API_VERSION_1) { + if (config.group) + break; + } else { + if (config.group >= dev->ngroups) + break; + if (dev->status & VIRTIO_CONFIG_S_DRIVER_OK) + break; + } + + if (config.reserved1 || + !is_mem_zero((const char *)config.reserved2, + sizeof(config.reserved2))) break; index = array_index_nospec(config.index, dev->vq_num); dev->vqs[index]->num_max = config.max_size; + dev->vqs[index]->group = config.group; ret = 0; break; } @@ -1737,12 +1763,20 @@ static bool features_is_valid(struct vduse_dev_config *config) return true; } -static bool vduse_validate_config(struct vduse_dev_config *config) +static bool vduse_validate_config(struct vduse_dev_config *config, + u64 api_version) { if (!is_mem_zero((const char *)config->reserved, sizeof(config->reserved))) return false; + if (api_version < VDUSE_API_VERSION_1 && config->ngroups) + return false; + + if (api_version >= VDUSE_API_VERSION_1 && + (!config->ngroups || config->ngroups > VDUSE_DEV_MAX_GROUPS)) + return false; + if (config->vq_align > PAGE_SIZE) return false; @@ -1858,6 +1892,9 @@ static int vduse_create_dev(struct vduse_dev_config *config, dev->device_features = config->features; dev->device_id = config->device_id; dev->vendor_id = config->vendor_id; + dev->ngroups = (dev->api_version < VDUSE_API_VERSION_1) + ? 1 + : config->ngroups; dev->name = kstrdup(config->name, GFP_KERNEL); if (!dev->name) goto err_str; @@ -1936,7 +1973,7 @@ static long vduse_ioctl(struct file *file, unsigned int cmd, break; ret = -EINVAL; - if (vduse_validate_config(&config) == false) + if (!vduse_validate_config(&config, control->api_version)) break; buf = vmemdup_user(argp + size, config.config_size); @@ -2017,7 +2054,7 @@ static int vduse_dev_init_vdpa(struct vduse_dev *dev, const char *name) vdev = vdpa_alloc_device(struct vduse_vdpa, vdpa, dev->dev, &vduse_vdpa_config_ops, &vduse_map_ops, - 1, 1, name, true); + dev->ngroups, 1, name, true); if (IS_ERR(vdev)) return PTR_ERR(vdev); diff --git a/include/uapi/linux/vduse.h b/include/uapi/linux/vduse.h index ccb92a1efce0..a3d51cf6df3a 100644 --- a/include/uapi/linux/vduse.h +++ b/include/uapi/linux/vduse.h @@ -31,6 +31,7 @@ * @features: virtio features * @vq_num: the number of virtqueues * @vq_align: the allocation alignment of virtqueue's metadata + * @ngroups: number of vq groups that VDUSE device declares * @reserved: for future use, needs to be initialized to zero * @config_size: the size of the configuration space * @config: the buffer of the configuration space @@ -45,7 +46,8 @@ struct vduse_dev_config { __u64 features; __u32 vq_num; __u32 vq_align; - __u32 reserved[13]; + __u32 ngroups; /* if VDUSE_API_VERSION >= 1 */ + __u32 reserved[12]; __u32 config_size; __u8 config[]; }; @@ -122,14 +124,18 @@ struct vduse_config_data { * struct vduse_vq_config - basic configuration of a virtqueue * @index: virtqueue index * @max_size: the max size of virtqueue - * @reserved: for future use, needs to be initialized to zero + * @reserved1: for future use, needs to be initialized to zero + * @group: virtqueue group + * @reserved2: for future use, needs to be initialized to zero * * Structure used by VDUSE_VQ_SETUP ioctl to setup a virtqueue. */ struct vduse_vq_config { __u32 index; __u16 max_size; - __u16 reserved[13]; + __u16 reserved1; + __u32 group; + __u16 reserved2[10]; }; /* -- cgit v1.2.3 From 079212f6877e5d07308c8998a8fbc7539ca3f8f3 Mon Sep 17 00:00:00 2001 From: Eugenio Pérez Date: Mon, 19 Jan 2026 15:33:04 +0100 Subject: vduse: add vq group asid support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for assigning Address Space Identifiers (ASIDs) to each VQ group. This enables mapping each group into a distinct memory space. The vq group to ASID association is protected by a rwlock now. But the mutex domain_lock keeps protecting the domains of all ASIDs, as some operations like the one related with the bounce buffer size still requires to lock all the ASIDs. Acked-by: Jason Wang Signed-off-by: Eugenio Pérez Signed-off-by: Michael S. Tsirkin Message-Id: <20260119143306.1818855-12-eperezma@redhat.com> --- drivers/vdpa/vdpa_user/vduse_dev.c | 385 ++++++++++++++++++++++++------------- include/uapi/linux/vduse.h | 66 ++++++- 2 files changed, 315 insertions(+), 136 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/vdpa/vdpa_user/vduse_dev.c b/drivers/vdpa/vdpa_user/vduse_dev.c index d658f3e1cebf..2727c0c26003 100644 --- a/drivers/vdpa/vdpa_user/vduse_dev.c +++ b/drivers/vdpa/vdpa_user/vduse_dev.c @@ -9,6 +9,7 @@ */ #include "linux/virtio_net.h" +#include #include #include #include @@ -41,6 +42,7 @@ #define VDUSE_DEV_MAX (1U << MINORBITS) #define VDUSE_DEV_MAX_GROUPS 0xffff +#define VDUSE_DEV_MAX_AS 0xffff #define VDUSE_MAX_BOUNCE_SIZE (1024 * 1024 * 1024) #define VDUSE_MIN_BOUNCE_SIZE (1024 * 1024) #define VDUSE_BOUNCE_SIZE (64 * 1024 * 1024) @@ -86,7 +88,15 @@ struct vduse_umem { struct mm_struct *mm; }; +struct vduse_as { + struct vduse_iova_domain *domain; + struct vduse_umem *umem; + struct mutex mem_lock; +}; + struct vduse_vq_group { + rwlock_t as_lock; + struct vduse_as *as; /* Protected by as_lock */ struct vduse_dev *dev; }; @@ -94,7 +104,7 @@ struct vduse_dev { struct vduse_vdpa *vdev; struct device *dev; struct vduse_virtqueue **vqs; - struct vduse_iova_domain *domain; + struct vduse_as *as; char *name; struct mutex lock; spinlock_t msg_lock; @@ -122,9 +132,8 @@ struct vduse_dev { u32 vq_num; u32 vq_align; u32 ngroups; - struct vduse_umem *umem; + u32 nas; struct vduse_vq_group *groups; - struct mutex mem_lock; unsigned int bounce_size; struct mutex domain_lock; }; @@ -314,7 +323,7 @@ static int vduse_dev_set_status(struct vduse_dev *dev, u8 status) return vduse_dev_msg_sync(dev, &msg); } -static int vduse_dev_update_iotlb(struct vduse_dev *dev, +static int vduse_dev_update_iotlb(struct vduse_dev *dev, u32 asid, u64 start, u64 last) { struct vduse_dev_msg msg = { 0 }; @@ -323,8 +332,14 @@ static int vduse_dev_update_iotlb(struct vduse_dev *dev, return -EINVAL; msg.req.type = VDUSE_UPDATE_IOTLB; - msg.req.iova.start = start; - msg.req.iova.last = last; + if (dev->api_version < VDUSE_API_VERSION_1) { + msg.req.iova.start = start; + msg.req.iova.last = last; + } else { + msg.req.iova_v2.start = start; + msg.req.iova_v2.last = last; + msg.req.iova_v2.asid = asid; + } return vduse_dev_msg_sync(dev, &msg); } @@ -439,11 +454,14 @@ static __poll_t vduse_dev_poll(struct file *file, poll_table *wait) static void vduse_dev_reset(struct vduse_dev *dev) { int i; - struct vduse_iova_domain *domain = dev->domain; /* The coherent mappings are handled in vduse_dev_free_coherent() */ - if (domain && domain->bounce_map) - vduse_domain_reset_bounce_map(domain); + for (i = 0; i < dev->nas; i++) { + struct vduse_iova_domain *domain = dev->as[i].domain; + + if (domain && domain->bounce_map) + vduse_domain_reset_bounce_map(domain); + } down_write(&dev->rwsem); @@ -622,6 +640,42 @@ static union virtio_map vduse_get_vq_map(struct vdpa_device *vdpa, u16 idx) return ret; } +DEFINE_GUARD(vq_group_as_read_lock, struct vduse_vq_group *, + if (_T->dev->nas > 1) + read_lock(&_T->as_lock), + if (_T->dev->nas > 1) + read_unlock(&_T->as_lock)) + +DEFINE_GUARD(vq_group_as_write_lock, struct vduse_vq_group *, + if (_T->dev->nas > 1) + write_lock(&_T->as_lock), + if (_T->dev->nas > 1) + write_unlock(&_T->as_lock)) + +static int vduse_set_group_asid(struct vdpa_device *vdpa, unsigned int group, + unsigned int asid) +{ + struct vduse_dev *dev = vdpa_to_vduse(vdpa); + struct vduse_dev_msg msg = { 0 }; + int r; + + if (dev->api_version < VDUSE_API_VERSION_1) + return -EINVAL; + + msg.req.type = VDUSE_SET_VQ_GROUP_ASID; + msg.req.vq_group_asid.group = group; + msg.req.vq_group_asid.asid = asid; + + r = vduse_dev_msg_sync(dev, &msg); + if (r < 0) + return r; + + guard(vq_group_as_write_lock)(&dev->groups[group]); + dev->groups[group].as = &dev->as[asid]; + + return 0; +} + static int vduse_vdpa_get_vq_state(struct vdpa_device *vdpa, u16 idx, struct vdpa_vq_state *state) { @@ -793,13 +847,13 @@ static int vduse_vdpa_set_map(struct vdpa_device *vdpa, struct vduse_dev *dev = vdpa_to_vduse(vdpa); int ret; - ret = vduse_domain_set_map(dev->domain, iotlb); + ret = vduse_domain_set_map(dev->as[asid].domain, iotlb); if (ret) return ret; - ret = vduse_dev_update_iotlb(dev, 0ULL, ULLONG_MAX); + ret = vduse_dev_update_iotlb(dev, asid, 0ULL, ULLONG_MAX); if (ret) { - vduse_domain_clear_map(dev->domain, iotlb); + vduse_domain_clear_map(dev->as[asid].domain, iotlb); return ret; } @@ -842,6 +896,7 @@ static const struct vdpa_config_ops vduse_vdpa_config_ops = { .get_vq_affinity = vduse_vdpa_get_vq_affinity, .reset = vduse_vdpa_reset, .set_map = vduse_vdpa_set_map, + .set_group_asid = vduse_set_group_asid, .get_vq_map = vduse_get_vq_map, .free = vduse_vdpa_free, }; @@ -850,15 +905,13 @@ static void vduse_dev_sync_single_for_device(union virtio_map token, dma_addr_t dma_addr, size_t size, enum dma_data_direction dir) { - struct vduse_dev *vdev; struct vduse_iova_domain *domain; if (!token.group) return; - vdev = token.group->dev; - domain = vdev->domain; - + guard(vq_group_as_read_lock)(token.group); + domain = token.group->as->domain; vduse_domain_sync_single_for_device(domain, dma_addr, size, dir); } @@ -866,15 +919,13 @@ static void vduse_dev_sync_single_for_cpu(union virtio_map token, dma_addr_t dma_addr, size_t size, enum dma_data_direction dir) { - struct vduse_dev *vdev; struct vduse_iova_domain *domain; if (!token.group) return; - vdev = token.group->dev; - domain = vdev->domain; - + guard(vq_group_as_read_lock)(token.group); + domain = token.group->as->domain; vduse_domain_sync_single_for_cpu(domain, dma_addr, size, dir); } @@ -883,15 +934,13 @@ static dma_addr_t vduse_dev_map_page(union virtio_map token, struct page *page, enum dma_data_direction dir, unsigned long attrs) { - struct vduse_dev *vdev; struct vduse_iova_domain *domain; if (!token.group) return DMA_MAPPING_ERROR; - vdev = token.group->dev; - domain = vdev->domain; - + guard(vq_group_as_read_lock)(token.group); + domain = token.group->as->domain; return vduse_domain_map_page(domain, page, offset, size, dir, attrs); } @@ -899,23 +948,19 @@ static void vduse_dev_unmap_page(union virtio_map token, dma_addr_t dma_addr, size_t size, enum dma_data_direction dir, unsigned long attrs) { - struct vduse_dev *vdev; struct vduse_iova_domain *domain; if (!token.group) return; - vdev = token.group->dev; - domain = vdev->domain; - - return vduse_domain_unmap_page(domain, dma_addr, size, dir, attrs); + guard(vq_group_as_read_lock)(token.group); + domain = token.group->as->domain; + vduse_domain_unmap_page(domain, dma_addr, size, dir, attrs); } static void *vduse_dev_alloc_coherent(union virtio_map token, size_t size, dma_addr_t *dma_addr, gfp_t flag) { - struct vduse_dev *vdev; - struct vduse_iova_domain *domain; void *addr; *dma_addr = DMA_MAPPING_ERROR; @@ -926,11 +971,15 @@ static void *vduse_dev_alloc_coherent(union virtio_map token, size_t size, if (!addr) return NULL; - vdev = token.group->dev; - domain = vdev->domain; - *dma_addr = vduse_domain_alloc_coherent(domain, size, addr); - if (*dma_addr == DMA_MAPPING_ERROR) - goto err; + { + struct vduse_iova_domain *domain; + + guard(vq_group_as_read_lock)(token.group); + domain = token.group->as->domain; + *dma_addr = vduse_domain_alloc_coherent(domain, size, addr); + if (*dma_addr == DMA_MAPPING_ERROR) + goto err; + } return addr; @@ -943,31 +992,27 @@ static void vduse_dev_free_coherent(union virtio_map token, size_t size, void *vaddr, dma_addr_t dma_addr, unsigned long attrs) { - struct vduse_dev *vdev; - struct vduse_iova_domain *domain; - if (!token.group) return; - vdev = token.group->dev; - domain = vdev->domain; + { + struct vduse_iova_domain *domain; + + guard(vq_group_as_read_lock)(token.group); + domain = token.group->as->domain; + vduse_domain_free_coherent(domain, size, dma_addr, attrs); + } - vduse_domain_free_coherent(domain, size, dma_addr, attrs); free_pages_exact(vaddr, size); } static bool vduse_dev_need_sync(union virtio_map token, dma_addr_t dma_addr) { - struct vduse_dev *vdev; - struct vduse_iova_domain *domain; - if (!token.group) return false; - vdev = token.group->dev; - domain = vdev->domain; - - return dma_addr < domain->bounce_size; + guard(vq_group_as_read_lock)(token.group); + return dma_addr < token.group->as->domain->bounce_size; } static int vduse_dev_mapping_error(union virtio_map token, dma_addr_t dma_addr) @@ -979,16 +1024,11 @@ static int vduse_dev_mapping_error(union virtio_map token, dma_addr_t dma_addr) static size_t vduse_dev_max_mapping_size(union virtio_map token) { - struct vduse_dev *vdev; - struct vduse_iova_domain *domain; - if (!token.group) return 0; - vdev = token.group->dev; - domain = vdev->domain; - - return domain->bounce_size; + guard(vq_group_as_read_lock)(token.group); + return token.group->as->domain->bounce_size; } static const struct virtio_map_ops vduse_map_ops = { @@ -1128,39 +1168,40 @@ unlock: return ret; } -static int vduse_dev_dereg_umem(struct vduse_dev *dev, +static int vduse_dev_dereg_umem(struct vduse_dev *dev, u32 asid, u64 iova, u64 size) { int ret; - mutex_lock(&dev->mem_lock); + mutex_lock(&dev->as[asid].mem_lock); ret = -ENOENT; - if (!dev->umem) + if (!dev->as[asid].umem) goto unlock; ret = -EINVAL; - if (!dev->domain) + if (!dev->as[asid].domain) goto unlock; - if (dev->umem->iova != iova || size != dev->domain->bounce_size) + if (dev->as[asid].umem->iova != iova || + size != dev->as[asid].domain->bounce_size) goto unlock; - vduse_domain_remove_user_bounce_pages(dev->domain); - unpin_user_pages_dirty_lock(dev->umem->pages, - dev->umem->npages, true); - atomic64_sub(dev->umem->npages, &dev->umem->mm->pinned_vm); - mmdrop(dev->umem->mm); - vfree(dev->umem->pages); - kfree(dev->umem); - dev->umem = NULL; + vduse_domain_remove_user_bounce_pages(dev->as[asid].domain); + unpin_user_pages_dirty_lock(dev->as[asid].umem->pages, + dev->as[asid].umem->npages, true); + atomic64_sub(dev->as[asid].umem->npages, &dev->as[asid].umem->mm->pinned_vm); + mmdrop(dev->as[asid].umem->mm); + vfree(dev->as[asid].umem->pages); + kfree(dev->as[asid].umem); + dev->as[asid].umem = NULL; ret = 0; unlock: - mutex_unlock(&dev->mem_lock); + mutex_unlock(&dev->as[asid].mem_lock); return ret; } static int vduse_dev_reg_umem(struct vduse_dev *dev, - u64 iova, u64 uaddr, u64 size) + u32 asid, u64 iova, u64 uaddr, u64 size) { struct page **page_list = NULL; struct vduse_umem *umem = NULL; @@ -1168,14 +1209,14 @@ static int vduse_dev_reg_umem(struct vduse_dev *dev, unsigned long npages, lock_limit; int ret; - if (!dev->domain || !dev->domain->bounce_map || - size != dev->domain->bounce_size || + if (!dev->as[asid].domain || !dev->as[asid].domain->bounce_map || + size != dev->as[asid].domain->bounce_size || iova != 0 || uaddr & ~PAGE_MASK) return -EINVAL; - mutex_lock(&dev->mem_lock); + mutex_lock(&dev->as[asid].mem_lock); ret = -EEXIST; - if (dev->umem) + if (dev->as[asid].umem) goto unlock; ret = -ENOMEM; @@ -1199,7 +1240,7 @@ static int vduse_dev_reg_umem(struct vduse_dev *dev, goto out; } - ret = vduse_domain_add_user_bounce_pages(dev->domain, + ret = vduse_domain_add_user_bounce_pages(dev->as[asid].domain, page_list, pinned); if (ret) goto out; @@ -1212,7 +1253,7 @@ static int vduse_dev_reg_umem(struct vduse_dev *dev, umem->mm = current->mm; mmgrab(current->mm); - dev->umem = umem; + dev->as[asid].umem = umem; out: if (ret && pinned > 0) unpin_user_pages(page_list, pinned); @@ -1223,7 +1264,7 @@ unlock: vfree(page_list); kfree(umem); } - mutex_unlock(&dev->mem_lock); + mutex_unlock(&dev->as[asid].mem_lock); return ret; } @@ -1244,44 +1285,47 @@ static void vduse_vq_update_effective_cpu(struct vduse_virtqueue *vq) } static int vduse_dev_iotlb_entry(struct vduse_dev *dev, - struct vduse_iotlb_entry *entry, + struct vduse_iotlb_entry_v2 *entry, struct file **f, uint64_t *capability) { + u32 asid; int r = -EINVAL; struct vhost_iotlb_map *map; - if (entry->start > entry->last) + if (entry->v1.start > entry->v1.last || entry->asid >= dev->nas) return -EINVAL; + asid = array_index_nospec(entry->asid, dev->nas); mutex_lock(&dev->domain_lock); - if (!dev->domain) + + if (!dev->as[asid].domain) goto out; - spin_lock(&dev->domain->iotlb_lock); - map = vhost_iotlb_itree_first(dev->domain->iotlb, entry->start, - entry->last); + spin_lock(&dev->as[asid].domain->iotlb_lock); + map = vhost_iotlb_itree_first(dev->as[asid].domain->iotlb, + entry->v1.start, entry->v1.last); if (map) { if (f) { const struct vdpa_map_file *map_file; map_file = (struct vdpa_map_file *)map->opaque; - entry->offset = map_file->offset; + entry->v1.offset = map_file->offset; *f = get_file(map_file->file); } - entry->start = map->start; - entry->last = map->last; - entry->perm = map->perm; + entry->v1.start = map->start; + entry->v1.last = map->last; + entry->v1.perm = map->perm; if (capability) { *capability = 0; - if (dev->domain->bounce_map && map->start == 0 && - map->last == dev->domain->bounce_size - 1) + if (dev->as[asid].domain->bounce_map && map->start == 0 && + map->last == dev->as[asid].domain->bounce_size - 1) *capability |= VDUSE_IOVA_CAP_UMEM; } r = 0; } - spin_unlock(&dev->domain->iotlb_lock); + spin_unlock(&dev->as[asid].domain->iotlb_lock); out: mutex_unlock(&dev->domain_lock); @@ -1299,12 +1343,29 @@ static long vduse_dev_ioctl(struct file *file, unsigned int cmd, return -EPERM; switch (cmd) { - case VDUSE_IOTLB_GET_FD: { - struct vduse_iotlb_entry entry; + case VDUSE_IOTLB_GET_FD: + case VDUSE_IOTLB_GET_FD2: { + struct vduse_iotlb_entry_v2 entry = {0}; struct file *f = NULL; + ret = -ENOIOCTLCMD; + if (dev->api_version < VDUSE_API_VERSION_1 && + cmd == VDUSE_IOTLB_GET_FD2) + break; + ret = -EFAULT; - if (copy_from_user(&entry, argp, sizeof(entry))) + if (cmd == VDUSE_IOTLB_GET_FD2) { + if (copy_from_user(&entry, argp, sizeof(entry))) + break; + } else { + if (copy_from_user(&entry.v1, argp, + sizeof(entry.v1))) + break; + } + + ret = -EINVAL; + if (!is_mem_zero((const char *)entry.reserved, + sizeof(entry.reserved))) break; ret = vduse_dev_iotlb_entry(dev, &entry, &f, NULL); @@ -1315,12 +1376,19 @@ static long vduse_dev_ioctl(struct file *file, unsigned int cmd, if (!f) break; - ret = -EFAULT; - if (copy_to_user(argp, &entry, sizeof(entry))) { + if (cmd == VDUSE_IOTLB_GET_FD2) + ret = copy_to_user(argp, &entry, + sizeof(entry)); + else + ret = copy_to_user(argp, &entry.v1, + sizeof(entry.v1)); + + if (ret) { + ret = -EFAULT; fput(f); break; } - ret = receive_fd(f, NULL, perm_to_file_flags(entry.perm)); + ret = receive_fd(f, NULL, perm_to_file_flags(entry.v1.perm)); fput(f); break; } @@ -1465,6 +1533,7 @@ static long vduse_dev_ioctl(struct file *file, unsigned int cmd, } case VDUSE_IOTLB_REG_UMEM: { struct vduse_iova_umem umem; + u32 asid; ret = -EFAULT; if (copy_from_user(&umem, argp, sizeof(umem))) @@ -1472,17 +1541,21 @@ static long vduse_dev_ioctl(struct file *file, unsigned int cmd, ret = -EINVAL; if (!is_mem_zero((const char *)umem.reserved, - sizeof(umem.reserved))) + sizeof(umem.reserved)) || + (dev->api_version < VDUSE_API_VERSION_1 && + umem.asid != 0) || umem.asid >= dev->nas) break; mutex_lock(&dev->domain_lock); - ret = vduse_dev_reg_umem(dev, umem.iova, + asid = array_index_nospec(umem.asid, dev->nas); + ret = vduse_dev_reg_umem(dev, asid, umem.iova, umem.uaddr, umem.size); mutex_unlock(&dev->domain_lock); break; } case VDUSE_IOTLB_DEREG_UMEM: { struct vduse_iova_umem umem; + u32 asid; ret = -EFAULT; if (copy_from_user(&umem, argp, sizeof(umem))) @@ -1490,17 +1563,22 @@ static long vduse_dev_ioctl(struct file *file, unsigned int cmd, ret = -EINVAL; if (!is_mem_zero((const char *)umem.reserved, - sizeof(umem.reserved))) + sizeof(umem.reserved)) || + (dev->api_version < VDUSE_API_VERSION_1 && + umem.asid != 0) || + umem.asid >= dev->nas) break; + mutex_lock(&dev->domain_lock); - ret = vduse_dev_dereg_umem(dev, umem.iova, + asid = array_index_nospec(umem.asid, dev->nas); + ret = vduse_dev_dereg_umem(dev, asid, umem.iova, umem.size); mutex_unlock(&dev->domain_lock); break; } case VDUSE_IOTLB_GET_INFO: { struct vduse_iova_info info; - struct vduse_iotlb_entry entry; + struct vduse_iotlb_entry_v2 entry; ret = -EFAULT; if (copy_from_user(&info, argp, sizeof(info))) @@ -1510,15 +1588,23 @@ static long vduse_dev_ioctl(struct file *file, unsigned int cmd, sizeof(info.reserved))) break; - entry.start = info.start; - entry.last = info.last; + if (dev->api_version < VDUSE_API_VERSION_1) { + if (info.asid) + break; + } else if (info.asid >= dev->nas) + break; + + entry.v1.start = info.start; + entry.v1.last = info.last; + entry.asid = info.asid; ret = vduse_dev_iotlb_entry(dev, &entry, NULL, &info.capability); if (ret < 0) break; - info.start = entry.start; - info.last = entry.last; + info.start = entry.v1.start; + info.last = entry.v1.last; + info.asid = entry.asid; ret = -EFAULT; if (copy_to_user(argp, &info, sizeof(info))) @@ -1540,8 +1626,10 @@ static int vduse_dev_release(struct inode *inode, struct file *file) struct vduse_dev *dev = file->private_data; mutex_lock(&dev->domain_lock); - if (dev->domain) - vduse_dev_dereg_umem(dev, 0, dev->domain->bounce_size); + for (int i = 0; i < dev->nas; i++) + if (dev->as[i].domain) + vduse_dev_dereg_umem(dev, i, 0, + dev->as[i].domain->bounce_size); mutex_unlock(&dev->domain_lock); spin_lock(&dev->msg_lock); /* Make sure the inflight messages can processed after reconncection */ @@ -1760,7 +1848,6 @@ static struct vduse_dev *vduse_dev_create(void) return NULL; mutex_init(&dev->lock); - mutex_init(&dev->mem_lock); mutex_init(&dev->domain_lock); spin_lock_init(&dev->msg_lock); INIT_LIST_HEAD(&dev->send_list); @@ -1811,8 +1898,11 @@ static int vduse_destroy_dev(char *name) idr_remove(&vduse_idr, dev->minor); kvfree(dev->config); vduse_dev_deinit_vqs(dev); - if (dev->domain) - vduse_domain_destroy(dev->domain); + for (int i = 0; i < dev->nas; i++) { + if (dev->as[i].domain) + vduse_domain_destroy(dev->as[i].domain); + } + kfree(dev->as); kfree(dev->name); kfree(dev->groups); vduse_dev_destroy(dev); @@ -1859,12 +1949,17 @@ static bool vduse_validate_config(struct vduse_dev_config *config, sizeof(config->reserved))) return false; - if (api_version < VDUSE_API_VERSION_1 && config->ngroups) + if (api_version < VDUSE_API_VERSION_1 && + (config->ngroups || config->nas)) return false; - if (api_version >= VDUSE_API_VERSION_1 && - (!config->ngroups || config->ngroups > VDUSE_DEV_MAX_GROUPS)) - return false; + if (api_version >= VDUSE_API_VERSION_1) { + if (!config->ngroups || config->ngroups > VDUSE_DEV_MAX_GROUPS) + return false; + + if (!config->nas || config->nas > VDUSE_DEV_MAX_AS) + return false; + } if (config->vq_align > PAGE_SIZE) return false; @@ -1929,7 +2024,8 @@ static ssize_t bounce_size_store(struct device *device, ret = -EPERM; mutex_lock(&dev->domain_lock); - if (dev->domain) + /* Assuming that if the first domain is allocated, all are allocated */ + if (dev->as[0].domain) goto unlock; ret = kstrtouint(buf, 10, &bounce_size); @@ -1981,6 +2077,14 @@ static int vduse_create_dev(struct vduse_dev_config *config, dev->device_features = config->features; dev->device_id = config->device_id; dev->vendor_id = config->vendor_id; + + dev->nas = (dev->api_version < VDUSE_API_VERSION_1) ? 1 : config->nas; + dev->as = kcalloc(dev->nas, sizeof(dev->as[0]), GFP_KERNEL); + if (!dev->as) + goto err_as; + for (int i = 0; i < dev->nas; i++) + mutex_init(&dev->as[i].mem_lock); + dev->ngroups = (dev->api_version < VDUSE_API_VERSION_1) ? 1 : config->ngroups; @@ -1988,8 +2092,11 @@ static int vduse_create_dev(struct vduse_dev_config *config, GFP_KERNEL); if (!dev->groups) goto err_vq_groups; - for (u32 i = 0; i < dev->ngroups; ++i) + for (u32 i = 0; i < dev->ngroups; ++i) { dev->groups[i].dev = dev; + rwlock_init(&dev->groups[i].as_lock); + dev->groups[i].as = &dev->as[0]; + } dev->name = kstrdup(config->name, GFP_KERNEL); if (!dev->name) @@ -2029,6 +2136,8 @@ err_idr: err_str: kfree(dev->groups); err_vq_groups: + kfree(dev->as); +err_as: vduse_dev_destroy(dev); err: return ret; @@ -2152,7 +2261,7 @@ static int vduse_dev_init_vdpa(struct vduse_dev *dev, const char *name) vdev = vdpa_alloc_device(struct vduse_vdpa, vdpa, dev->dev, &vduse_vdpa_config_ops, &vduse_map_ops, - dev->ngroups, 1, name, true); + dev->ngroups, dev->nas, name, true); if (IS_ERR(vdev)) return PTR_ERR(vdev); @@ -2167,7 +2276,8 @@ static int vdpa_dev_add(struct vdpa_mgmt_dev *mdev, const char *name, const struct vdpa_dev_set_config *config) { struct vduse_dev *dev; - int ret; + size_t domain_bounce_size; + int ret, i; mutex_lock(&vduse_lock); dev = vduse_find_dev(name); @@ -2181,29 +2291,38 @@ static int vdpa_dev_add(struct vdpa_mgmt_dev *mdev, const char *name, return ret; mutex_lock(&dev->domain_lock); - if (!dev->domain) - dev->domain = vduse_domain_create(VDUSE_IOVA_SIZE - 1, - dev->bounce_size); - mutex_unlock(&dev->domain_lock); - if (!dev->domain) { - ret = -ENOMEM; - goto domain_err; + ret = 0; + + domain_bounce_size = dev->bounce_size / dev->nas; + for (i = 0; i < dev->nas; ++i) { + dev->as[i].domain = vduse_domain_create(VDUSE_IOVA_SIZE - 1, + domain_bounce_size); + if (!dev->as[i].domain) { + ret = -ENOMEM; + goto err; + } } + mutex_unlock(&dev->domain_lock); + ret = _vdpa_register_device(&dev->vdev->vdpa, dev->vq_num); - if (ret) { - goto register_err; - } + if (ret) + goto err_register; return 0; -register_err: +err_register: mutex_lock(&dev->domain_lock); - vduse_domain_destroy(dev->domain); - dev->domain = NULL; + +err: + for (int j = 0; j < i; j++) { + if (dev->as[j].domain) { + vduse_domain_destroy(dev->as[j].domain); + dev->as[j].domain = NULL; + } + } mutex_unlock(&dev->domain_lock); -domain_err: put_device(&dev->vdev->vdpa.dev); return ret; diff --git a/include/uapi/linux/vduse.h b/include/uapi/linux/vduse.h index a3d51cf6df3a..68b4287f9fac 100644 --- a/include/uapi/linux/vduse.h +++ b/include/uapi/linux/vduse.h @@ -32,6 +32,7 @@ * @vq_num: the number of virtqueues * @vq_align: the allocation alignment of virtqueue's metadata * @ngroups: number of vq groups that VDUSE device declares + * @nas: number of address spaces that VDUSE device declares * @reserved: for future use, needs to be initialized to zero * @config_size: the size of the configuration space * @config: the buffer of the configuration space @@ -47,7 +48,8 @@ struct vduse_dev_config { __u32 vq_num; __u32 vq_align; __u32 ngroups; /* if VDUSE_API_VERSION >= 1 */ - __u32 reserved[12]; + __u32 nas; /* if VDUSE_API_VERSION >= 1 */ + __u32 reserved[11]; __u32 config_size; __u8 config[]; }; @@ -166,6 +168,16 @@ struct vduse_vq_state_packed { __u16 last_used_idx; }; +/** + * struct vduse_vq_group_asid - virtqueue group ASID + * @group: Index of the virtqueue group + * @asid: Address space ID of the group + */ +struct vduse_vq_group_asid { + __u32 group; + __u32 asid; +}; + /** * struct vduse_vq_info - information of a virtqueue * @index: virtqueue index @@ -225,6 +237,7 @@ struct vduse_vq_eventfd { * @uaddr: start address of userspace memory, it must be aligned to page size * @iova: start of the IOVA region * @size: size of the IOVA region + * @asid: Address space ID of the IOVA region * @reserved: for future use, needs to be initialized to zero * * Structure used by VDUSE_IOTLB_REG_UMEM and VDUSE_IOTLB_DEREG_UMEM @@ -234,7 +247,8 @@ struct vduse_iova_umem { __u64 uaddr; __u64 iova; __u64 size; - __u64 reserved[3]; + __u32 asid; + __u32 reserved[5]; }; /* Register userspace memory for IOVA regions */ @@ -248,6 +262,7 @@ struct vduse_iova_umem { * @start: start of the IOVA region * @last: last of the IOVA region * @capability: capability of the IOVA region + * @asid: Address space ID of the IOVA region, only if device API version >= 1 * @reserved: for future use, needs to be initialized to zero * * Structure used by VDUSE_IOTLB_GET_INFO ioctl to get information of @@ -258,7 +273,8 @@ struct vduse_iova_info { __u64 last; #define VDUSE_IOVA_CAP_UMEM (1 << 0) __u64 capability; - __u64 reserved[3]; + __u32 asid; /* Only if device API version >= 1 */ + __u32 reserved[5]; }; /* @@ -267,6 +283,28 @@ struct vduse_iova_info { */ #define VDUSE_IOTLB_GET_INFO _IOWR(VDUSE_BASE, 0x1a, struct vduse_iova_info) +/** + * struct vduse_iotlb_entry_v2 - entry of IOTLB to describe one IOVA region + * + * @v1: the original vduse_iotlb_entry + * @asid: address space ID of the IOVA region + * @reserved: for future use, needs to be initialized to zero + * + * Structure used by VDUSE_IOTLB_GET_FD2 ioctl to find an overlapped IOVA region. + */ +struct vduse_iotlb_entry_v2 { + struct vduse_iotlb_entry v1; + __u32 asid; + __u32 reserved[12]; +}; + +/* + * Same as VDUSE_IOTLB_GET_FD but with vduse_iotlb_entry_v2 argument that + * support extra fields. + */ +#define VDUSE_IOTLB_GET_FD2 _IOWR(VDUSE_BASE, 0x1b, struct vduse_iotlb_entry_v2) + + /* The control messages definition for read(2)/write(2) on /dev/vduse/$NAME */ /** @@ -275,11 +313,14 @@ struct vduse_iova_info { * @VDUSE_SET_STATUS: set the device status * @VDUSE_UPDATE_IOTLB: Notify userspace to update the memory mapping for * specified IOVA range via VDUSE_IOTLB_GET_FD ioctl + * @VDUSE_SET_VQ_GROUP_ASID: Notify userspace to update the address space of a + * virtqueue group. */ enum vduse_req_type { VDUSE_GET_VQ_STATE, VDUSE_SET_STATUS, VDUSE_UPDATE_IOTLB, + VDUSE_SET_VQ_GROUP_ASID, }; /** @@ -314,6 +355,18 @@ struct vduse_iova_range { __u64 last; }; +/** + * struct vduse_iova_range_v2 - IOVA range [start, last] if API_VERSION >= 1 + * @start: start of the IOVA range + * @last: last of the IOVA range + * @asid: address space ID of the IOVA range + */ +struct vduse_iova_range_v2 { + __u64 start; + __u64 last; + __u32 asid; +}; + /** * struct vduse_dev_request - control request * @type: request type @@ -322,6 +375,8 @@ struct vduse_iova_range { * @vq_state: virtqueue state, only index field is available * @s: device status * @iova: IOVA range for updating + * @iova_v2: IOVA range for updating if API_VERSION >= 1 + * @vq_group_asid: ASID of a virtqueue group * @padding: padding * * Structure used by read(2) on /dev/vduse/$NAME. @@ -334,6 +389,11 @@ struct vduse_dev_request { struct vduse_vq_state vq_state; struct vduse_dev_status s; struct vduse_iova_range iova; + /* Following members but padding exist only if vduse api + * version >= 1 + */ + struct vduse_iova_range_v2 iova_v2; + struct vduse_vq_group_asid vq_group_asid; __u32 padding[32]; }; }; -- cgit v1.2.3 From 5ca243f6e3c30b979a54a96b96df355dda2b4d0f Mon Sep 17 00:00:00 2001 From: Deepak Gupta Date: Sun, 25 Jan 2026 21:09:54 -0700 Subject: prctl: add arch-agnostic prctl()s for indirect branch tracking Three architectures (x86, aarch64, riscv) have support for indirect branch tracking feature in a very similar fashion. On a very high level, indirect branch tracking is a CPU feature where CPU tracks branches which use a memory operand to transfer control. As part of this tracking, during an indirect branch, the CPU expects a landing pad instruction on the target PC, and if not found, the CPU raises some fault (architecture-dependent). x86 landing pad instr - 'ENDBRANCH' arch64 landing pad instr - 'BTI' riscv landing instr - 'lpad' Given that three major architectures have support for indirect branch tracking, this patch creates architecture-agnostic 'prctls' to allow userspace to control this feature. They are: - PR_GET_INDIR_BR_LP_STATUS: Get the current configured status for indirect branch tracking. - PR_SET_INDIR_BR_LP_STATUS: Set the configuration for indirect branch tracking. The following status options are allowed: - PR_INDIR_BR_LP_ENABLE: Enables indirect branch tracking on user thread. - PR_INDIR_BR_LP_DISABLE: Disables indirect branch tracking on user thread. - PR_LOCK_INDIR_BR_LP_STATUS: Locks configured status for indirect branch tracking for user thread. Reviewed-by: Mark Brown Reviewed-by: Zong Li Signed-off-by: Deepak Gupta Tested-by: Andreas Korb # QEMU, custom CVA6 Tested-by: Valentin Haudiquet Link: https://patch.msgid.link/20251112-v5_user_cfi_series-v23-13-b55691eacf4f@rivosinc.com [pjw@kernel.org: cleaned up patch description, code comments] Signed-off-by: Paul Walmsley --- include/linux/cpu.h | 4 ++++ include/uapi/linux/prctl.h | 27 +++++++++++++++++++++++++++ kernel/sys.c | 30 ++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+) (limited to 'include/uapi/linux') diff --git a/include/linux/cpu.h b/include/linux/cpu.h index 487b3bf2e1ea..8239cd95a005 100644 --- a/include/linux/cpu.h +++ b/include/linux/cpu.h @@ -229,4 +229,8 @@ static inline bool cpu_attack_vector_mitigated(enum cpu_attack_vectors v) #define smt_mitigations SMT_MITIGATIONS_OFF #endif +int arch_get_indir_br_lp_status(struct task_struct *t, unsigned long __user *status); +int arch_set_indir_br_lp_status(struct task_struct *t, unsigned long status); +int arch_lock_indir_br_lp_status(struct task_struct *t, unsigned long status); + #endif /* _LINUX_CPU_H_ */ diff --git a/include/uapi/linux/prctl.h b/include/uapi/linux/prctl.h index 51c4e8c82b1e..f57098fb0ba8 100644 --- a/include/uapi/linux/prctl.h +++ b/include/uapi/linux/prctl.h @@ -386,4 +386,31 @@ struct prctl_mm_map { # define PR_FUTEX_HASH_SET_SLOTS 1 # define PR_FUTEX_HASH_GET_SLOTS 2 +/* + * Get the current indirect branch tracking configuration for the current + * thread, this will be the value configured via PR_SET_INDIR_BR_LP_STATUS. + */ +#define PR_GET_INDIR_BR_LP_STATUS 79 + +/* + * Set the indirect branch tracking configuration. PR_INDIR_BR_LP_ENABLE will + * enable cpu feature for user thread, to track all indirect branches and ensure + * they land on arch defined landing pad instruction. + * x86 - If enabled, an indirect branch must land on an ENDBRANCH instruction. + * arch64 - If enabled, an indirect branch must land on a BTI instruction. + * riscv - If enabled, an indirect branch must land on an lpad instruction. + * PR_INDIR_BR_LP_DISABLE will disable feature for user thread and indirect + * branches will no more be tracked by cpu to land on arch defined landing pad + * instruction. + */ +#define PR_SET_INDIR_BR_LP_STATUS 80 +# define PR_INDIR_BR_LP_ENABLE (1UL << 0) + +/* + * Prevent further changes to the specified indirect branch tracking + * configuration. All bits may be locked via this call, including + * undefined bits. + */ +#define PR_LOCK_INDIR_BR_LP_STATUS 81 + #endif /* _LINUX_PRCTL_H */ diff --git a/kernel/sys.c b/kernel/sys.c index 8b58eece4e58..9071422c1609 100644 --- a/kernel/sys.c +++ b/kernel/sys.c @@ -2388,6 +2388,21 @@ int __weak arch_lock_shadow_stack_status(struct task_struct *t, unsigned long st return -EINVAL; } +int __weak arch_get_indir_br_lp_status(struct task_struct *t, unsigned long __user *status) +{ + return -EINVAL; +} + +int __weak arch_set_indir_br_lp_status(struct task_struct *t, unsigned long status) +{ + return -EINVAL; +} + +int __weak arch_lock_indir_br_lp_status(struct task_struct *t, unsigned long status) +{ + return -EINVAL; +} + #define PR_IO_FLUSHER (PF_MEMALLOC_NOIO | PF_LOCAL_THROTTLE) static int prctl_set_vma(unsigned long opt, unsigned long addr, @@ -2868,6 +2883,21 @@ SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3, case PR_FUTEX_HASH: error = futex_hash_prctl(arg2, arg3, arg4); break; + case PR_GET_INDIR_BR_LP_STATUS: + if (arg3 || arg4 || arg5) + return -EINVAL; + error = arch_get_indir_br_lp_status(me, (unsigned long __user *)arg2); + break; + case PR_SET_INDIR_BR_LP_STATUS: + if (arg3 || arg4 || arg5) + return -EINVAL; + error = arch_set_indir_br_lp_status(me, arg2); + break; + case PR_LOCK_INDIR_BR_LP_STATUS: + if (arg3 || arg4 || arg5) + return -EINVAL; + error = arch_lock_indir_br_lp_status(me, arg2); + break; default: trace_task_prctl_unknown(option, arg2, arg3, arg4, arg5); error = -EINVAL; -- cgit v1.2.3 From 2af7c9cf021c5dabe880b68e5cc22c618060d954 Mon Sep 17 00:00:00 2001 From: Deepak Gupta Date: Sun, 25 Jan 2026 21:09:55 -0700 Subject: riscv/ptrace: expose riscv CFI status and state via ptrace and in core files Expose a new register type NT_RISCV_USER_CFI for risc-v CFI status and state. Intentionally, both landing pad and shadow stack status and state are rolled into the CFI state. Creating two different NT_RISCV_USER_XXX would not be useful and would waste a note type. Enabling, disabling and locking the CFI feature is not allowed via ptrace set interface. However, setting 'elp' state or setting shadow stack pointer are allowed via the ptrace set interface. It is expected that 'gdb' might need to fixup 'elp' state or 'shadow stack' pointer. Signed-off-by: Deepak Gupta Tested-by: Andreas Korb # QEMU, custom CVA6 Tested-by: Valentin Haudiquet Link: https://patch.msgid.link/20251112-v5_user_cfi_series-v23-19-b55691eacf4f@rivosinc.com [pjw@kernel.org: updated to apply; cleaned patch description and comments; addressed checkpatch issues] Signed-off-by: Paul Walmsley --- arch/riscv/include/uapi/asm/ptrace.h | 30 ++++++++++++ arch/riscv/kernel/ptrace.c | 95 ++++++++++++++++++++++++++++++++++++ include/uapi/linux/elf.h | 2 + 3 files changed, 127 insertions(+) (limited to 'include/uapi/linux') diff --git a/arch/riscv/include/uapi/asm/ptrace.h b/arch/riscv/include/uapi/asm/ptrace.h index 261bfe70f60a..18988a5f1a63 100644 --- a/arch/riscv/include/uapi/asm/ptrace.h +++ b/arch/riscv/include/uapi/asm/ptrace.h @@ -131,6 +131,36 @@ struct __sc_riscv_cfi_state { unsigned long ss_ptr; /* shadow stack pointer */ }; +#define PTRACE_CFI_LP_EN_BIT 0 +#define PTRACE_CFI_LP_LOCK_BIT 1 +#define PTRACE_CFI_ELP_BIT 2 +#define PTRACE_CFI_SS_EN_BIT 3 +#define PTRACE_CFI_SS_LOCK_BIT 4 +#define PTRACE_CFI_SS_PTR_BIT 5 + +#define PTRACE_CFI_LP_EN_STATE BIT(PTRACE_CFI_LP_EN_BIT) +#define PTRACE_CFI_LP_LOCK_STATE BIT(PTRACE_CFI_LP_LOCK_BIT) +#define PTRACE_CFI_ELP_STATE BIT(PTRACE_CFI_ELP_BIT) +#define PTRACE_CFI_SS_EN_STATE BIT(PTRACE_CFI_SS_EN_BIT) +#define PTRACE_CFI_SS_LOCK_STATE BIT(PTRACE_CFI_SS_LOCK_BIT) +#define PTRACE_CFI_SS_PTR_STATE BIT(PTRACE_CFI_SS_PTR_BIT) + +#define PRACE_CFI_STATE_INVALID_MASK ~(PTRACE_CFI_LP_EN_STATE | \ + PTRACE_CFI_LP_LOCK_STATE | \ + PTRACE_CFI_ELP_STATE | \ + PTRACE_CFI_SS_EN_STATE | \ + PTRACE_CFI_SS_LOCK_STATE | \ + PTRACE_CFI_SS_PTR_STATE) + +struct __cfi_status { + __u64 cfi_state; +}; + +struct user_cfi_state { + struct __cfi_status cfi_status; + __u64 shstk_ptr; +}; + #endif /* __ASSEMBLER__ */ #endif /* _UAPI_ASM_RISCV_PTRACE_H */ diff --git a/arch/riscv/kernel/ptrace.c b/arch/riscv/kernel/ptrace.c index e6272d74572f..57e257d459e8 100644 --- a/arch/riscv/kernel/ptrace.c +++ b/arch/riscv/kernel/ptrace.c @@ -19,6 +19,7 @@ #include #include #include +#include enum riscv_regset { REGSET_X, @@ -31,6 +32,9 @@ enum riscv_regset { #ifdef CONFIG_RISCV_ISA_SUPM REGSET_TAGGED_ADDR_CTRL, #endif +#ifdef CONFIG_RISCV_USER_CFI + REGSET_CFI, +#endif }; static int riscv_gpr_get(struct task_struct *target, @@ -195,6 +199,87 @@ static int tagged_addr_ctrl_set(struct task_struct *target, } #endif +#ifdef CONFIG_RISCV_USER_CFI +static int riscv_cfi_get(struct task_struct *target, + const struct user_regset *regset, + struct membuf to) +{ + struct user_cfi_state user_cfi; + struct pt_regs *regs; + + memset(&user_cfi, 0, sizeof(user_cfi)); + regs = task_pt_regs(target); + + if (is_indir_lp_enabled(target)) { + user_cfi.cfi_status.cfi_state |= PTRACE_CFI_LP_EN_STATE; + user_cfi.cfi_status.cfi_state |= is_indir_lp_locked(target) ? + PTRACE_CFI_LP_LOCK_STATE : 0; + user_cfi.cfi_status.cfi_state |= (regs->status & SR_ELP) ? + PTRACE_CFI_ELP_STATE : 0; + } + + if (is_shstk_enabled(target)) { + user_cfi.cfi_status.cfi_state |= (PTRACE_CFI_SS_EN_STATE | + PTRACE_CFI_SS_PTR_STATE); + user_cfi.cfi_status.cfi_state |= is_shstk_locked(target) ? + PTRACE_CFI_SS_LOCK_STATE : 0; + user_cfi.shstk_ptr = get_active_shstk(target); + } + + return membuf_write(&to, &user_cfi, sizeof(user_cfi)); +} + +/* + * Does it make sense to allow enable / disable of cfi via ptrace? + * We don't allow enable / disable / locking control via ptrace for now. + * Setting the shadow stack pointer is allowed. GDB might use it to unwind or + * some other fixup. Similarly gdb might want to suppress elp and may want + * to reset elp state. + */ +static int riscv_cfi_set(struct task_struct *target, + const struct user_regset *regset, + unsigned int pos, unsigned int count, + const void *kbuf, const void __user *ubuf) +{ + int ret; + struct user_cfi_state user_cfi; + struct pt_regs *regs; + + regs = task_pt_regs(target); + + ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &user_cfi, 0, -1); + if (ret) + return ret; + + /* + * Not allowing enabling or locking shadow stack or landing pad + * There is no disabling of shadow stack or landing pad via ptrace + * rsvd field should be set to zero so that if those fields are needed in future + */ + if ((user_cfi.cfi_status.cfi_state & + (PTRACE_CFI_LP_EN_STATE | PTRACE_CFI_LP_LOCK_STATE | + PTRACE_CFI_SS_EN_STATE | PTRACE_CFI_SS_LOCK_STATE)) || + (user_cfi.cfi_status.cfi_state & PRACE_CFI_STATE_INVALID_MASK)) + return -EINVAL; + + /* If lpad is enabled on target and ptrace requests to set / clear elp, do that */ + if (is_indir_lp_enabled(target)) { + if (user_cfi.cfi_status.cfi_state & + PTRACE_CFI_ELP_STATE) /* set elp state */ + regs->status |= SR_ELP; + else + regs->status &= ~SR_ELP; /* clear elp state */ + } + + /* If shadow stack enabled on target, set new shadow stack pointer */ + if (is_shstk_enabled(target) && + (user_cfi.cfi_status.cfi_state & PTRACE_CFI_SS_PTR_STATE)) + set_active_shstk(target, user_cfi.shstk_ptr); + + return 0; +} +#endif + static struct user_regset riscv_user_regset[] __ro_after_init = { [REGSET_X] = { USER_REGSET_NOTE_TYPE(PRSTATUS), @@ -234,6 +319,16 @@ static struct user_regset riscv_user_regset[] __ro_after_init = { .set = tagged_addr_ctrl_set, }, #endif +#ifdef CONFIG_RISCV_USER_CFI + [REGSET_CFI] = { + .core_note_type = NT_RISCV_USER_CFI, + .align = sizeof(__u64), + .n = sizeof(struct user_cfi_state) / sizeof(__u64), + .size = sizeof(__u64), + .regset_get = riscv_cfi_get, + .set = riscv_cfi_set, + }, +#endif }; static const struct user_regset_view riscv_user_native_view = { diff --git a/include/uapi/linux/elf.h b/include/uapi/linux/elf.h index 819ded2d39de..ee30dcd80901 100644 --- a/include/uapi/linux/elf.h +++ b/include/uapi/linux/elf.h @@ -545,6 +545,8 @@ typedef struct elf64_shdr { #define NT_RISCV_VECTOR 0x901 /* RISC-V vector registers */ #define NN_RISCV_TAGGED_ADDR_CTRL "LINUX" #define NT_RISCV_TAGGED_ADDR_CTRL 0x902 /* RISC-V tagged address control (prctl()) */ +#define NN_RISCV_USER_CFI "LINUX" +#define NT_RISCV_USER_CFI 0x903 /* RISC-V shadow stack state */ #define NN_LOONGARCH_CPUCFG "LINUX" #define NT_LOONGARCH_CPUCFG 0xa00 /* LoongArch CPU config registers */ #define NN_LOONGARCH_CSR "LINUX" -- cgit v1.2.3 From 0e6b7eae1fded85f94a357d6132f07d64c614cfa Mon Sep 17 00:00:00 2001 From: Andrey Albershteyn Date: Mon, 26 Jan 2026 12:56:57 +0100 Subject: fs: add FS_XFLAG_VERITY for fs-verity files fs-verity introduced inode flag for inodes with enabled fs-verity on them. This patch adds FS_XFLAG_VERITY file attribute which can be retrieved with FS_IOC_FSGETXATTR ioctl() and file_getattr() syscall. This flag is read-only and can not be set with corresponding set ioctl() and file_setattr(). The FS_IOC_SETFLAGS requires file to be opened for writing which is not allowed for verity files. The FS_IOC_FSSETXATTR and file_setattr() clears this flag from the user input. As this is now common flag for both flag interfaces (flags/xflags) add it to overlapping flags list to exclude it from overwrite. Signed-off-by: Andrey Albershteyn Link: https://patch.msgid.link/20260126115658.27656-2-aalbersh@kernel.org Reviewed-by: Darrick J. Wong Signed-off-by: Christian Brauner --- Documentation/filesystems/fsverity.rst | 16 ++++++++++++++++ fs/file_attr.c | 4 ++++ include/linux/fileattr.h | 6 +++--- include/uapi/linux/fs.h | 1 + 4 files changed, 24 insertions(+), 3 deletions(-) (limited to 'include/uapi/linux') diff --git a/Documentation/filesystems/fsverity.rst b/Documentation/filesystems/fsverity.rst index 412cf11e3298..22b49b295d1f 100644 --- a/Documentation/filesystems/fsverity.rst +++ b/Documentation/filesystems/fsverity.rst @@ -341,6 +341,22 @@ the file has fs-verity enabled. This can perform better than FS_IOC_GETFLAGS and FS_IOC_MEASURE_VERITY because it doesn't require opening the file, and opening verity files can be expensive. +FS_IOC_FSGETXATTR +----------------- + +Since Linux v7.0, the FS_IOC_FSGETXATTR ioctl sets FS_XFLAG_VERITY (0x00020000) +in the returned flags when the file has verity enabled. Note that this attribute +cannot be set with FS_IOC_FSSETXATTR as enabling verity requires input +parameters. See FS_IOC_ENABLE_VERITY. + +file_getattr +------------ + +Since Linux v7.0, the file_getattr() syscall sets FS_XFLAG_VERITY (0x00020000) +in the returned flags when the file has verity enabled. Note that this attribute +cannot be set with file_setattr() as enabling verity requires input parameters. +See FS_IOC_ENABLE_VERITY. + .. _accessing_verity_files: Accessing verity files diff --git a/fs/file_attr.c b/fs/file_attr.c index f3704881c126..dfde87401817 100644 --- a/fs/file_attr.c +++ b/fs/file_attr.c @@ -36,6 +36,8 @@ void fileattr_fill_xflags(struct file_kattr *fa, u32 xflags) fa->flags |= FS_DAX_FL; if (fa->fsx_xflags & FS_XFLAG_PROJINHERIT) fa->flags |= FS_PROJINHERIT_FL; + if (fa->fsx_xflags & FS_XFLAG_VERITY) + fa->flags |= FS_VERITY_FL; } EXPORT_SYMBOL(fileattr_fill_xflags); @@ -66,6 +68,8 @@ void fileattr_fill_flags(struct file_kattr *fa, u32 flags) fa->fsx_xflags |= FS_XFLAG_DAX; if (fa->flags & FS_PROJINHERIT_FL) fa->fsx_xflags |= FS_XFLAG_PROJINHERIT; + if (fa->flags & FS_VERITY_FL) + fa->fsx_xflags |= FS_XFLAG_VERITY; } EXPORT_SYMBOL(fileattr_fill_flags); diff --git a/include/linux/fileattr.h b/include/linux/fileattr.h index f89dcfad3f8f..3780904a63a6 100644 --- a/include/linux/fileattr.h +++ b/include/linux/fileattr.h @@ -7,16 +7,16 @@ #define FS_COMMON_FL \ (FS_SYNC_FL | FS_IMMUTABLE_FL | FS_APPEND_FL | \ FS_NODUMP_FL | FS_NOATIME_FL | FS_DAX_FL | \ - FS_PROJINHERIT_FL) + FS_PROJINHERIT_FL | FS_VERITY_FL) #define FS_XFLAG_COMMON \ (FS_XFLAG_SYNC | FS_XFLAG_IMMUTABLE | FS_XFLAG_APPEND | \ FS_XFLAG_NODUMP | FS_XFLAG_NOATIME | FS_XFLAG_DAX | \ - FS_XFLAG_PROJINHERIT) + FS_XFLAG_PROJINHERIT | FS_XFLAG_VERITY) /* Read-only inode flags */ #define FS_XFLAG_RDONLY_MASK \ - (FS_XFLAG_PREALLOC | FS_XFLAG_HASATTR) + (FS_XFLAG_PREALLOC | FS_XFLAG_HASATTR | FS_XFLAG_VERITY) /* Flags to indicate valid value of fsx_ fields */ #define FS_XFLAG_VALUES_MASK \ diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h index 66ca526cf786..70b2b661f42c 100644 --- a/include/uapi/linux/fs.h +++ b/include/uapi/linux/fs.h @@ -253,6 +253,7 @@ struct file_attr { #define FS_XFLAG_FILESTREAM 0x00004000 /* use filestream allocator */ #define FS_XFLAG_DAX 0x00008000 /* use DAX for IO */ #define FS_XFLAG_COWEXTSIZE 0x00010000 /* CoW extent size allocator hint */ +#define FS_XFLAG_VERITY 0x00020000 /* fs-verity enabled */ #define FS_XFLAG_HASATTR 0x80000000 /* no DIFLAG for this */ /* the read-only stuff doesn't really belong here, but any other place is -- cgit v1.2.3 From 8cf82bb558517503a81f8e3c49914c0836360aa6 Mon Sep 17 00:00:00 2001 From: Koichiro Den Date: Sat, 24 Jan 2026 23:50:11 +0900 Subject: misc: pci_endpoint_test: Add BAR subrange mapping test case Add a new PCITEST_BAR_SUBRANGE ioctl to exercise EPC BAR subrange mapping end-to-end. The test programs a simple 2-subrange layout on the endpoint (via pci-epf-test) and verifies that: - the endpoint-provided per-subrange signature bytes are observed at the expected PCIe BAR offsets, and - writes to each subrange are routed to the correct backing region (i.e. the submap order is applied rather than accidentally working due to an identity mapping). Return -EOPNOTSUPP when the endpoint does not advertise subrange mapping, -ENODATA when the BAR is disabled, and -EBUSY when the BAR is reserved for the test register space. Signed-off-by: Koichiro Den Signed-off-by: Manivannan Sadhasivam Signed-off-by: Bjorn Helgaas Link: https://patch.msgid.link/20260124145012.2794108-8-den@valinux.co.jp --- drivers/misc/pci_endpoint_test.c | 203 ++++++++++++++++++++++++++++++++++++++- include/uapi/linux/pcitest.h | 1 + 2 files changed, 203 insertions(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/drivers/misc/pci_endpoint_test.c b/drivers/misc/pci_endpoint_test.c index 1c0fd185114f..74ab5b5b9011 100644 --- a/drivers/misc/pci_endpoint_test.c +++ b/drivers/misc/pci_endpoint_test.c @@ -39,6 +39,8 @@ #define COMMAND_COPY BIT(5) #define COMMAND_ENABLE_DOORBELL BIT(6) #define COMMAND_DISABLE_DOORBELL BIT(7) +#define COMMAND_BAR_SUBRANGE_SETUP BIT(8) +#define COMMAND_BAR_SUBRANGE_CLEAR BIT(9) #define PCI_ENDPOINT_TEST_STATUS 0x8 #define STATUS_READ_SUCCESS BIT(0) @@ -55,6 +57,10 @@ #define STATUS_DOORBELL_ENABLE_FAIL BIT(11) #define STATUS_DOORBELL_DISABLE_SUCCESS BIT(12) #define STATUS_DOORBELL_DISABLE_FAIL BIT(13) +#define STATUS_BAR_SUBRANGE_SETUP_SUCCESS BIT(14) +#define STATUS_BAR_SUBRANGE_SETUP_FAIL BIT(15) +#define STATUS_BAR_SUBRANGE_CLEAR_SUCCESS BIT(16) +#define STATUS_BAR_SUBRANGE_CLEAR_FAIL BIT(17) #define PCI_ENDPOINT_TEST_LOWER_SRC_ADDR 0x0c #define PCI_ENDPOINT_TEST_UPPER_SRC_ADDR 0x10 @@ -77,6 +83,7 @@ #define CAP_MSI BIT(1) #define CAP_MSIX BIT(2) #define CAP_INTX BIT(3) +#define CAP_SUBRANGE_MAPPING BIT(4) #define PCI_ENDPOINT_TEST_DB_BAR 0x34 #define PCI_ENDPOINT_TEST_DB_OFFSET 0x38 @@ -100,6 +107,8 @@ #define PCI_DEVICE_ID_ROCKCHIP_RK3588 0x3588 +#define PCI_ENDPOINT_TEST_BAR_SUBRANGE_NSUB 2 + static DEFINE_IDA(pci_endpoint_test_ida); #define to_endpoint_test(priv) container_of((priv), struct pci_endpoint_test, \ @@ -414,6 +423,193 @@ static int pci_endpoint_test_bars(struct pci_endpoint_test *test) return 0; } +static u8 pci_endpoint_test_subrange_sig_byte(enum pci_barno barno, + unsigned int subno) +{ + return 0x50 + (barno * 8) + subno; +} + +static u8 pci_endpoint_test_subrange_test_byte(enum pci_barno barno, + unsigned int subno) +{ + return 0xa0 + (barno * 8) + subno; +} + +static int pci_endpoint_test_bar_subrange_cmd(struct pci_endpoint_test *test, + enum pci_barno barno, u32 command, + u32 ok_bit, u32 fail_bit) +{ + struct pci_dev *pdev = test->pdev; + struct device *dev = &pdev->dev; + int irq_type = test->irq_type; + u32 status; + + if (irq_type < PCITEST_IRQ_TYPE_INTX || + irq_type > PCITEST_IRQ_TYPE_MSIX) { + dev_err(dev, "Invalid IRQ type\n"); + return -EINVAL; + } + + reinit_completion(&test->irq_raised); + + pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_STATUS, 0); + pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_IRQ_TYPE, irq_type); + pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_IRQ_NUMBER, 1); + /* Reuse SIZE as a command parameter: bar number. */ + pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_SIZE, barno); + pci_endpoint_test_writel(test, PCI_ENDPOINT_TEST_COMMAND, command); + + if (!wait_for_completion_timeout(&test->irq_raised, + msecs_to_jiffies(1000))) + return -ETIMEDOUT; + + status = pci_endpoint_test_readl(test, PCI_ENDPOINT_TEST_STATUS); + if (status & fail_bit) + return -EIO; + + if (!(status & ok_bit)) + return -EIO; + + return 0; +} + +static int pci_endpoint_test_bar_subrange_setup(struct pci_endpoint_test *test, + enum pci_barno barno) +{ + return pci_endpoint_test_bar_subrange_cmd(test, barno, + COMMAND_BAR_SUBRANGE_SETUP, + STATUS_BAR_SUBRANGE_SETUP_SUCCESS, + STATUS_BAR_SUBRANGE_SETUP_FAIL); +} + +static int pci_endpoint_test_bar_subrange_clear(struct pci_endpoint_test *test, + enum pci_barno barno) +{ + return pci_endpoint_test_bar_subrange_cmd(test, barno, + COMMAND_BAR_SUBRANGE_CLEAR, + STATUS_BAR_SUBRANGE_CLEAR_SUCCESS, + STATUS_BAR_SUBRANGE_CLEAR_FAIL); +} + +static int pci_endpoint_test_bar_subrange(struct pci_endpoint_test *test, + enum pci_barno barno) +{ + u32 nsub = PCI_ENDPOINT_TEST_BAR_SUBRANGE_NSUB; + struct device *dev = &test->pdev->dev; + size_t sub_size, buf_size; + resource_size_t bar_size; + void __iomem *bar_addr; + void *read_buf = NULL; + int ret, clear_ret; + size_t off, chunk; + u32 i, exp, val; + u8 pattern; + + if (!(test->ep_caps & CAP_SUBRANGE_MAPPING)) + return -EOPNOTSUPP; + + /* + * The test register BAR is not safe to reprogram and write/read + * over its full size. BAR_TEST already special-cases it to a tiny + * range. For subrange mapping tests, let's simply skip it. + */ + if (barno == test->test_reg_bar) + return -EBUSY; + + bar_size = pci_resource_len(test->pdev, barno); + if (!bar_size) + return -ENODATA; + + bar_addr = test->bar[barno]; + if (!bar_addr) + return -ENOMEM; + + ret = pci_endpoint_test_bar_subrange_setup(test, barno); + if (ret) + return ret; + + if (bar_size % nsub || bar_size / nsub > SIZE_MAX) { + ret = -EINVAL; + goto out_clear; + } + + sub_size = bar_size / nsub; + if (sub_size < sizeof(u32)) { + ret = -ENOSPC; + goto out_clear; + } + + /* Limit the temporary buffer size */ + buf_size = min_t(size_t, sub_size, SZ_1M); + + read_buf = kmalloc(buf_size, GFP_KERNEL); + if (!read_buf) { + ret = -ENOMEM; + goto out_clear; + } + + /* + * Step 1: verify EP-provided signature per subrange. This detects + * whether the EP actually applied the submap order. + */ + for (i = 0; i < nsub; i++) { + exp = (u32)pci_endpoint_test_subrange_sig_byte(barno, i) * + 0x01010101U; + val = ioread32(bar_addr + (i * sub_size)); + if (val != exp) { + dev_err(dev, + "BAR%d subrange%u signature mismatch @%#zx: exp %#08x got %#08x\n", + barno, i, (size_t)i * sub_size, exp, val); + ret = -EIO; + goto out_clear; + } + val = ioread32(bar_addr + (i * sub_size) + sub_size - sizeof(u32)); + if (val != exp) { + dev_err(dev, + "BAR%d subrange%u signature mismatch @%#zx: exp %#08x got %#08x\n", + barno, i, + ((size_t)i * sub_size) + sub_size - sizeof(u32), + exp, val); + ret = -EIO; + goto out_clear; + } + } + + /* Step 2: write unique pattern per subrange (write all first). */ + for (i = 0; i < nsub; i++) { + pattern = pci_endpoint_test_subrange_test_byte(barno, i); + memset_io(bar_addr + (i * sub_size), pattern, sub_size); + } + + /* Step 3: read back and verify (read all after all writes). */ + for (i = 0; i < nsub; i++) { + pattern = pci_endpoint_test_subrange_test_byte(barno, i); + for (off = 0; off < sub_size; off += chunk) { + void *bad; + + chunk = min_t(size_t, buf_size, sub_size - off); + memcpy_fromio(read_buf, bar_addr + (i * sub_size) + off, + chunk); + bad = memchr_inv(read_buf, pattern, chunk); + if (bad) { + size_t bad_off = (u8 *)bad - (u8 *)read_buf; + + dev_err(dev, + "BAR%d subrange%u data mismatch @%#zx (pattern %#02x)\n", + barno, i, (size_t)i * sub_size + off + bad_off, + pattern); + ret = -EIO; + goto out_clear; + } + } + } + +out_clear: + kfree(read_buf); + clear_ret = pci_endpoint_test_bar_subrange_clear(test, barno); + return ret ?: clear_ret; +} + static int pci_endpoint_test_intx_irq(struct pci_endpoint_test *test) { u32 val; @@ -936,12 +1132,17 @@ static long pci_endpoint_test_ioctl(struct file *file, unsigned int cmd, switch (cmd) { case PCITEST_BAR: + case PCITEST_BAR_SUBRANGE: bar = arg; if (bar <= NO_BAR || bar > BAR_5) goto ret; if (is_am654_pci_dev(pdev) && bar == BAR_0) goto ret; - ret = pci_endpoint_test_bar(test, bar); + + if (cmd == PCITEST_BAR) + ret = pci_endpoint_test_bar(test, bar); + else + ret = pci_endpoint_test_bar_subrange(test, bar); break; case PCITEST_BARS: ret = pci_endpoint_test_bars(test); diff --git a/include/uapi/linux/pcitest.h b/include/uapi/linux/pcitest.h index d6023a45a9d0..710f8842223f 100644 --- a/include/uapi/linux/pcitest.h +++ b/include/uapi/linux/pcitest.h @@ -22,6 +22,7 @@ #define PCITEST_GET_IRQTYPE _IO('P', 0x9) #define PCITEST_BARS _IO('P', 0xa) #define PCITEST_DOORBELL _IO('P', 0xb) +#define PCITEST_BAR_SUBRANGE _IO('P', 0xc) #define PCITEST_CLEAR_IRQ _IO('P', 0x10) #define PCITEST_IRQ_TYPE_UNDEFINED -1 -- cgit v1.2.3 From bc443c253fcdd2636e2a29fde3f749d39d479cf0 Mon Sep 17 00:00:00 2001 From: Ivan Vecera Date: Mon, 26 Jan 2026 17:22:51 +0100 Subject: dpll: expose fractional frequency offset in ppt Currently, the dpll subsystem exports the fractional frequency offset (FFO) in parts per million (ppm). This granularity is insufficient for high-precision synchronization scenarios which often require parts per trillion (ppt) resolution. Add a new netlink attribute DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET_PPT to expose the FFO in ppt. Update the dpll netlink core to expect the driver-provided FFO value to be in ppt. To maintain backward compatibility with existing userspace tools, populate the legacy DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET attribute by dividing the new ppt value by 1,000,000. Update the zl3073x and mlx5 drivers to provide the FFO value in ppt: - zl3073x: adjust the fixed-point calculation to produce ppt (10^12) instead of ppm (10^6). - mlx5: scale the existing ppm value by 1,000,000. Signed-off-by: Ivan Vecera Reviewed-by: Arkadiusz Kubalewski Reviewed-by: Jiri Pirko Link: https://patch.msgid.link/20260126162253.27890-1-ivecera@redhat.com Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/dpll.yaml | 11 +++++++++++ drivers/dpll/dpll_netlink.c | 10 +++++++++- drivers/dpll/zl3073x/core.c | 7 +++++-- drivers/net/ethernet/mellanox/mlx5/core/dpll.c | 2 +- include/uapi/linux/dpll.h | 1 + 5 files changed, 27 insertions(+), 4 deletions(-) (limited to 'include/uapi/linux') diff --git a/Documentation/netlink/specs/dpll.yaml b/Documentation/netlink/specs/dpll.yaml index b55afa77eac4..3dd48a32f783 100644 --- a/Documentation/netlink/specs/dpll.yaml +++ b/Documentation/netlink/specs/dpll.yaml @@ -446,6 +446,16 @@ attribute-sets: doc: | Granularity of phase adjustment, in picoseconds. The value of phase adjustment must be a multiple of this granularity. + - + name: fractional-frequency-offset-ppt + type: sint + doc: | + The FFO (Fractional Frequency Offset) of the pin with respect to + the nominal frequency. + Value = (frequency_measured - frequency_nominal) / frequency_nominal + Value is in PPT (parts per trillion, 10^-12). + Note: This attribute provides higher resolution than the standard + fractional-frequency-offset (which is in PPM). - name: pin-parent-device @@ -628,6 +638,7 @@ operations: - phase-adjust-max - phase-adjust - fractional-frequency-offset + - fractional-frequency-offset-ppt - esync-frequency - esync-frequency-supported - esync-pulse diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c index 499bca460b1e..904199ddd178 100644 --- a/drivers/dpll/dpll_netlink.c +++ b/drivers/dpll/dpll_netlink.c @@ -389,7 +389,15 @@ static int dpll_msg_add_ffo(struct sk_buff *msg, struct dpll_pin *pin, return 0; return ret; } - return nla_put_sint(msg, DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET, ffo); + /* Put the FFO value in PPM to preserve compatibility with older + * programs. + */ + ret = nla_put_sint(msg, DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET, + div_s64(ffo, 1000000)); + if (ret) + return -EMSGSIZE; + return nla_put_sint(msg, DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET_PPT, + ffo); } static int diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c index 383e2397dd03..63bd97181b9e 100644 --- a/drivers/dpll/zl3073x/core.c +++ b/drivers/dpll/zl3073x/core.c @@ -710,8 +710,11 @@ zl3073x_ref_ffo_update(struct zl3073x_dev *zldev) if (rc) return rc; - /* Convert to ppm -> ffo = (10^6 * value) / 2^32 */ - zldev->ref[i].ffo = mul_s64_u64_shr(value, 1000000, 32); + /* Convert to ppt + * ffo = (10^12 * value) / 2^32 + * ffo = ( 5^12 * value) / 2^20 + */ + zldev->ref[i].ffo = mul_s64_u64_shr(value, 244140625, 20); } return 0; diff --git a/drivers/net/ethernet/mellanox/mlx5/core/dpll.c b/drivers/net/ethernet/mellanox/mlx5/core/dpll.c index 1e5522a19483..3ea8a1766ae2 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/dpll.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/dpll.c @@ -136,7 +136,7 @@ mlx5_dpll_pin_ffo_get(struct mlx5_dpll_synce_status *synce_status, { if (!synce_status->oper_freq_measure) return -ENODATA; - *ffo = synce_status->frequency_diff; + *ffo = 1000000LL * synce_status->frequency_diff; return 0; } diff --git a/include/uapi/linux/dpll.h b/include/uapi/linux/dpll.h index b7ff9c44f9aa..de0005f28e5c 100644 --- a/include/uapi/linux/dpll.h +++ b/include/uapi/linux/dpll.h @@ -253,6 +253,7 @@ enum dpll_a_pin { DPLL_A_PIN_ESYNC_PULSE, DPLL_A_PIN_REFERENCE_SYNC, DPLL_A_PIN_PHASE_ADJUST_GRAN, + DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET_PPT, __DPLL_A_PIN_MAX, DPLL_A_PIN_MAX = (__DPLL_A_PIN_MAX - 1) -- cgit v1.2.3 From 8443e2087e7002fa25984faad6bbf5f63b280645 Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Fri, 30 Jan 2026 00:19:51 +0800 Subject: ublk: add UBLK_F_NO_AUTO_PART_SCAN feature flag Add a new feature flag UBLK_F_NO_AUTO_PART_SCAN to allow users to suppress automatic partition scanning when starting a ublk device. This is useful for some cases in which user don't want to scan partitions. Users still can manually trigger partition scanning later when appropriate using standard tools (e.g., partprobe, blockdev --rereadpt). Reported-by: Yoav Cohen Link: https://lore.kernel.org/linux-block/DM4PR12MB63280C5637917C071C2F0D65A9A8A@DM4PR12MB6328.namprd12.prod.outlook.com/ Signed-off-by: Ming Lei Signed-off-by: Jens Axboe --- drivers/block/ublk_drv.c | 14 ++++++++++---- include/uapi/linux/ublk_cmd.h | 3 +++ 2 files changed, 13 insertions(+), 4 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c index 92bd2351e3ad..4fe754e7d1e8 100644 --- a/drivers/block/ublk_drv.c +++ b/drivers/block/ublk_drv.c @@ -80,7 +80,8 @@ | UBLK_F_BUF_REG_OFF_DAEMON \ | (IS_ENABLED(CONFIG_BLK_DEV_INTEGRITY) ? UBLK_F_INTEGRITY : 0) \ | UBLK_F_SAFE_STOP_DEV \ - | UBLK_F_BATCH_IO) + | UBLK_F_BATCH_IO \ + | UBLK_F_NO_AUTO_PART_SCAN) #define UBLK_F_ALL_RECOVERY_FLAGS (UBLK_F_USER_RECOVERY \ | UBLK_F_USER_RECOVERY_REISSUE \ @@ -4430,9 +4431,14 @@ static int ublk_ctrl_start_dev(struct ublk_device *ub, set_bit(UB_STATE_USED, &ub->state); - /* Schedule async partition scan for trusted daemons */ - if (!ub->unprivileged_daemons) - schedule_work(&ub->partition_scan_work); + /* Skip partition scan if disabled by user */ + if (ub->dev_info.flags & UBLK_F_NO_AUTO_PART_SCAN) { + clear_bit(GD_SUPPRESS_PART_SCAN, &disk->state); + } else { + /* Schedule async partition scan for trusted daemons */ + if (!ub->unprivileged_daemons) + schedule_work(&ub->partition_scan_work); + } out_put_cdev: if (ret) { diff --git a/include/uapi/linux/ublk_cmd.h b/include/uapi/linux/ublk_cmd.h index 743d31491387..a88876756805 100644 --- a/include/uapi/linux/ublk_cmd.h +++ b/include/uapi/linux/ublk_cmd.h @@ -367,6 +367,9 @@ */ #define UBLK_F_SAFE_STOP_DEV (1ULL << 17) +/* Disable automatic partition scanning when device is started */ +#define UBLK_F_NO_AUTO_PART_SCAN (1ULL << 18) + /* device state */ #define UBLK_S_DEV_DEAD 0 #define UBLK_S_DEV_LIVE 1 -- cgit v1.2.3 From 503efe850c7463a1e59df133b84461ef53c0361f Mon Sep 17 00:00:00 2001 From: Wang Yaxin Date: Mon, 19 Jan 2026 10:02:41 +0800 Subject: delayacct: add timestamp of delay max Problem ======= Commit 658eb5ab916d ("delayacct: add delay max to record delay peak") introduced the delay max for getdelays, which records abnormal latency peaks and helps us understand the magnitude of such delays. However, the peak latency value alone is insufficient for effective root cause analysis. Without the precise timestamp of when the peak occurred, we still lack the critical context needed to correlate it with other system events. Solution ======== To address this, we need to additionally record a precise timestamp when the maximum latency occurs. By correlating this timestamp with system logs and monitoring metrics, we can identify processes with abnormal resource usage at the same moment, which can help us to pinpoint root causes. Use Case ======== bash-4.4# ./getdelays -d -t 227 print delayacct stats ON TGID 227 CPU count real total virtual total delay total delay average delay max delay min delay max timestamp 46 188000000 192348334 4098012 0.089ms 0.429260ms 0.051205ms 2026-01-15T15:06:58 IO count delay total delay average delay max delay min delay max timestamp 0 0 0.000ms 0.000000ms 0.000000ms N/A SWAP count delay total delay average delay max delay min delay max timestamp 0 0 0.000ms 0.000000ms 0.000000ms N/A RECLAIM count delay total delay average delay max delay min delay max timestamp 0 0 0.000ms 0.000000ms 0.000000ms N/A THRAS HING count delay total delay average delay max delay min delay max timestamp 0 0 0.000ms 0.000000ms 0.000000ms N/A COMPACT count delay total delay average delay max delay min delay max timestamp 0 0 0.000ms 0.000000ms 0.000000ms N/A WPCOPY count delay total delay average delay max delay min delay max timestamp 182 19413338 0.107ms 0.547353ms 0.022462ms 2026-01-15T15:05:24 IRQ count delay total delay average delay max delay min delay max timestamp 0 0 0.000ms 0.000000ms 0.000000ms N/A Link: https://lkml.kernel.org/r/20260119100241520gWubW8-5QfhSf9gjqcc_E@zte.com.cn Signed-off-by: Wang Yaxin Cc: Fan Yu Cc: Jonathan Corbet Cc: xu xin Cc: Yang Yang Signed-off-by: Andrew Morton --- Documentation/accounting/delay-accounting.rst | 32 ++--- include/linux/delayacct.h | 8 ++ include/linux/sched.h | 5 + include/uapi/linux/taskstats.h | 22 +++- kernel/delayacct.c | 31 +++-- kernel/sched/stats.h | 8 +- tools/accounting/getdelays.c | 172 ++++++++++++++++++++++---- 7 files changed, 223 insertions(+), 55 deletions(-) (limited to 'include/uapi/linux') diff --git a/Documentation/accounting/delay-accounting.rst b/Documentation/accounting/delay-accounting.rst index 86d7902a657f..e209c46241b0 100644 --- a/Documentation/accounting/delay-accounting.rst +++ b/Documentation/accounting/delay-accounting.rst @@ -107,22 +107,22 @@ Get sum and peak of delays, since system boot, for all pids with tgid 242:: TGID 242 - CPU count real total virtual total delay total delay average delay max delay min - 39 156000000 156576579 2111069 0.054ms 0.212296ms 0.031307ms - IO count delay total delay average delay max delay min - 0 0 0.000ms 0.000000ms 0.000000ms - SWAP count delay total delay average delay max delay min - 0 0 0.000ms 0.000000ms 0.000000ms - RECLAIM count delay total delay average delay max delay min - 0 0 0.000ms 0.000000ms 0.000000ms - THRASHING count delay total delay average delay max delay min - 0 0 0.000ms 0.000000ms 0.000000ms - COMPACT count delay total delay average delay max delay min - 0 0 0.000ms 0.000000ms 0.000000ms - WPCOPY count delay total delay average delay max delay min - 156 11215873 0.072ms 0.207403ms 0.033913ms - IRQ count delay total delay average delay max delay min - 0 0 0.000ms 0.000000ms 0.000000ms + CPU count real total virtual total delay total delay average delay max delay min delay max timestamp + 46 188000000 192348334 4098012 0.089ms 0.429260ms 0.051205ms 2026-01-15T15:06:58 + IO count delay total delay average delay max delay min delay max timestamp + 0 0 0.000ms 0.000000ms 0.000000ms N/A + SWAP count delay total delay average delay max delay min delay max timestamp + 0 0 0.000ms 0.000000ms 0.000000ms N/A + RECLAIM count delay total delay average delay max delay min delay max timestamp + 0 0 0.000ms 0.000000ms 0.000000ms N/A + THRASHING count delay total delay average delay max delay min delay max timestamp + 0 0 0.000ms 0.000000ms 0.000000ms N/A + COMPACT count delay total delay average delay max delay min delay max timestamp + 0 0 0.000ms 0.000000ms 0.000000ms N/A + WPCOPY count delay total delay average delay max delay min delay max timestamp + 182 19413338 0.107ms 0.547353ms 0.022462ms 2026-01-15T15:05:24 + IRQ count delay total delay average delay max delay min delay max timestamp + 0 0 0.000ms 0.000000ms 0.000000ms N/A Get IO accounting for pid 1, it works only with -p:: diff --git a/include/linux/delayacct.h b/include/linux/delayacct.h index 800dcc360db2..ecb06f16d22c 100644 --- a/include/linux/delayacct.h +++ b/include/linux/delayacct.h @@ -69,6 +69,14 @@ struct task_delay_info { u32 compact_count; /* total count of memory compact */ u32 wpcopy_count; /* total count of write-protect copy */ u32 irq_count; /* total count of IRQ/SOFTIRQ */ + + struct timespec64 blkio_delay_max_ts; + struct timespec64 swapin_delay_max_ts; + struct timespec64 freepages_delay_max_ts; + struct timespec64 thrashing_delay_max_ts; + struct timespec64 compact_delay_max_ts; + struct timespec64 wpcopy_delay_max_ts; + struct timespec64 irq_delay_max_ts; }; #endif diff --git a/include/linux/sched.h b/include/linux/sched.h index da0133524d08..1d22b6229b95 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -49,6 +49,7 @@ #include #include #include +#include #ifndef COMPILE_OFFSETS #include #endif @@ -86,6 +87,7 @@ struct signal_struct; struct task_delay_info; struct task_group; struct task_struct; +struct timespec64; struct user_event_mm; #include @@ -435,6 +437,9 @@ struct sched_info { /* When were we last queued to run? */ unsigned long long last_queued; + /* Timestamp of max time spent waiting on a runqueue: */ + struct timespec64 max_run_delay_ts; + #endif /* CONFIG_SCHED_INFO */ }; diff --git a/include/uapi/linux/taskstats.h b/include/uapi/linux/taskstats.h index 5929030d4e8b..1b31e8e14d2f 100644 --- a/include/uapi/linux/taskstats.h +++ b/include/uapi/linux/taskstats.h @@ -18,6 +18,16 @@ #define _LINUX_TASKSTATS_H #include +#ifdef __KERNEL__ +#include +#else +#ifndef _LINUX_TIME64_H +struct timespec64 { + __s64 tv_sec; /* seconds */ + long tv_nsec; /* nanoseconds */ +}; +#endif +#endif /* Format for per-task data returned to userland when * - a task exits @@ -34,7 +44,7 @@ */ -#define TASKSTATS_VERSION 16 +#define TASKSTATS_VERSION 17 #define TS_COMM_LEN 32 /* should be >= TASK_COMM_LEN * in linux/sched.h */ @@ -230,6 +240,16 @@ struct taskstats { __u64 irq_delay_max; __u64 irq_delay_min; + + /*v17: delay max timestamp record*/ + struct timespec64 cpu_delay_max_ts; + struct timespec64 blkio_delay_max_ts; + struct timespec64 swapin_delay_max_ts; + struct timespec64 freepages_delay_max_ts; + struct timespec64 thrashing_delay_max_ts; + struct timespec64 compact_delay_max_ts; + struct timespec64 wpcopy_delay_max_ts; + struct timespec64 irq_delay_max_ts; }; diff --git a/kernel/delayacct.c b/kernel/delayacct.c index 30e7912ebb0d..d58ffc63bcba 100644 --- a/kernel/delayacct.c +++ b/kernel/delayacct.c @@ -18,6 +18,7 @@ do { \ d->type##_delay_max = tsk->delays->type##_delay_max; \ d->type##_delay_min = tsk->delays->type##_delay_min; \ + d->type##_delay_max_ts = tsk->delays->type##_delay_max_ts; \ tmp = d->type##_delay_total + tsk->delays->type##_delay; \ d->type##_delay_total = (tmp < d->type##_delay_total) ? 0 : tmp; \ d->type##_count += tsk->delays->type##_count; \ @@ -104,7 +105,8 @@ void __delayacct_tsk_init(struct task_struct *tsk) * Finish delay accounting for a statistic using its timestamps (@start), * accumulator (@total) and @count */ -static void delayacct_end(raw_spinlock_t *lock, u64 *start, u64 *total, u32 *count, u64 *max, u64 *min) +static void delayacct_end(raw_spinlock_t *lock, u64 *start, u64 *total, u32 *count, + u64 *max, u64 *min, struct timespec64 *ts) { s64 ns = local_clock() - *start; unsigned long flags; @@ -113,8 +115,10 @@ static void delayacct_end(raw_spinlock_t *lock, u64 *start, u64 *total, u32 *cou raw_spin_lock_irqsave(lock, flags); *total += ns; (*count)++; - if (ns > *max) + if (ns > *max) { *max = ns; + ktime_get_real_ts64(ts); + } if (*min == 0 || ns < *min) *min = ns; raw_spin_unlock_irqrestore(lock, flags); @@ -137,7 +141,8 @@ void __delayacct_blkio_end(struct task_struct *p) &p->delays->blkio_delay, &p->delays->blkio_count, &p->delays->blkio_delay_max, - &p->delays->blkio_delay_min); + &p->delays->blkio_delay_min, + &p->delays->blkio_delay_max_ts); } int delayacct_add_tsk(struct taskstats *d, struct task_struct *tsk) @@ -170,6 +175,7 @@ int delayacct_add_tsk(struct taskstats *d, struct task_struct *tsk) d->cpu_delay_max = tsk->sched_info.max_run_delay; d->cpu_delay_min = tsk->sched_info.min_run_delay; + d->cpu_delay_max_ts = tsk->sched_info.max_run_delay_ts; tmp = (s64)d->cpu_delay_total + t2; d->cpu_delay_total = (tmp < (s64)d->cpu_delay_total) ? 0 : tmp; tmp = (s64)d->cpu_run_virtual_total + t3; @@ -217,7 +223,8 @@ void __delayacct_freepages_end(void) ¤t->delays->freepages_delay, ¤t->delays->freepages_count, ¤t->delays->freepages_delay_max, - ¤t->delays->freepages_delay_min); + ¤t->delays->freepages_delay_min, + ¤t->delays->freepages_delay_max_ts); } void __delayacct_thrashing_start(bool *in_thrashing) @@ -241,7 +248,8 @@ void __delayacct_thrashing_end(bool *in_thrashing) ¤t->delays->thrashing_delay, ¤t->delays->thrashing_count, ¤t->delays->thrashing_delay_max, - ¤t->delays->thrashing_delay_min); + ¤t->delays->thrashing_delay_min, + ¤t->delays->thrashing_delay_max_ts); } void __delayacct_swapin_start(void) @@ -256,7 +264,8 @@ void __delayacct_swapin_end(void) ¤t->delays->swapin_delay, ¤t->delays->swapin_count, ¤t->delays->swapin_delay_max, - ¤t->delays->swapin_delay_min); + ¤t->delays->swapin_delay_min, + ¤t->delays->swapin_delay_max_ts); } void __delayacct_compact_start(void) @@ -271,7 +280,8 @@ void __delayacct_compact_end(void) ¤t->delays->compact_delay, ¤t->delays->compact_count, ¤t->delays->compact_delay_max, - ¤t->delays->compact_delay_min); + ¤t->delays->compact_delay_min, + ¤t->delays->compact_delay_max_ts); } void __delayacct_wpcopy_start(void) @@ -286,7 +296,8 @@ void __delayacct_wpcopy_end(void) ¤t->delays->wpcopy_delay, ¤t->delays->wpcopy_count, ¤t->delays->wpcopy_delay_max, - ¤t->delays->wpcopy_delay_min); + ¤t->delays->wpcopy_delay_min, + ¤t->delays->wpcopy_delay_max_ts); } void __delayacct_irq(struct task_struct *task, u32 delta) @@ -296,8 +307,10 @@ void __delayacct_irq(struct task_struct *task, u32 delta) raw_spin_lock_irqsave(&task->delays->lock, flags); task->delays->irq_delay += delta; task->delays->irq_count++; - if (delta > task->delays->irq_delay_max) + if (delta > task->delays->irq_delay_max) { task->delays->irq_delay_max = delta; + ktime_get_real_ts64(&task->delays->irq_delay_max_ts); + } if (delta && (!task->delays->irq_delay_min || delta < task->delays->irq_delay_min)) task->delays->irq_delay_min = delta; raw_spin_unlock_irqrestore(&task->delays->lock, flags); diff --git a/kernel/sched/stats.h b/kernel/sched/stats.h index c903f1a42891..a612cf253c87 100644 --- a/kernel/sched/stats.h +++ b/kernel/sched/stats.h @@ -253,8 +253,10 @@ static inline void sched_info_dequeue(struct rq *rq, struct task_struct *t) delta = rq_clock(rq) - t->sched_info.last_queued; t->sched_info.last_queued = 0; t->sched_info.run_delay += delta; - if (delta > t->sched_info.max_run_delay) + if (delta > t->sched_info.max_run_delay) { t->sched_info.max_run_delay = delta; + ktime_get_real_ts64(&t->sched_info.max_run_delay_ts); + } if (delta && (!t->sched_info.min_run_delay || delta < t->sched_info.min_run_delay)) t->sched_info.min_run_delay = delta; rq_sched_info_dequeue(rq, delta); @@ -278,8 +280,10 @@ static void sched_info_arrive(struct rq *rq, struct task_struct *t) t->sched_info.run_delay += delta; t->sched_info.last_arrival = now; t->sched_info.pcount++; - if (delta > t->sched_info.max_run_delay) + if (delta > t->sched_info.max_run_delay) { t->sched_info.max_run_delay = delta; + ktime_get_real_ts64(&t->sched_info.max_run_delay_ts); + } if (delta && (!t->sched_info.min_run_delay || delta < t->sched_info.min_run_delay)) t->sched_info.min_run_delay = delta; diff --git a/tools/accounting/getdelays.c b/tools/accounting/getdelays.c index 21cb3c3d1331..64796c0223be 100644 --- a/tools/accounting/getdelays.c +++ b/tools/accounting/getdelays.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -194,6 +195,37 @@ static int get_family_id(int sd) #define average_ms(t, c) (t / 1000000ULL / (c ? c : 1)) #define delay_ms(t) (t / 1000000ULL) +/* + * Format timespec64 to human readable string (YYYY-MM-DD HH:MM:SS) + * Returns formatted string or "N/A" if timestamp is zero + */ +static const char *format_timespec64(struct timespec64 *ts) +{ + static char buffer[32]; + struct tm tm_info; + time_t time_sec; + + /* Check if timestamp is zero (not set) */ + if (ts->tv_sec == 0 && ts->tv_nsec == 0) + return "N/A"; + + time_sec = (time_t)ts->tv_sec; + + /* Use thread-safe localtime_r */ + if (localtime_r(&time_sec, &tm_info) == NULL) + return "N/A"; + + snprintf(buffer, sizeof(buffer), "%04d-%02d-%02dT%02d:%02d:%02d", + tm_info.tm_year + 1900, + tm_info.tm_mon + 1, + tm_info.tm_mday, + tm_info.tm_hour, + tm_info.tm_min, + tm_info.tm_sec); + + return buffer; +} + /* * Version compatibility note: * Field availability depends on taskstats version (t->version), @@ -205,13 +237,28 @@ static int get_family_id(int sd) * version >= 13 - supports WPCOPY statistics * version >= 14 - supports IRQ statistics * version >= 16 - supports *_max and *_min delay statistics + * version >= 17 - supports delay max timestamp statistics * * Always verify version before accessing version-dependent fields * to maintain backward compatibility. */ #define PRINT_CPU_DELAY(version, t) \ do { \ - if (version >= 16) { \ + if (version >= 17) { \ + printf("%-10s%15s%15s%15s%15s%15s%15s%15s%25s\n", \ + "CPU", "count", "real total", "virtual total", \ + "delay total", "delay average", "delay max", \ + "delay min", "delay max timestamp"); \ + printf(" %15llu%15llu%15llu%15llu%15.3fms%13.6fms%13.6fms%23s\n", \ + (unsigned long long)(t)->cpu_count, \ + (unsigned long long)(t)->cpu_run_real_total, \ + (unsigned long long)(t)->cpu_run_virtual_total, \ + (unsigned long long)(t)->cpu_delay_total, \ + average_ms((double)(t)->cpu_delay_total, (t)->cpu_count), \ + delay_ms((double)(t)->cpu_delay_max), \ + delay_ms((double)(t)->cpu_delay_min), \ + format_timespec64(&(t)->cpu_delay_max_ts)); \ + } else if (version >= 16) { \ printf("%-10s%15s%15s%15s%15s%15s%15s%15s\n", \ "CPU", "count", "real total", "virtual total", \ "delay total", "delay average", "delay max", "delay min"); \ @@ -257,44 +304,115 @@ static int get_family_id(int sd) } \ } while (0) +#define PRINT_FILED_DELAY_WITH_TS(name, version, t, count, total, max, min, max_ts) \ + do { \ + if (version >= 17) { \ + printf("%-10s%15s%15s%15s%15s%15s%25s\n", \ + name, "count", "delay total", "delay average", \ + "delay max", "delay min", "delay max timestamp"); \ + printf(" %15llu%15llu%15.3fms%13.6fms%13.6fms%23s\n", \ + (unsigned long long)(t)->count, \ + (unsigned long long)(t)->total, \ + average_ms((double)(t)->total, (t)->count), \ + delay_ms((double)(t)->max), \ + delay_ms((double)(t)->min), \ + format_timespec64(&(t)->max_ts)); \ + } else if (version >= 16) { \ + printf("%-10s%15s%15s%15s%15s%15s\n", \ + name, "count", "delay total", "delay average", \ + "delay max", "delay min"); \ + printf(" %15llu%15llu%15.3fms%13.6fms%13.6fms\n", \ + (unsigned long long)(t)->count, \ + (unsigned long long)(t)->total, \ + average_ms((double)(t)->total, (t)->count), \ + delay_ms((double)(t)->max), \ + delay_ms((double)(t)->min)); \ + } else { \ + printf("%-10s%15s%15s%15s\n", \ + name, "count", "delay total", "delay average"); \ + printf(" %15llu%15llu%15.3fms\n", \ + (unsigned long long)(t)->count, \ + (unsigned long long)(t)->total, \ + average_ms((double)(t)->total, (t)->count)); \ + } \ + } while (0) + static void print_delayacct(struct taskstats *t) { printf("\n\n"); PRINT_CPU_DELAY(t->version, t); - PRINT_FILED_DELAY("IO", t->version, t, - blkio_count, blkio_delay_total, - blkio_delay_max, blkio_delay_min); + /* Use new macro with timestamp support for version >= 17 */ + if (t->version >= 17) { + PRINT_FILED_DELAY_WITH_TS("IO", t->version, t, + blkio_count, blkio_delay_total, + blkio_delay_max, blkio_delay_min, blkio_delay_max_ts); - PRINT_FILED_DELAY("SWAP", t->version, t, - swapin_count, swapin_delay_total, - swapin_delay_max, swapin_delay_min); + PRINT_FILED_DELAY_WITH_TS("SWAP", t->version, t, + swapin_count, swapin_delay_total, + swapin_delay_max, swapin_delay_min, swapin_delay_max_ts); - PRINT_FILED_DELAY("RECLAIM", t->version, t, - freepages_count, freepages_delay_total, - freepages_delay_max, freepages_delay_min); + PRINT_FILED_DELAY_WITH_TS("RECLAIM", t->version, t, + freepages_count, freepages_delay_total, + freepages_delay_max, freepages_delay_min, freepages_delay_max_ts); - PRINT_FILED_DELAY("THRASHING", t->version, t, - thrashing_count, thrashing_delay_total, - thrashing_delay_max, thrashing_delay_min); + PRINT_FILED_DELAY_WITH_TS("THRASHING", t->version, t, + thrashing_count, thrashing_delay_total, + thrashing_delay_max, thrashing_delay_min, thrashing_delay_max_ts); - if (t->version >= 11) { - PRINT_FILED_DELAY("COMPACT", t->version, t, - compact_count, compact_delay_total, - compact_delay_max, compact_delay_min); - } + if (t->version >= 11) { + PRINT_FILED_DELAY_WITH_TS("COMPACT", t->version, t, + compact_count, compact_delay_total, + compact_delay_max, compact_delay_min, compact_delay_max_ts); + } - if (t->version >= 13) { - PRINT_FILED_DELAY("WPCOPY", t->version, t, - wpcopy_count, wpcopy_delay_total, - wpcopy_delay_max, wpcopy_delay_min); - } + if (t->version >= 13) { + PRINT_FILED_DELAY_WITH_TS("WPCOPY", t->version, t, + wpcopy_count, wpcopy_delay_total, + wpcopy_delay_max, wpcopy_delay_min, wpcopy_delay_max_ts); + } - if (t->version >= 14) { - PRINT_FILED_DELAY("IRQ", t->version, t, - irq_count, irq_delay_total, - irq_delay_max, irq_delay_min); + if (t->version >= 14) { + PRINT_FILED_DELAY_WITH_TS("IRQ", t->version, t, + irq_count, irq_delay_total, + irq_delay_max, irq_delay_min, irq_delay_max_ts); + } + } else { + /* Use original macro for older versions */ + PRINT_FILED_DELAY("IO", t->version, t, + blkio_count, blkio_delay_total, + blkio_delay_max, blkio_delay_min); + + PRINT_FILED_DELAY("SWAP", t->version, t, + swapin_count, swapin_delay_total, + swapin_delay_max, swapin_delay_min); + + PRINT_FILED_DELAY("RECLAIM", t->version, t, + freepages_count, freepages_delay_total, + freepages_delay_max, freepages_delay_min); + + PRINT_FILED_DELAY("THRASHING", t->version, t, + thrashing_count, thrashing_delay_total, + thrashing_delay_max, thrashing_delay_min); + + if (t->version >= 11) { + PRINT_FILED_DELAY("COMPACT", t->version, t, + compact_count, compact_delay_total, + compact_delay_max, compact_delay_min); + } + + if (t->version >= 13) { + PRINT_FILED_DELAY("WPCOPY", t->version, t, + wpcopy_count, wpcopy_delay_total, + wpcopy_delay_max, wpcopy_delay_min); + } + + if (t->version >= 14) { + PRINT_FILED_DELAY("IRQ", t->version, t, + irq_count, irq_delay_total, + irq_delay_max, irq_delay_min); + } } } -- cgit v1.2.3 From 072e6f7f416f5d17be71000b31fb108651ad360d Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 30 Jan 2026 16:21:06 +0100 Subject: wifi: cfg80211: add initial UHR support Add initial support for making UHR connections (or suppressing that), adding UHR capable stations on the AP side, encoding and decoding UHR MCSes (except rate calculation for the new MCSes 17, 19, 20 and 23) as well as regulatory support. Link: https://patch.msgid.link/20260130164259.54cc12fbb307.I26126bebd83c7ab17e99827489f946ceabb3521f@changeid Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 58 ++++++++++++++++++++++-- include/uapi/linux/nl80211.h | 30 +++++++++++++ net/wireless/nl80211.c | 102 +++++++++++++++++++++++++++++++++++++++++-- net/wireless/reg.c | 4 +- net/wireless/util.c | 101 ++++++++++++++++++++++++++++++++---------- 5 files changed, 265 insertions(+), 30 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 7911ed58abbb..fc01de19c798 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -7,7 +7,7 @@ * Copyright 2006-2010 Johannes Berg * Copyright 2013-2014 Intel Mobile Communications GmbH * Copyright 2015-2017 Intel Deutschland GmbH - * Copyright (C) 2018-2025 Intel Corporation + * Copyright (C) 2018-2026 Intel Corporation */ #include @@ -126,6 +126,7 @@ struct wiphy; * @IEEE80211_CHAN_NO_4MHZ: 4 MHz bandwidth is not permitted on this channel. * @IEEE80211_CHAN_NO_8MHZ: 8 MHz bandwidth is not permitted on this channel. * @IEEE80211_CHAN_NO_16MHZ: 16 MHz bandwidth is not permitted on this channel. + * @IEEE80211_CHAN_NO_UHR: UHR operation is not permitted on this channel. */ enum ieee80211_channel_flags { IEEE80211_CHAN_DISABLED = BIT(0), @@ -143,6 +144,7 @@ enum ieee80211_channel_flags { IEEE80211_CHAN_NO_10MHZ = BIT(12), IEEE80211_CHAN_NO_HE = BIT(13), /* can use free bits here */ + IEEE80211_CHAN_NO_UHR = BIT(18), IEEE80211_CHAN_NO_320MHZ = BIT(19), IEEE80211_CHAN_NO_EHT = BIT(20), IEEE80211_CHAN_DFS_CONCURRENT = BIT(21), @@ -429,6 +431,18 @@ struct ieee80211_sta_eht_cap { u8 eht_ppe_thres[IEEE80211_EHT_PPE_THRES_MAX_LEN]; }; +/** + * struct ieee80211_sta_uhr_cap - STA's UHR capabilities + * @has_uhr: true iff UHR is supported and data is valid + * @mac: fixed MAC capabilities + * @phy: fixed PHY capabilities + */ +struct ieee80211_sta_uhr_cap { + bool has_uhr; + struct ieee80211_uhr_cap_mac mac; + struct ieee80211_uhr_cap_phy phy; +}; + /* sparse defines __CHECKER__; see Documentation/dev-tools/sparse.rst */ #ifdef __CHECKER__ /* @@ -454,6 +468,7 @@ struct ieee80211_sta_eht_cap { * @he_6ghz_capa: HE 6 GHz capabilities, must be filled in for a * 6 GHz band channel (and 0 may be valid value). * @eht_cap: STA's EHT capabilities + * @uhr_cap: STA's UHR capabilities * @vendor_elems: vendor element(s) to advertise * @vendor_elems.data: vendor element(s) data * @vendor_elems.len: vendor element(s) length @@ -463,6 +478,7 @@ struct ieee80211_sband_iftype_data { struct ieee80211_sta_he_cap he_cap; struct ieee80211_he_6ghz_capa he_6ghz_capa; struct ieee80211_sta_eht_cap eht_cap; + struct ieee80211_sta_uhr_cap uhr_cap; struct { const u8 *data; unsigned int len; @@ -704,6 +720,26 @@ ieee80211_get_eht_iftype_cap(const struct ieee80211_supported_band *sband, return NULL; } +/** + * ieee80211_get_uhr_iftype_cap - return UHR capabilities for an sband's iftype + * @sband: the sband to search for the iftype on + * @iftype: enum nl80211_iftype + * + * Return: pointer to the struct ieee80211_sta_uhr_cap, or NULL is none found + */ +static inline const struct ieee80211_sta_uhr_cap * +ieee80211_get_uhr_iftype_cap(const struct ieee80211_supported_band *sband, + enum nl80211_iftype iftype) +{ + const struct ieee80211_sband_iftype_data *data = + ieee80211_get_sband_iftype_data(sband, iftype); + + if (data && data->uhr_cap.has_uhr) + return &data->uhr_cap; + + return NULL; +} + /** * wiphy_read_of_freq_limits - read frequency limits from device tree * @@ -1486,6 +1522,7 @@ struct cfg80211_s1g_short_beacon { * @he_cap: HE capabilities (or %NULL if HE isn't enabled) * @eht_cap: EHT capabilities (or %NULL if EHT isn't enabled) * @eht_oper: EHT operation IE (or %NULL if EHT isn't enabled) + * @uhr_oper: UHR operation (or %NULL if UHR isn't enabled) * @ht_required: stations must support HT * @vht_required: stations must support VHT * @twt_responder: Enable Target Wait Time @@ -1525,6 +1562,7 @@ struct cfg80211_ap_settings { const struct ieee80211_he_operation *he_oper; const struct ieee80211_eht_cap_elem *eht_cap; const struct ieee80211_eht_operation *eht_oper; + const struct ieee80211_uhr_operation *uhr_oper; bool ht_required, vht_required, he_required, sae_h2e_required; bool twt_responder; u32 flags; @@ -1698,6 +1736,8 @@ struct sta_txpwr { * @eht_capa: EHT capabilities of station * @eht_capa_len: the length of the EHT capabilities * @s1g_capa: S1G capabilities of station + * @uhr_capa: UHR capabilities of the station + * @uhr_capa_len: the length of the UHR capabilities */ struct link_station_parameters { const u8 *mld_mac; @@ -1717,6 +1757,8 @@ struct link_station_parameters { const struct ieee80211_eht_cap_elem *eht_capa; u8 eht_capa_len; const struct ieee80211_s1g_cap *s1g_capa; + const struct ieee80211_uhr_cap *uhr_capa; + u8 uhr_capa_len; }; /** @@ -1898,6 +1940,11 @@ int cfg80211_check_station_change(struct wiphy *wiphy, * @RATE_INFO_FLAGS_EXTENDED_SC_DMG: 60GHz extended SC MCS * @RATE_INFO_FLAGS_EHT_MCS: EHT MCS information * @RATE_INFO_FLAGS_S1G_MCS: MCS field filled with S1G MCS + * @RATE_INFO_FLAGS_UHR_MCS: UHR MCS information + * @RATE_INFO_FLAGS_UHR_ELR_MCS: UHR ELR MCS was used + * (set together with @RATE_INFO_FLAGS_UHR_MCS) + * @RATE_INFO_FLAGS_UHR_IM: UHR Interference Mitigation + * was used */ enum rate_info_flags { RATE_INFO_FLAGS_MCS = BIT(0), @@ -1909,6 +1956,9 @@ enum rate_info_flags { RATE_INFO_FLAGS_EXTENDED_SC_DMG = BIT(6), RATE_INFO_FLAGS_EHT_MCS = BIT(7), RATE_INFO_FLAGS_S1G_MCS = BIT(8), + RATE_INFO_FLAGS_UHR_MCS = BIT(9), + RATE_INFO_FLAGS_UHR_ELR_MCS = BIT(10), + RATE_INFO_FLAGS_UHR_IM = BIT(11), }; /** @@ -1924,7 +1974,7 @@ enum rate_info_flags { * @RATE_INFO_BW_160: 160 MHz bandwidth * @RATE_INFO_BW_HE_RU: bandwidth determined by HE RU allocation * @RATE_INFO_BW_320: 320 MHz bandwidth - * @RATE_INFO_BW_EHT_RU: bandwidth determined by EHT RU allocation + * @RATE_INFO_BW_EHT_RU: bandwidth determined by EHT/UHR RU allocation * @RATE_INFO_BW_1: 1 MHz bandwidth * @RATE_INFO_BW_2: 2 MHz bandwidth * @RATE_INFO_BW_4: 4 MHz bandwidth @@ -1955,7 +2005,7 @@ enum rate_info_bw { * * @flags: bitflag of flags from &enum rate_info_flags * @legacy: bitrate in 100kbit/s for 802.11abg - * @mcs: mcs index if struct describes an HT/VHT/HE/EHT/S1G rate + * @mcs: mcs index if struct describes an HT/VHT/HE/EHT/S1G/UHR rate * @nss: number of streams (VHT & HE only) * @bw: bandwidth (from &enum rate_info_bw) * @he_gi: HE guard interval (from &enum nl80211_he_gi) @@ -3262,6 +3312,7 @@ struct cfg80211_ml_reconf_req { * Drivers shall disable MLO features for the current association if this * flag is not set. * @ASSOC_REQ_SPP_AMSDU: SPP A-MSDUs will be used on this connection (if any) + * @ASSOC_REQ_DISABLE_UHR: Disable UHR */ enum cfg80211_assoc_req_flags { ASSOC_REQ_DISABLE_HT = BIT(0), @@ -3272,6 +3323,7 @@ enum cfg80211_assoc_req_flags { ASSOC_REQ_DISABLE_EHT = BIT(5), CONNECT_REQ_MLO_SUPPORT = BIT(6), ASSOC_REQ_SPP_AMSDU = BIT(7), + ASSOC_REQ_DISABLE_UHR = BIT(8), }; /** diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index 706a98686068..b63f71850906 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -2977,6 +2977,13 @@ enum nl80211_commands { * @NL80211_ATTR_EPP_PEER: A flag attribute to indicate if the peer is an EPP * STA. Used with %NL80211_CMD_NEW_STA and %NL80211_CMD_ADD_LINK_STA * + * @NL80211_ATTR_UHR_CAPABILITY: UHR Capability information element (from + * association request when used with NL80211_CMD_NEW_STATION). Can be set + * only if HE/EHT are also available. + * @NL80211_ATTR_DISABLE_UHR: Force UHR capable interfaces to disable + * this feature during association. This is a flag attribute. + * Currently only supported in mac80211 drivers. + * * @NUM_NL80211_ATTR: total number of nl80211_attrs available * @NL80211_ATTR_MAX: highest attribute number currently defined * @__NL80211_ATTR_AFTER_LAST: internal use @@ -3547,6 +3554,9 @@ enum nl80211_attrs { NL80211_ATTR_EPP_PEER, + NL80211_ATTR_UHR_CAPABILITY, + NL80211_ATTR_DISABLE_UHR, + /* add attributes here, update the policy in nl80211.c */ __NL80211_ATTR_AFTER_LAST, @@ -3899,6 +3909,12 @@ enum nl80211_eht_ru_alloc { * @NL80211_RATE_INFO_4_MHZ_WIDTH: 4 MHz S1G rate * @NL80211_RATE_INFO_8_MHZ_WIDTH: 8 MHz S1G rate * @NL80211_RATE_INFO_16_MHZ_WIDTH: 16 MHz S1G rate + * @NL80211_RATE_INFO_UHR_MCS: UHR MCS index (u8, 0-15, 17, 19, 20, 23) + * Note that the other EHT attributes (such as @NL80211_RATE_INFO_EHT_NSS) + * are used in conjunction with this where applicable + * @NL80211_RATE_INFO_UHR_ELR: UHR ELR flag, which restricts NSS to 1, + * MCS to 0 or 1, and GI to %NL80211_RATE_INFO_EHT_GI_1_6. + * @NL80211_RATE_INFO_UHR_IM: UHR Interference Mitigation flag * @__NL80211_RATE_INFO_AFTER_LAST: internal use */ enum nl80211_rate_info { @@ -3932,6 +3948,9 @@ enum nl80211_rate_info { NL80211_RATE_INFO_4_MHZ_WIDTH, NL80211_RATE_INFO_8_MHZ_WIDTH, NL80211_RATE_INFO_16_MHZ_WIDTH, + NL80211_RATE_INFO_UHR_MCS, + NL80211_RATE_INFO_UHR_ELR, + NL80211_RATE_INFO_UHR_IM, /* keep last */ __NL80211_RATE_INFO_AFTER_LAST, @@ -4254,6 +4273,10 @@ enum nl80211_mpath_info { * capabilities element * @NL80211_BAND_IFTYPE_ATTR_EHT_CAP_PPE: EHT PPE thresholds information as * defined in EHT capabilities element + * @NL80211_BAND_IFTYPE_ATTR_UHR_CAP_MAC: UHR MAC capabilities as in UHR + * capabilities element + * @NL80211_BAND_IFTYPE_ATTR_UHR_CAP_PHY: UHR PHY capabilities as in UHR + * capabilities element * @__NL80211_BAND_IFTYPE_ATTR_AFTER_LAST: internal use * @NL80211_BAND_IFTYPE_ATTR_MAX: highest band attribute currently defined */ @@ -4271,6 +4294,8 @@ enum nl80211_band_iftype_attr { NL80211_BAND_IFTYPE_ATTR_EHT_CAP_PHY, NL80211_BAND_IFTYPE_ATTR_EHT_CAP_MCS_SET, NL80211_BAND_IFTYPE_ATTR_EHT_CAP_PPE, + NL80211_BAND_IFTYPE_ATTR_UHR_CAP_MAC, + NL80211_BAND_IFTYPE_ATTR_UHR_CAP_PHY, /* keep last */ __NL80211_BAND_IFTYPE_ATTR_AFTER_LAST, @@ -4453,6 +4478,8 @@ enum nl80211_wmm_rule { * @NL80211_FREQUENCY_ATTR_S1G_NO_PRIMARY: Channel is not permitted for use * as a primary channel. Does not prevent the channel from existing * as a non-primary subchannel. Only applicable to S1G channels. + * @NL80211_FREQUENCY_ATTR_NO_UHR: UHR operation is not allowed on this channel + * in current regulatory domain. * @NL80211_FREQUENCY_ATTR_MAX: highest frequency attribute number * currently defined * @__NL80211_FREQUENCY_ATTR_AFTER_LAST: internal use @@ -4502,6 +4529,7 @@ enum nl80211_frequency_attr { NL80211_FREQUENCY_ATTR_NO_8MHZ, NL80211_FREQUENCY_ATTR_NO_16MHZ, NL80211_FREQUENCY_ATTR_S1G_NO_PRIMARY, + NL80211_FREQUENCY_ATTR_NO_UHR, /* keep last */ __NL80211_FREQUENCY_ATTR_AFTER_LAST, @@ -4715,6 +4743,7 @@ enum nl80211_sched_scan_match_attr { * despite NO_IR configuration. * @NL80211_RRF_ALLOW_20MHZ_ACTIVITY: Allow activity in 20 MHz bandwidth, * despite NO_IR configuration. + * @NL80211_RRF_NO_UHR: UHR operation not allowed */ enum nl80211_reg_rule_flags { NL80211_RRF_NO_OFDM = 1 << 0, @@ -4741,6 +4770,7 @@ enum nl80211_reg_rule_flags { NL80211_RRF_NO_6GHZ_AFC_CLIENT = 1 << 23, NL80211_RRF_ALLOW_6GHZ_VLP_AP = 1 << 24, NL80211_RRF_ALLOW_20MHZ_ACTIVITY = 1 << 25, + NL80211_RRF_NO_UHR = 1 << 26, }; #define NL80211_RRF_PASSIVE_SCAN NL80211_RRF_NO_IR diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 9aa83a6943a2..6e58b238a1f8 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -332,6 +332,15 @@ static int validate_nan_cluster_id(const struct nlattr *attr, return 0; } +static int validate_uhr_capa(const struct nlattr *attr, + struct netlink_ext_ack *extack) +{ + const u8 *data = nla_data(attr); + unsigned int len = nla_len(attr); + + return ieee80211_uhr_capa_size_ok(data, len, false); +} + /* policy for the attributes */ static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR]; @@ -934,6 +943,9 @@ static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = { [NL80211_ATTR_BSS_PARAM] = { .type = NLA_FLAG }, [NL80211_ATTR_S1G_PRIMARY_2MHZ] = { .type = NLA_FLAG }, [NL80211_ATTR_EPP_PEER] = { .type = NLA_FLAG }, + [NL80211_ATTR_UHR_CAPABILITY] = + NLA_POLICY_VALIDATE_FN(NLA_BINARY, validate_uhr_capa, 255), + [NL80211_ATTR_DISABLE_UHR] = { .type = NLA_FLAG }, }; /* policy for the key attributes */ @@ -1319,6 +1331,9 @@ static int nl80211_msg_put_channel(struct sk_buff *msg, struct wiphy *wiphy, if ((chan->flags & IEEE80211_CHAN_S1G_NO_PRIMARY) && nla_put_flag(msg, NL80211_FREQUENCY_ATTR_S1G_NO_PRIMARY)) goto nla_put_failure; + if ((chan->flags & IEEE80211_CHAN_NO_UHR) && + nla_put_flag(msg, NL80211_FREQUENCY_ATTR_NO_UHR)) + goto nla_put_failure; } if (nla_put_u32(msg, NL80211_FREQUENCY_ATTR_MAX_TX_POWER, @@ -1954,6 +1969,7 @@ nl80211_send_iftype_data(struct sk_buff *msg, { const struct ieee80211_sta_he_cap *he_cap = &iftdata->he_cap; const struct ieee80211_sta_eht_cap *eht_cap = &iftdata->eht_cap; + const struct ieee80211_sta_uhr_cap *uhr_cap = &iftdata->uhr_cap; if (nl80211_put_iftypes(msg, NL80211_BAND_IFTYPE_ATTR_IFTYPES, iftdata->types_mask)) @@ -2005,6 +2021,14 @@ nl80211_send_iftype_data(struct sk_buff *msg, return -ENOBUFS; } + if (uhr_cap->has_uhr) { + if (nla_put(msg, NL80211_BAND_IFTYPE_ATTR_UHR_CAP_MAC, + sizeof(uhr_cap->mac), &uhr_cap->mac) || + nla_put(msg, NL80211_BAND_IFTYPE_ATTR_UHR_CAP_PHY, + sizeof(uhr_cap->phy), &uhr_cap->phy)) + return -ENOBUFS; + } + if (sband->band == NL80211_BAND_6GHZ && nla_put(msg, NL80211_BAND_IFTYPE_ATTR_HE_6GHZ_CAPA, sizeof(iftdata->he_6ghz_capa), @@ -6462,6 +6486,17 @@ static int nl80211_calculate_ap_params(struct cfg80211_ap_settings *params) cap->datalen - 1)) return -EINVAL; } + + cap = cfg80211_find_ext_elem(WLAN_EID_EXT_UHR_OPER, ies, ies_len); + if (cap) { + if (!cap->datalen) + return -EINVAL; + params->uhr_oper = (void *)(cap->data + 1); + if (!ieee80211_uhr_oper_size_ok((const u8 *)params->uhr_oper, + cap->datalen - 1, true)) + return -EINVAL; + } + return 0; } @@ -6593,6 +6628,9 @@ static int nl80211_validate_ap_phy_operation(struct cfg80211_ap_settings *params (channel->flags & IEEE80211_CHAN_NO_EHT)) return -EOPNOTSUPP; + if (params->uhr_oper && (channel->flags & IEEE80211_CHAN_NO_UHR)) + return -EOPNOTSUPP; + return 0; } @@ -7175,7 +7213,8 @@ bool nl80211_put_sta_rate(struct sk_buff *msg, struct rate_info *info, int attr) break; case RATE_INFO_BW_EHT_RU: rate_flg = 0; - WARN_ON(!(info->flags & RATE_INFO_FLAGS_EHT_MCS)); + WARN_ON(!(info->flags & RATE_INFO_FLAGS_EHT_MCS) && + !(info->flags & RATE_INFO_FLAGS_UHR_MCS)); break; } @@ -7228,6 +7267,23 @@ bool nl80211_put_sta_rate(struct sk_buff *msg, struct rate_info *info, int attr) nla_put_u8(msg, NL80211_RATE_INFO_EHT_RU_ALLOC, info->eht_ru_alloc)) return false; + } else if (info->flags & RATE_INFO_FLAGS_UHR_MCS) { + if (nla_put_u8(msg, NL80211_RATE_INFO_UHR_MCS, info->mcs)) + return false; + if (nla_put_u8(msg, NL80211_RATE_INFO_EHT_NSS, info->nss)) + return false; + if (nla_put_u8(msg, NL80211_RATE_INFO_EHT_GI, info->eht_gi)) + return false; + if (info->bw == RATE_INFO_BW_EHT_RU && + nla_put_u8(msg, NL80211_RATE_INFO_EHT_RU_ALLOC, + info->eht_ru_alloc)) + return false; + if (info->flags & RATE_INFO_FLAGS_UHR_ELR_MCS && + nla_put_flag(msg, NL80211_RATE_INFO_UHR_ELR)) + return false; + if (info->flags & RATE_INFO_FLAGS_UHR_IM && + nla_put_flag(msg, NL80211_RATE_INFO_UHR_IM)) + return false; } nla_nest_end(msg, rate); @@ -8101,7 +8157,8 @@ int cfg80211_check_station_change(struct wiphy *wiphy, if (params->ext_capab || params->link_sta_params.ht_capa || params->link_sta_params.vht_capa || params->link_sta_params.he_capa || - params->link_sta_params.eht_capa) + params->link_sta_params.eht_capa || + params->link_sta_params.uhr_capa) return -EINVAL; if (params->sta_flags_mask & BIT(NL80211_STA_FLAG_SPP_AMSDU)) return -EINVAL; @@ -8321,6 +8378,16 @@ static int nl80211_set_station_tdls(struct genl_info *info, } } + if (info->attrs[NL80211_ATTR_UHR_CAPABILITY]) { + if (!params->link_sta_params.eht_capa) + return -EINVAL; + + params->link_sta_params.uhr_capa = + nla_data(info->attrs[NL80211_ATTR_UHR_CAPABILITY]); + params->link_sta_params.uhr_capa_len = + nla_len(info->attrs[NL80211_ATTR_UHR_CAPABILITY]); + } + if (info->attrs[NL80211_ATTR_S1G_CAPABILITY]) params->link_sta_params.s1g_capa = nla_data(info->attrs[NL80211_ATTR_S1G_CAPABILITY]); @@ -8641,6 +8708,16 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info) } } + if (info->attrs[NL80211_ATTR_UHR_CAPABILITY]) { + if (!params.link_sta_params.eht_capa) + return -EINVAL; + + params.link_sta_params.uhr_capa = + nla_data(info->attrs[NL80211_ATTR_UHR_CAPABILITY]); + params.link_sta_params.uhr_capa_len = + nla_len(info->attrs[NL80211_ATTR_UHR_CAPABILITY]); + } + if (info->attrs[NL80211_ATTR_EML_CAPABILITY]) { params.eml_cap_present = true; params.eml_cap = @@ -8700,10 +8777,11 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info) params.link_sta_params.ht_capa = NULL; params.link_sta_params.vht_capa = NULL; - /* HE and EHT require WME */ + /* HE, EHT and UHR require WME */ if (params.link_sta_params.he_capa_len || params.link_sta_params.he_6ghz_capa || - params.link_sta_params.eht_capa_len) + params.link_sta_params.eht_capa_len || + params.link_sta_params.uhr_capa_len) return -EINVAL; } @@ -12376,6 +12454,9 @@ static int nl80211_associate(struct sk_buff *skb, struct genl_info *info) if (nla_get_flag(info->attrs[NL80211_ATTR_DISABLE_EHT])) req.flags |= ASSOC_REQ_DISABLE_EHT; + if (nla_get_flag(info->attrs[NL80211_ATTR_DISABLE_UHR])) + req.flags |= ASSOC_REQ_DISABLE_UHR; + if (info->attrs[NL80211_ATTR_VHT_CAPABILITY_MASK]) memcpy(&req.vht_capa_mask, nla_data(info->attrs[NL80211_ATTR_VHT_CAPABILITY_MASK]), @@ -13248,6 +13329,9 @@ static int nl80211_connect(struct sk_buff *skb, struct genl_info *info) if (nla_get_flag(info->attrs[NL80211_ATTR_DISABLE_EHT])) connect.flags |= ASSOC_REQ_DISABLE_EHT; + if (nla_get_flag(info->attrs[NL80211_ATTR_DISABLE_UHR])) + connect.flags |= ASSOC_REQ_DISABLE_UHR; + if (info->attrs[NL80211_ATTR_VHT_CAPABILITY_MASK]) memcpy(&connect.vht_capa_mask, nla_data(info->attrs[NL80211_ATTR_VHT_CAPABILITY_MASK]), @@ -17680,6 +17764,16 @@ nl80211_add_mod_link_station(struct sk_buff *skb, struct genl_info *info, } } + if (info->attrs[NL80211_ATTR_UHR_CAPABILITY]) { + if (!params.eht_capa) + return -EINVAL; + + params.uhr_capa = + nla_data(info->attrs[NL80211_ATTR_UHR_CAPABILITY]); + params.uhr_capa_len = + nla_len(info->attrs[NL80211_ATTR_UHR_CAPABILITY]); + } + if (info->attrs[NL80211_ATTR_HE_6GHZ_CAPABILITY]) params.he_6ghz_capa = nla_data(info->attrs[NL80211_ATTR_HE_6GHZ_CAPABILITY]); diff --git a/net/wireless/reg.c b/net/wireless/reg.c index 6cbfa3b78311..139cb27e5a81 100644 --- a/net/wireless/reg.c +++ b/net/wireless/reg.c @@ -5,7 +5,7 @@ * Copyright 2008-2011 Luis R. Rodriguez * Copyright 2013-2014 Intel Mobile Communications GmbH * Copyright 2017 Intel Deutschland GmbH - * Copyright (C) 2018 - 2025 Intel Corporation + * Copyright (C) 2018 - 2026 Intel Corporation * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -1605,6 +1605,8 @@ static u32 map_regdom_flags(u32 rd_flags) channel_flags |= IEEE80211_CHAN_ALLOW_6GHZ_VLP_AP; if (rd_flags & NL80211_RRF_ALLOW_20MHZ_ACTIVITY) channel_flags |= IEEE80211_CHAN_ALLOW_20MHZ_ACTIVITY; + if (rd_flags & NL80211_RRF_NO_UHR) + channel_flags |= IEEE80211_CHAN_NO_UHR; return channel_flags; } diff --git a/net/wireless/util.c b/net/wireless/util.c index 08c525835518..404fe604a8db 100644 --- a/net/wireless/util.c +++ b/net/wireless/util.c @@ -5,7 +5,7 @@ * Copyright 2007-2009 Johannes Berg * Copyright 2013-2014 Intel Mobile Communications GmbH * Copyright 2017 Intel Deutschland GmbH - * Copyright (C) 2018-2023, 2025 Intel Corporation + * Copyright (C) 2018-2023, 2025-2026 Intel Corporation */ #include #include @@ -1574,26 +1574,30 @@ static u32 cfg80211_calculate_bitrate_he(struct rate_info *rate) return result / 10000; } -static u32 cfg80211_calculate_bitrate_eht(struct rate_info *rate) +static u32 _cfg80211_calculate_bitrate_eht_uhr(struct rate_info *rate) { #define SCALE 6144 - static const u32 mcs_divisors[16] = { - 102399, /* 16.666666... */ - 51201, /* 8.333333... */ - 34134, /* 5.555555... */ - 25599, /* 4.166666... */ - 17067, /* 2.777777... */ - 12801, /* 2.083333... */ - 11377, /* 1.851725... */ - 10239, /* 1.666666... */ - 8532, /* 1.388888... */ - 7680, /* 1.250000... */ - 6828, /* 1.111111... */ - 6144, /* 1.000000... */ - 5690, /* 0.926106... */ - 5120, /* 0.833333... */ - 409600, /* 66.666666... */ - 204800, /* 33.333333... */ + static const u32 mcs_divisors[] = { + [ 0] = 102399, /* 16.666666... */ + [ 1] = 51201, /* 8.333333... */ + [ 2] = 34134, /* 5.555555... */ + [ 3] = 25599, /* 4.166666... */ + [ 4] = 17067, /* 2.777777... */ + [ 5] = 12801, /* 2.083333... */ + [ 6] = 11377, /* 1.851725... */ + [ 7] = 10239, /* 1.666666... */ + [ 8] = 8532, /* 1.388888... */ + [ 9] = 7680, /* 1.250000... */ + [10] = 6828, /* 1.111111... */ + [11] = 6144, /* 1.000000... */ + [12] = 5690, /* 0.926106... */ + [13] = 5120, /* 0.833333... */ + [14] = 409600, /* 66.666666... */ + [15] = 204800, /* 33.333333... */ + [17] = 38400, /* 6.250180... */ + [19] = 19200, /* 3.125090... */ + [20] = 15360, /* 2.500000... */ + [23] = 9600, /* 1.562545... */ }; static const u32 rates_996[3] = { 480388888, 453700000, 408333333 }; static const u32 rates_484[3] = { 229411111, 216666666, 195000000 }; @@ -1604,8 +1608,6 @@ static u32 cfg80211_calculate_bitrate_eht(struct rate_info *rate) u64 tmp; u32 result; - if (WARN_ON_ONCE(rate->mcs > 15)) - return 0; if (WARN_ON_ONCE(rate->eht_gi > NL80211_RATE_INFO_EHT_GI_3_2)) return 0; if (WARN_ON_ONCE(rate->eht_ru_alloc > @@ -1686,7 +1688,7 @@ static u32 cfg80211_calculate_bitrate_eht(struct rate_info *rate) rate->eht_ru_alloc == NL80211_RATE_INFO_EHT_RU_ALLOC_26) result = rates_26[rate->eht_gi]; else { - WARN(1, "invalid EHT MCS: bw:%d, ru:%d\n", + WARN(1, "invalid EHT or UHR MCS: bw:%d, ru:%d\n", rate->bw, rate->eht_ru_alloc); return 0; } @@ -1700,11 +1702,64 @@ static u32 cfg80211_calculate_bitrate_eht(struct rate_info *rate) tmp *= rate->nss; do_div(tmp, 8); + /* and handle interference mitigation - 0.9x */ + if (rate->flags & RATE_INFO_FLAGS_UHR_IM) { + if (WARN(rate->nss != 1 || rate->mcs == 15, + "invalid NSS or MCS for UHR IM\n")) + return 0; + tmp *= 9000; + do_div(tmp, 10000); + } + result = tmp; return result / 10000; } +static u32 cfg80211_calculate_bitrate_eht(struct rate_info *rate) +{ + if (WARN_ONCE(rate->mcs > 15, "bad EHT MCS %d\n", rate->mcs)) + return 0; + + if (WARN_ONCE(rate->flags & (RATE_INFO_FLAGS_UHR_ELR_MCS | + RATE_INFO_FLAGS_UHR_IM), + "bad EHT MCS flags 0x%x\n", rate->flags)) + return 0; + + return _cfg80211_calculate_bitrate_eht_uhr(rate); +} + +static u32 cfg80211_calculate_bitrate_uhr(struct rate_info *rate) +{ + if (rate->flags & RATE_INFO_FLAGS_UHR_ELR_MCS) { + WARN_ONCE(rate->eht_gi != NL80211_RATE_INFO_EHT_GI_1_6, + "bad UHR ELR guard interval %d\n", + rate->eht_gi); + WARN_ONCE(rate->mcs > 1, "bad UHR ELR MCS %d\n", rate->mcs); + WARN_ONCE(rate->nss != 1, "bad UHR ELR NSS %d\n", rate->nss); + WARN_ONCE(rate->bw != RATE_INFO_BW_20, + "bad UHR ELR bandwidth %d\n", + rate->bw); + WARN_ONCE(rate->flags & RATE_INFO_FLAGS_UHR_IM, + "bad UHR MCS flags 0x%x\n", rate->flags); + if (rate->mcs == 0) + return 17; + return 33; + } + + switch (rate->mcs) { + case 0 ... 15: + case 17: + case 19: + case 20: + case 23: + return _cfg80211_calculate_bitrate_eht_uhr(rate); + } + + WARN_ONCE(1, "bad UHR MCS %d\n", rate->mcs); + return 0; +} + static u32 cfg80211_calculate_bitrate_s1g(struct rate_info *rate) { /* For 1, 2, 4, 8 and 16 MHz channels */ @@ -1829,6 +1884,8 @@ u32 cfg80211_calculate_bitrate(struct rate_info *rate) return cfg80211_calculate_bitrate_he(rate); if (rate->flags & RATE_INFO_FLAGS_EHT_MCS) return cfg80211_calculate_bitrate_eht(rate); + if (rate->flags & RATE_INFO_FLAGS_UHR_MCS) + return cfg80211_calculate_bitrate_uhr(rate); if (rate->flags & RATE_INFO_FLAGS_S1G_MCS) return cfg80211_calculate_bitrate_s1g(rate); -- cgit v1.2.3 From 3495064b6d65a669b409cfe1241db4f3c540251a Mon Sep 17 00:00:00 2001 From: Babis Chalios Date: Fri, 30 Jan 2026 17:36:00 +0000 Subject: ptp: vmclock: add vm generation counter Similar to live migration, loading a VM from some saved state (aka snapshot) is also an event that calls for clock adjustments in the guest. However, guests might want to take more actions as a response to such events, e.g. as discarding UUIDs, resetting network connections, reseeding entropy pools, etc. These are actions that guests don't typically take during live migration, so add a new field in the vmclock_abi called vm_generation_counter which informs the guest about such events. Hypervisor advertises support for vm_generation_counter through the VMCLOCK_FLAG_VM_GEN_COUNTER_PRESENT flag. Users need to check the presence of this bit in vmclock_abi flags field before using this flag. Signed-off-by: Babis Chalios Signed-off-by: David Woodhouse Reviewed-by: David Woodhouse Tested-by: Takahiro Itazur Link: https://patch.msgid.link/20260130173704.12575-2-itazur@amazon.com Signed-off-by: Jakub Kicinski --- include/uapi/linux/vmclock-abi.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/vmclock-abi.h b/include/uapi/linux/vmclock-abi.h index 2d99b29ac44a..937fe00e4f33 100644 --- a/include/uapi/linux/vmclock-abi.h +++ b/include/uapi/linux/vmclock-abi.h @@ -115,6 +115,12 @@ struct vmclock_abi { * bit again after the update, using the about-to-be-valid fields. */ #define VMCLOCK_FLAG_TIME_MONOTONIC (1 << 7) + /* + * If the VM_GEN_COUNTER_PRESENT flag is set, the hypervisor will + * bump the vm_generation_counter field every time the guest is + * loaded from some save state (restored from a snapshot). + */ +#define VMCLOCK_FLAG_VM_GEN_COUNTER_PRESENT (1 << 8) __u8 pad[2]; __u8 clock_status; @@ -177,6 +183,15 @@ struct vmclock_abi { __le64 time_frac_sec; /* Units of 1/2^64 of a second */ __le64 time_esterror_nanosec; __le64 time_maxerror_nanosec; + + /* + * This field changes to another non-repeating value when the guest + * has been loaded from a snapshot. In addition to handling a + * disruption in time (which will also be signalled through the + * disruption_marker field), a guest may wish to discard UUIDs, + * reset network connections, reseed entropy, etc. + */ + __le64 vm_generation_counter; }; #endif /* __VMCLOCK_ABI_H__ */ -- cgit v1.2.3 From 3b1526ddb25452385b52f2588b655f524a57070b Mon Sep 17 00:00:00 2001 From: Babis Chalios Date: Fri, 30 Jan 2026 17:36:01 +0000 Subject: ptp: vmclock: support device notifications Add optional support for device notifications in VMClock. When supported, the hypervisor will send a device notification every time it updates the seq_count to a new even value. Moreover, add support for poll() in VMClock as a means to propagate this notification to user space. poll() will return a POLLIN event to listeners every time seq_count changes to a value different than the one last seen (since open() or last read()/pread()). This means that when poll() returns a POLLIN event, listeners need to use read() to observe what has changed and update the reader's view of seq_count. In other words, after a poll() returned, all subsequent calls to poll() will immediately return with a POLLIN event until the listener calls read(). The device advertises support for the notification mechanism by setting flag VMCLOCK_FLAG_NOTIFICATION_PRESENT in vmclock_abi flags field. If the flag is not present the driver won't setup the ACPI notification handler and poll() will always immediately return POLLHUP. Signed-off-by: Babis Chalios Signed-off-by: David Woodhouse Signed-off-by: Takahiro Itazuri Reviewed-by: David Woodhouse Tested-by: Takahiro Itazuri Link: https://patch.msgid.link/20260130173704.12575-3-itazur@amazon.com Signed-off-by: Jakub Kicinski --- drivers/ptp/ptp_vmclock.c | 162 ++++++++++++++++++++++++++++++++++----- include/uapi/linux/vmclock-abi.h | 5 ++ 2 files changed, 148 insertions(+), 19 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/ptp/ptp_vmclock.c b/drivers/ptp/ptp_vmclock.c index b3a83b03d9c1..f8b24f9e85cd 100644 --- a/drivers/ptp/ptp_vmclock.c +++ b/drivers/ptp/ptp_vmclock.c @@ -5,6 +5,9 @@ * Copyright © 2024 Amazon.com, Inc. or its affiliates. */ +#include "linux/poll.h" +#include "linux/types.h" +#include "linux/wait.h" #include #include #include @@ -39,6 +42,7 @@ struct vmclock_state { struct resource res; struct vmclock_abi *clk; struct miscdevice miscdev; + wait_queue_head_t disrupt_wait; struct ptp_clock_info ptp_clock_info; struct ptp_clock *ptp_clock; enum clocksource_ids cs_id, sys_cs_id; @@ -357,10 +361,15 @@ static struct ptp_clock *vmclock_ptp_register(struct device *dev, return ptp_clock_register(&st->ptp_clock_info, dev); } +struct vmclock_file_state { + struct vmclock_state *st; + atomic_t seq; +}; + static int vmclock_miscdev_mmap(struct file *fp, struct vm_area_struct *vma) { - struct vmclock_state *st = container_of(fp->private_data, - struct vmclock_state, miscdev); + struct vmclock_file_state *fst = fp->private_data; + struct vmclock_state *st = fst->st; if ((vma->vm_flags & (VM_READ|VM_WRITE)) != VM_READ) return -EROFS; @@ -379,11 +388,11 @@ static int vmclock_miscdev_mmap(struct file *fp, struct vm_area_struct *vma) static ssize_t vmclock_miscdev_read(struct file *fp, char __user *buf, size_t count, loff_t *ppos) { - struct vmclock_state *st = container_of(fp->private_data, - struct vmclock_state, miscdev); ktime_t deadline = ktime_add(ktime_get(), VMCLOCK_MAX_WAIT); + struct vmclock_file_state *fst = fp->private_data; + struct vmclock_state *st = fst->st; + uint32_t seq, old_seq; size_t max_count; - uint32_t seq; if (*ppos >= PAGE_SIZE) return 0; @@ -392,6 +401,7 @@ static ssize_t vmclock_miscdev_read(struct file *fp, char __user *buf, if (count > max_count) count = max_count; + old_seq = atomic_read(&fst->seq); while (1) { seq = le32_to_cpu(st->clk->seq_count) & ~1U; /* Pairs with hypervisor wmb */ @@ -402,8 +412,16 @@ static ssize_t vmclock_miscdev_read(struct file *fp, char __user *buf, /* Pairs with hypervisor wmb */ virt_rmb(); - if (seq == le32_to_cpu(st->clk->seq_count)) - break; + if (seq == le32_to_cpu(st->clk->seq_count)) { + /* + * Either we updated fst->seq to seq (the latest version we observed) + * or someone else did (old_seq == seq), so we can break. + */ + if (atomic_try_cmpxchg(&fst->seq, &old_seq, seq) || + old_seq == seq) { + break; + } + } if (ktime_after(ktime_get(), deadline)) return -ETIMEDOUT; @@ -413,25 +431,62 @@ static ssize_t vmclock_miscdev_read(struct file *fp, char __user *buf, return count; } +static __poll_t vmclock_miscdev_poll(struct file *fp, poll_table *wait) +{ + struct vmclock_file_state *fst = fp->private_data; + struct vmclock_state *st = fst->st; + uint32_t seq; + + /* + * Hypervisor will not send us any notifications, so fail immediately + * to avoid having caller sleeping for ever. + */ + if (!(le64_to_cpu(st->clk->flags) & VMCLOCK_FLAG_NOTIFICATION_PRESENT)) + return POLLHUP; + + poll_wait(fp, &st->disrupt_wait, wait); + + seq = le32_to_cpu(st->clk->seq_count); + if (atomic_read(&fst->seq) != seq) + return POLLIN | POLLRDNORM; + + return 0; +} + +static int vmclock_miscdev_open(struct inode *inode, struct file *fp) +{ + struct vmclock_state *st = container_of(fp->private_data, + struct vmclock_state, miscdev); + struct vmclock_file_state *fst = kzalloc(sizeof(*fst), GFP_KERNEL); + + if (!fst) + return -ENOMEM; + + fst->st = st; + atomic_set(&fst->seq, 0); + + fp->private_data = fst; + + return 0; +} + +static int vmclock_miscdev_release(struct inode *inode, struct file *fp) +{ + kfree(fp->private_data); + return 0; +} + static const struct file_operations vmclock_miscdev_fops = { .owner = THIS_MODULE, + .open = vmclock_miscdev_open, + .release = vmclock_miscdev_release, .mmap = vmclock_miscdev_mmap, .read = vmclock_miscdev_read, + .poll = vmclock_miscdev_poll, }; /* module operations */ -static void vmclock_remove(void *data) -{ - struct vmclock_state *st = data; - - if (st->ptp_clock) - ptp_clock_unregister(st->ptp_clock); - - if (st->miscdev.minor != MISC_DYNAMIC_MINOR) - misc_deregister(&st->miscdev); -} - static acpi_status vmclock_acpi_resources(struct acpi_resource *ares, void *data) { struct vmclock_state *st = data; @@ -459,6 +514,44 @@ static acpi_status vmclock_acpi_resources(struct acpi_resource *ares, void *data return AE_ERROR; } +static void +vmclock_acpi_notification_handler(acpi_handle __always_unused handle, + u32 __always_unused event, void *dev) +{ + struct device *device = dev; + struct vmclock_state *st = device->driver_data; + + wake_up_interruptible(&st->disrupt_wait); +} + +static int vmclock_setup_notification(struct device *dev, struct vmclock_state *st) +{ + struct acpi_device *adev = ACPI_COMPANION(dev); + acpi_status status; + + /* + * This should never happen as this function is only called when + * has_acpi_companion(dev) is true, but the logic is sufficiently + * complex that Coverity can't see the tautology. + */ + if (!adev) + return -ENODEV; + + /* The device does not support notifications. Nothing else to do */ + if (!(le64_to_cpu(st->clk->flags) & VMCLOCK_FLAG_NOTIFICATION_PRESENT)) + return 0; + + status = acpi_install_notify_handler(adev->handle, ACPI_DEVICE_NOTIFY, + vmclock_acpi_notification_handler, + dev); + if (ACPI_FAILURE(status)) { + dev_err(dev, "failed to install notification handler"); + return -ENODEV; + } + + return 0; +} + static int vmclock_probe_acpi(struct device *dev, struct vmclock_state *st) { struct acpi_device *adev = ACPI_COMPANION(dev); @@ -482,6 +575,30 @@ static int vmclock_probe_acpi(struct device *dev, struct vmclock_state *st) return 0; } +static void vmclock_remove(void *data) +{ + struct device *dev = data; + struct vmclock_state *st = dev->driver_data; + + if (!st) { + dev_err(dev, "%s called with NULL driver_data", __func__); + return; + } + + if (has_acpi_companion(dev)) + acpi_remove_notify_handler(ACPI_COMPANION(dev)->handle, + ACPI_DEVICE_NOTIFY, + vmclock_acpi_notification_handler); + + if (st->ptp_clock) + ptp_clock_unregister(st->ptp_clock); + + if (st->miscdev.minor != MISC_DYNAMIC_MINOR) + misc_deregister(&st->miscdev); + + dev->driver_data = NULL; +} + static void vmclock_put_idx(void *data) { struct vmclock_state *st = data; @@ -545,7 +662,14 @@ static int vmclock_probe(struct platform_device *pdev) st->miscdev.minor = MISC_DYNAMIC_MINOR; - ret = devm_add_action_or_reset(&pdev->dev, vmclock_remove, st); + init_waitqueue_head(&st->disrupt_wait); + dev->driver_data = st; + + ret = devm_add_action_or_reset(&pdev->dev, vmclock_remove, dev); + if (ret) + return ret; + + ret = vmclock_setup_notification(dev, st); if (ret) return ret; diff --git a/include/uapi/linux/vmclock-abi.h b/include/uapi/linux/vmclock-abi.h index 937fe00e4f33..d320623b0118 100644 --- a/include/uapi/linux/vmclock-abi.h +++ b/include/uapi/linux/vmclock-abi.h @@ -121,6 +121,11 @@ struct vmclock_abi { * loaded from some save state (restored from a snapshot). */ #define VMCLOCK_FLAG_VM_GEN_COUNTER_PRESENT (1 << 8) + /* + * If the NOTIFICATION_PRESENT flag is set, the hypervisor will send + * a notification every time it updates seq_count to a new even number. + */ +#define VMCLOCK_FLAG_NOTIFICATION_PRESENT (1 << 9) __u8 pad[2]; __u8 clock_status; -- cgit v1.2.3 From ef6a31d035a1000071dc4846aebd02ad081db9e4 Mon Sep 17 00:00:00 2001 From: Mark Harmstone Date: Wed, 7 Jan 2026 14:09:01 +0000 Subject: btrfs: add definitions and constants for remap-tree Add an incompat flag for the new remap-tree feature, and the constants and definitions needed to support it. Reviewed-by: Boris Burkov Signed-off-by: Mark Harmstone Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/accessors.h | 4 ++++ fs/btrfs/locking.c | 1 + fs/btrfs/sysfs.c | 3 +++ fs/btrfs/tree-checker.c | 6 ++---- fs/btrfs/tree-checker.h | 5 +++++ fs/btrfs/volumes.c | 1 + include/uapi/linux/btrfs.h | 1 + include/uapi/linux/btrfs_tree.h | 17 +++++++++++++++++ 8 files changed, 34 insertions(+), 4 deletions(-) (limited to 'include/uapi/linux') diff --git a/fs/btrfs/accessors.h b/fs/btrfs/accessors.h index 78721412951c..09cdd6bfddf5 100644 --- a/fs/btrfs/accessors.h +++ b/fs/btrfs/accessors.h @@ -1010,6 +1010,10 @@ BTRFS_SETGET_STACK_FUNCS(stack_verity_descriptor_encryption, BTRFS_SETGET_STACK_FUNCS(stack_verity_descriptor_size, struct btrfs_verity_descriptor_item, size, 64); +BTRFS_SETGET_FUNCS(remap_address, struct btrfs_remap_item, address, 64); +BTRFS_SETGET_STACK_FUNCS(stack_remap_address, struct btrfs_remap_item, + address, 64); + /* Cast into the data area of the leaf. */ #define btrfs_item_ptr(leaf, slot, type) \ ((type *)(btrfs_item_nr_offset(leaf, 0) + btrfs_item_offset(leaf, slot))) diff --git a/fs/btrfs/locking.c b/fs/btrfs/locking.c index 0035851d72b0..e3df5ca0b552 100644 --- a/fs/btrfs/locking.c +++ b/fs/btrfs/locking.c @@ -73,6 +73,7 @@ static struct btrfs_lockdep_keyset { { .id = BTRFS_FREE_SPACE_TREE_OBJECTID, DEFINE_NAME("free-space") }, { .id = BTRFS_BLOCK_GROUP_TREE_OBJECTID, DEFINE_NAME("block-group") }, { .id = BTRFS_RAID_STRIPE_TREE_OBJECTID, DEFINE_NAME("raid-stripe") }, + { .id = BTRFS_REMAP_TREE_OBJECTID, DEFINE_NAME("remap") }, { .id = 0, DEFINE_NAME("tree") }, }; diff --git a/fs/btrfs/sysfs.c b/fs/btrfs/sysfs.c index ebd6d1d6778b..8834a1dd499c 100644 --- a/fs/btrfs/sysfs.c +++ b/fs/btrfs/sysfs.c @@ -299,6 +299,8 @@ BTRFS_FEAT_ATTR_INCOMPAT(zoned, ZONED); BTRFS_FEAT_ATTR_INCOMPAT(extent_tree_v2, EXTENT_TREE_V2); /* Remove once support for raid stripe tree is feature complete. */ BTRFS_FEAT_ATTR_INCOMPAT(raid_stripe_tree, RAID_STRIPE_TREE); +/* Remove once support for remap tree is feature complete. */ +BTRFS_FEAT_ATTR_INCOMPAT(remap_tree, REMAP_TREE); #endif #ifdef CONFIG_FS_VERITY BTRFS_FEAT_ATTR_COMPAT_RO(verity, VERITY); @@ -331,6 +333,7 @@ static struct attribute *btrfs_supported_feature_attrs[] = { #ifdef CONFIG_BTRFS_EXPERIMENTAL BTRFS_FEAT_ATTR_PTR(extent_tree_v2), BTRFS_FEAT_ATTR_PTR(raid_stripe_tree), + BTRFS_FEAT_ATTR_PTR(remap_tree), #endif #ifdef CONFIG_FS_VERITY BTRFS_FEAT_ATTR_PTR(verity), diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c index c21c21adf61e..aedc208a95b8 100644 --- a/fs/btrfs/tree-checker.c +++ b/fs/btrfs/tree-checker.c @@ -913,12 +913,10 @@ int btrfs_check_chunk_valid(const struct btrfs_fs_info *fs_info, length, btrfs_stripe_nr_to_offset(U32_MAX)); return -EUCLEAN; } - if (unlikely(type & ~(BTRFS_BLOCK_GROUP_TYPE_MASK | - BTRFS_BLOCK_GROUP_PROFILE_MASK))) { + if (unlikely(type & ~BTRFS_BLOCK_GROUP_VALID)) { chunk_err(fs_info, leaf, chunk, logical, "unrecognized chunk type: 0x%llx", - ~(BTRFS_BLOCK_GROUP_TYPE_MASK | - BTRFS_BLOCK_GROUP_PROFILE_MASK) & type); + type & ~BTRFS_BLOCK_GROUP_VALID); return -EUCLEAN; } diff --git a/fs/btrfs/tree-checker.h b/fs/btrfs/tree-checker.h index eb201f4ec3c7..833e2fd989eb 100644 --- a/fs/btrfs/tree-checker.h +++ b/fs/btrfs/tree-checker.h @@ -57,6 +57,11 @@ enum btrfs_tree_block_status { BTRFS_TREE_BLOCK_WRITTEN_NOT_SET, }; + +#define BTRFS_BLOCK_GROUP_VALID (BTRFS_BLOCK_GROUP_TYPE_MASK | \ + BTRFS_BLOCK_GROUP_PROFILE_MASK | \ + BTRFS_BLOCK_GROUP_REMAPPED) + /* * Exported simply for btrfs-progs which wants to have the * btrfs_tree_block_status return codes. diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c index c4be17fcb87a..d2b7352eb7cb 100644 --- a/fs/btrfs/volumes.c +++ b/fs/btrfs/volumes.c @@ -231,6 +231,7 @@ void btrfs_describe_block_groups(u64 bg_flags, char *buf, u32 size_buf) DESCRIBE_FLAG(BTRFS_BLOCK_GROUP_DATA, "data"); DESCRIBE_FLAG(BTRFS_BLOCK_GROUP_SYSTEM, "system"); DESCRIBE_FLAG(BTRFS_BLOCK_GROUP_METADATA, "metadata"); + DESCRIBE_FLAG(BTRFS_BLOCK_GROUP_REMAPPED, "remapped"); DESCRIBE_FLAG(BTRFS_AVAIL_ALLOC_BIT_SINGLE, "single"); for (i = 0; i < BTRFS_NR_RAID_TYPES; i++) diff --git a/include/uapi/linux/btrfs.h b/include/uapi/linux/btrfs.h index e8fd92789423..9165154a274d 100644 --- a/include/uapi/linux/btrfs.h +++ b/include/uapi/linux/btrfs.h @@ -336,6 +336,7 @@ struct btrfs_ioctl_fs_info_args { #define BTRFS_FEATURE_INCOMPAT_EXTENT_TREE_V2 (1ULL << 13) #define BTRFS_FEATURE_INCOMPAT_RAID_STRIPE_TREE (1ULL << 14) #define BTRFS_FEATURE_INCOMPAT_SIMPLE_QUOTA (1ULL << 16) +#define BTRFS_FEATURE_INCOMPAT_REMAP_TREE (1ULL << 17) struct btrfs_ioctl_feature_flags { __u64 compat_flags; diff --git a/include/uapi/linux/btrfs_tree.h b/include/uapi/linux/btrfs_tree.h index fc29d273845d..f011d34cb699 100644 --- a/include/uapi/linux/btrfs_tree.h +++ b/include/uapi/linux/btrfs_tree.h @@ -76,6 +76,9 @@ /* Tracks RAID stripes in block groups. */ #define BTRFS_RAID_STRIPE_TREE_OBJECTID 12ULL +/* Holds details of remapped addresses after relocation. */ +#define BTRFS_REMAP_TREE_OBJECTID 13ULL + /* device stats in the device tree */ #define BTRFS_DEV_STATS_OBJECTID 0ULL @@ -282,6 +285,10 @@ #define BTRFS_RAID_STRIPE_KEY 230 +#define BTRFS_IDENTITY_REMAP_KEY 234 +#define BTRFS_REMAP_KEY 235 +#define BTRFS_REMAP_BACKREF_KEY 236 + /* * Records the overall state of the qgroups. * There's only one instance of this key present, @@ -1161,6 +1168,7 @@ struct btrfs_dev_replace_item { #define BTRFS_BLOCK_GROUP_RAID6 (1ULL << 8) #define BTRFS_BLOCK_GROUP_RAID1C3 (1ULL << 9) #define BTRFS_BLOCK_GROUP_RAID1C4 (1ULL << 10) +#define BTRFS_BLOCK_GROUP_REMAPPED (1ULL << 11) #define BTRFS_BLOCK_GROUP_RESERVED (BTRFS_AVAIL_ALLOC_BIT_SINGLE | \ BTRFS_SPACE_INFO_GLOBAL_RSV) @@ -1323,4 +1331,13 @@ struct btrfs_verity_descriptor_item { __u8 encryption; } __attribute__ ((__packed__)); +/* + * For a range identified by a BTRFS_REMAP_KEY item in the remap tree, gives + * the address that the start of the range will get remapped to. This + * structure is also shared by BTRFS_REMAP_BACKREF_KEY. + */ +struct btrfs_remap_item { + __le64 address; +} __attribute__ ((__packed__)); + #endif /* _BTRFS_CTREE_H_ */ -- cgit v1.2.3 From 0b4d29fa98ca1a49c4498353253f857573871ba0 Mon Sep 17 00:00:00 2001 From: Mark Harmstone Date: Wed, 7 Jan 2026 14:09:02 +0000 Subject: btrfs: add METADATA_REMAP chunk type Add a new METADATA_REMAP chunk type, which is a metadata chunk that holds the remap tree. This is needed for bootstrapping purposes: the remap tree can't itself be remapped, and must be relocated the existing way, by COWing every leaf. The remap tree can't go in the SYSTEM chunk as space there is limited, because a copy of the chunk item gets placed in the superblock. The changes in fs/btrfs/volumes.h are because we're adding a new block group type bit after the profile bits, and so can no longer rely on the const_ilog2 trick. The sizing to 32MB per chunk, matching the SYSTEM chunk, is an estimate here, we can adjust it later if it proves to be too big or too small. This works out to be ~500,000 remap items, which for a 4KB block size covers ~2GB of remapped data in the worst case and ~500TB in the best case. Reviewed-by: Boris Burkov Signed-off-by: Mark Harmstone Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/block-rsv.c | 8 ++++++++ fs/btrfs/block-rsv.h | 1 + fs/btrfs/disk-io.c | 1 + fs/btrfs/fs.h | 2 ++ fs/btrfs/space-info.c | 13 ++++++++++++- fs/btrfs/sysfs.c | 2 ++ fs/btrfs/tree-checker.c | 13 +++++++++++-- fs/btrfs/volumes.c | 3 +++ fs/btrfs/volumes.h | 10 +++++++++- include/uapi/linux/btrfs_tree.h | 4 +++- 10 files changed, 52 insertions(+), 5 deletions(-) (limited to 'include/uapi/linux') diff --git a/fs/btrfs/block-rsv.c b/fs/btrfs/block-rsv.c index 96cf7a162987..e823230c09b7 100644 --- a/fs/btrfs/block-rsv.c +++ b/fs/btrfs/block-rsv.c @@ -419,6 +419,9 @@ void btrfs_init_root_block_rsv(struct btrfs_root *root) case BTRFS_TREE_LOG_OBJECTID: root->block_rsv = &fs_info->treelog_rsv; break; + case BTRFS_REMAP_TREE_OBJECTID: + root->block_rsv = &fs_info->remap_block_rsv; + break; default: root->block_rsv = NULL; break; @@ -432,6 +435,9 @@ void btrfs_init_global_block_rsv(struct btrfs_fs_info *fs_info) space_info = btrfs_find_space_info(fs_info, BTRFS_BLOCK_GROUP_SYSTEM); fs_info->chunk_block_rsv.space_info = space_info; + space_info = btrfs_find_space_info(fs_info, BTRFS_BLOCK_GROUP_METADATA_REMAP); + fs_info->remap_block_rsv.space_info = space_info; + space_info = btrfs_find_space_info(fs_info, BTRFS_BLOCK_GROUP_METADATA); fs_info->global_block_rsv.space_info = space_info; fs_info->trans_block_rsv.space_info = space_info; @@ -458,6 +464,8 @@ void btrfs_release_global_block_rsv(struct btrfs_fs_info *fs_info) WARN_ON(fs_info->trans_block_rsv.reserved > 0); WARN_ON(fs_info->chunk_block_rsv.size > 0); WARN_ON(fs_info->chunk_block_rsv.reserved > 0); + WARN_ON(fs_info->remap_block_rsv.size > 0); + WARN_ON(fs_info->remap_block_rsv.reserved > 0); WARN_ON(fs_info->delayed_block_rsv.size > 0); WARN_ON(fs_info->delayed_block_rsv.reserved > 0); WARN_ON(fs_info->delayed_refs_rsv.reserved > 0); diff --git a/fs/btrfs/block-rsv.h b/fs/btrfs/block-rsv.h index 79ae9d05cd91..8359fb96bc3c 100644 --- a/fs/btrfs/block-rsv.h +++ b/fs/btrfs/block-rsv.h @@ -22,6 +22,7 @@ enum btrfs_rsv_type { BTRFS_BLOCK_RSV_DELALLOC, BTRFS_BLOCK_RSV_TRANS, BTRFS_BLOCK_RSV_CHUNK, + BTRFS_BLOCK_RSV_REMAP, BTRFS_BLOCK_RSV_DELOPS, BTRFS_BLOCK_RSV_DELREFS, BTRFS_BLOCK_RSV_TREELOG, diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c index faa1c2c20ecd..922e69038d81 100644 --- a/fs/btrfs/disk-io.c +++ b/fs/btrfs/disk-io.c @@ -2751,6 +2751,7 @@ void btrfs_init_fs_info(struct btrfs_fs_info *fs_info) BTRFS_BLOCK_RSV_GLOBAL); btrfs_init_block_rsv(&fs_info->trans_block_rsv, BTRFS_BLOCK_RSV_TRANS); btrfs_init_block_rsv(&fs_info->chunk_block_rsv, BTRFS_BLOCK_RSV_CHUNK); + btrfs_init_block_rsv(&fs_info->remap_block_rsv, BTRFS_BLOCK_RSV_REMAP); btrfs_init_block_rsv(&fs_info->treelog_rsv, BTRFS_BLOCK_RSV_TREELOG); btrfs_init_block_rsv(&fs_info->empty_block_rsv, BTRFS_BLOCK_RSV_EMPTY); btrfs_init_block_rsv(&fs_info->delayed_block_rsv, diff --git a/fs/btrfs/fs.h b/fs/btrfs/fs.h index e3e5e52e97a2..195428ecfd75 100644 --- a/fs/btrfs/fs.h +++ b/fs/btrfs/fs.h @@ -509,6 +509,8 @@ struct btrfs_fs_info { struct btrfs_block_rsv trans_block_rsv; /* Block reservation for chunk tree */ struct btrfs_block_rsv chunk_block_rsv; + /* Block reservation for remap tree. */ + struct btrfs_block_rsv remap_block_rsv; /* Block reservation for delayed operations */ struct btrfs_block_rsv delayed_block_rsv; /* Block reservation for delayed refs */ diff --git a/fs/btrfs/space-info.c b/fs/btrfs/space-info.c index 1d76242f5e0d..2c9cf1ab232b 100644 --- a/fs/btrfs/space-info.c +++ b/fs/btrfs/space-info.c @@ -215,7 +215,7 @@ static u64 calc_chunk_size(const struct btrfs_fs_info *fs_info, u64 flags) if (flags & BTRFS_BLOCK_GROUP_DATA) return BTRFS_MAX_DATA_CHUNK_SIZE; - else if (flags & BTRFS_BLOCK_GROUP_SYSTEM) + else if (flags & (BTRFS_BLOCK_GROUP_SYSTEM | BTRFS_BLOCK_GROUP_METADATA_REMAP)) return SZ_32M; /* Handle BTRFS_BLOCK_GROUP_METADATA */ @@ -348,6 +348,8 @@ int btrfs_init_space_info(struct btrfs_fs_info *fs_info) if (mixed) { flags = BTRFS_BLOCK_GROUP_METADATA | BTRFS_BLOCK_GROUP_DATA; ret = create_space_info(fs_info, flags); + if (ret) + goto out; } else { flags = BTRFS_BLOCK_GROUP_METADATA; ret = create_space_info(fs_info, flags); @@ -356,7 +358,15 @@ int btrfs_init_space_info(struct btrfs_fs_info *fs_info) flags = BTRFS_BLOCK_GROUP_DATA; ret = create_space_info(fs_info, flags); + if (ret) + goto out; + } + + if (features & BTRFS_FEATURE_INCOMPAT_REMAP_TREE) { + flags = BTRFS_BLOCK_GROUP_METADATA_REMAP; + ret = create_space_info(fs_info, flags); } + out: return ret; } @@ -611,6 +621,7 @@ static void dump_global_block_rsv(struct btrfs_fs_info *fs_info) DUMP_BLOCK_RSV(fs_info, global_block_rsv); DUMP_BLOCK_RSV(fs_info, trans_block_rsv); DUMP_BLOCK_RSV(fs_info, chunk_block_rsv); + DUMP_BLOCK_RSV(fs_info, remap_block_rsv); DUMP_BLOCK_RSV(fs_info, delayed_block_rsv); DUMP_BLOCK_RSV(fs_info, delayed_refs_rsv); } diff --git a/fs/btrfs/sysfs.c b/fs/btrfs/sysfs.c index 8834a1dd499c..27bfb7b55ec4 100644 --- a/fs/btrfs/sysfs.c +++ b/fs/btrfs/sysfs.c @@ -1929,6 +1929,8 @@ static const char *alloc_name(struct btrfs_space_info *space_info) case BTRFS_BLOCK_GROUP_SYSTEM: ASSERT(space_info->subgroup_id == BTRFS_SUB_GROUP_PRIMARY); return "system"; + case BTRFS_BLOCK_GROUP_METADATA_REMAP: + return "metadata-remap"; default: WARN_ON(1); return "invalid-combination"; diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c index aedc208a95b8..a6c158cd8fcd 100644 --- a/fs/btrfs/tree-checker.c +++ b/fs/btrfs/tree-checker.c @@ -748,17 +748,26 @@ static int check_block_group_item(struct extent_buffer *leaf, return -EUCLEAN; } + if (unlikely(flags & BTRFS_BLOCK_GROUP_METADATA_REMAP && + !btrfs_fs_incompat(fs_info, REMAP_TREE))) { + block_group_err(leaf, slot, +"invalid flags, have 0x%llx (METADATA_REMAP flag set) but no remap-tree incompat flag", + flags); + return -EUCLEAN; + } + type = flags & BTRFS_BLOCK_GROUP_TYPE_MASK; if (unlikely(type != BTRFS_BLOCK_GROUP_DATA && type != BTRFS_BLOCK_GROUP_METADATA && type != BTRFS_BLOCK_GROUP_SYSTEM && + type != BTRFS_BLOCK_GROUP_METADATA_REMAP && type != (BTRFS_BLOCK_GROUP_METADATA | BTRFS_BLOCK_GROUP_DATA))) { block_group_err(leaf, slot, -"invalid type, have 0x%llx (%lu bits set) expect either 0x%llx, 0x%llx, 0x%llx or 0x%llx", +"invalid type, have 0x%llx (%lu bits set) expect either 0x%llx, 0x%llx, 0x%llx, 0x%llx or 0x%llx", type, hweight64(type), BTRFS_BLOCK_GROUP_DATA, BTRFS_BLOCK_GROUP_METADATA, - BTRFS_BLOCK_GROUP_SYSTEM, + BTRFS_BLOCK_GROUP_SYSTEM, BTRFS_BLOCK_GROUP_METADATA_REMAP, BTRFS_BLOCK_GROUP_METADATA | BTRFS_BLOCK_GROUP_DATA); return -EUCLEAN; } diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c index d2b7352eb7cb..eda6505f3ee5 100644 --- a/fs/btrfs/volumes.c +++ b/fs/btrfs/volumes.c @@ -231,6 +231,9 @@ void btrfs_describe_block_groups(u64 bg_flags, char *buf, u32 size_buf) DESCRIBE_FLAG(BTRFS_BLOCK_GROUP_DATA, "data"); DESCRIBE_FLAG(BTRFS_BLOCK_GROUP_SYSTEM, "system"); DESCRIBE_FLAG(BTRFS_BLOCK_GROUP_METADATA, "metadata"); + /* Block groups containing the remap tree. */ + DESCRIBE_FLAG(BTRFS_BLOCK_GROUP_METADATA_REMAP, "metadata-remap"); + /* Block group that has been remapped. */ DESCRIBE_FLAG(BTRFS_BLOCK_GROUP_REMAPPED, "remapped"); DESCRIBE_FLAG(BTRFS_AVAIL_ALLOC_BIT_SINGLE, "single"); diff --git a/fs/btrfs/volumes.h b/fs/btrfs/volumes.h index 59347a4bb185..e4b3cb50f94a 100644 --- a/fs/btrfs/volumes.h +++ b/fs/btrfs/volumes.h @@ -58,7 +58,6 @@ static_assert(ilog2(BTRFS_STRIPE_LEN) == BTRFS_STRIPE_LEN_SHIFT); */ static_assert(const_ffs(BTRFS_BLOCK_GROUP_RAID0) < const_ffs(BTRFS_BLOCK_GROUP_PROFILE_MASK & ~BTRFS_BLOCK_GROUP_RAID0)); -static_assert(ilog2(BTRFS_BLOCK_GROUP_RAID0) > ilog2(BTRFS_BLOCK_GROUP_TYPE_MASK)); /* ilog2() can handle both constants and variables */ #define BTRFS_BG_FLAG_TO_INDEX(profile) \ @@ -80,6 +79,15 @@ enum btrfs_raid_types { BTRFS_NR_RAID_TYPES }; +static_assert(BTRFS_RAID_RAID0 == 1); +static_assert(BTRFS_RAID_RAID1 == 2); +static_assert(BTRFS_RAID_DUP == 3); +static_assert(BTRFS_RAID_RAID10 == 4); +static_assert(BTRFS_RAID_RAID5 == 5); +static_assert(BTRFS_RAID_RAID6 == 6); +static_assert(BTRFS_RAID_RAID1C3 == 7); +static_assert(BTRFS_RAID_RAID1C4 == 8); + /* * Use sequence counter to get consistent device stat data on * 32-bit processors. diff --git a/include/uapi/linux/btrfs_tree.h b/include/uapi/linux/btrfs_tree.h index f011d34cb699..76578426671c 100644 --- a/include/uapi/linux/btrfs_tree.h +++ b/include/uapi/linux/btrfs_tree.h @@ -1169,12 +1169,14 @@ struct btrfs_dev_replace_item { #define BTRFS_BLOCK_GROUP_RAID1C3 (1ULL << 9) #define BTRFS_BLOCK_GROUP_RAID1C4 (1ULL << 10) #define BTRFS_BLOCK_GROUP_REMAPPED (1ULL << 11) +#define BTRFS_BLOCK_GROUP_METADATA_REMAP (1ULL << 12) #define BTRFS_BLOCK_GROUP_RESERVED (BTRFS_AVAIL_ALLOC_BIT_SINGLE | \ BTRFS_SPACE_INFO_GLOBAL_RSV) #define BTRFS_BLOCK_GROUP_TYPE_MASK (BTRFS_BLOCK_GROUP_DATA | \ BTRFS_BLOCK_GROUP_SYSTEM | \ - BTRFS_BLOCK_GROUP_METADATA) + BTRFS_BLOCK_GROUP_METADATA | \ + BTRFS_BLOCK_GROUP_METADATA_REMAP) #define BTRFS_BLOCK_GROUP_PROFILE_MASK (BTRFS_BLOCK_GROUP_RAID0 | \ BTRFS_BLOCK_GROUP_RAID1 | \ -- cgit v1.2.3 From 7977011460cffc6f5a0cd830584c832c4aa07076 Mon Sep 17 00:00:00 2001 From: Mark Harmstone Date: Wed, 7 Jan 2026 14:09:07 +0000 Subject: btrfs: add extended version of struct block_group_item Add a struct btrfs_block_group_item_v2, which is used in the block group tree if the remap-tree incompat flag is set. This adds two new fields to the block group item: `remap_bytes` and `identity_remap_count`. `remap_bytes` records the amount of data that's physically within this block group, but nominally in another, remapped block group. This is necessary because this data will need to be moved first if this block group is itself relocated. If `remap_bytes` > 0, this is an indicator to the relocation thread that it will need to search the remap-tree for backrefs. A block group must also have `remap_bytes` == 0 before it can be dropped. `identity_remap_count` records how many identity remap items are located in the remap tree for this block group. When relocation is begun for this block group, this is set to the number of holes in the free-space tree for this range. As identity remaps are converted into actual remaps by the relocation process, this number is decreased. Once it reaches 0, either because of relocation or because extents have been deleted, the block group has been fully remapped and its chunk's device extents are removed. Reviewed-by: Boris Burkov Signed-off-by: Mark Harmstone Signed-off-by: David Sterba --- fs/btrfs/accessors.h | 20 +++++++++ fs/btrfs/block-group.c | 92 ++++++++++++++++++++++++++++++----------- fs/btrfs/block-group.h | 10 ++++- fs/btrfs/discard.c | 2 +- fs/btrfs/tree-checker.c | 10 ++++- include/uapi/linux/btrfs_tree.h | 8 ++++ 6 files changed, 114 insertions(+), 28 deletions(-) (limited to 'include/uapi/linux') diff --git a/fs/btrfs/accessors.h b/fs/btrfs/accessors.h index 09cdd6bfddf5..9797f9e8d4e5 100644 --- a/fs/btrfs/accessors.h +++ b/fs/btrfs/accessors.h @@ -240,6 +240,26 @@ BTRFS_SETGET_FUNCS(block_group_flags, struct btrfs_block_group_item, flags, 64); BTRFS_SETGET_STACK_FUNCS(stack_block_group_flags, struct btrfs_block_group_item, flags, 64); +/* struct btrfs_block_group_item_v2 */ +BTRFS_SETGET_STACK_FUNCS(stack_block_group_v2_used, struct btrfs_block_group_item_v2, + used, 64); +BTRFS_SETGET_FUNCS(block_group_v2_used, struct btrfs_block_group_item_v2, used, 64); +BTRFS_SETGET_STACK_FUNCS(stack_block_group_v2_chunk_objectid, + struct btrfs_block_group_item_v2, chunk_objectid, 64); +BTRFS_SETGET_FUNCS(block_group_v2_chunk_objectid, + struct btrfs_block_group_item_v2, chunk_objectid, 64); +BTRFS_SETGET_STACK_FUNCS(stack_block_group_v2_flags, + struct btrfs_block_group_item_v2, flags, 64); +BTRFS_SETGET_FUNCS(block_group_v2_flags, struct btrfs_block_group_item_v2, flags, 64); +BTRFS_SETGET_STACK_FUNCS(stack_block_group_v2_remap_bytes, + struct btrfs_block_group_item_v2, remap_bytes, 64); +BTRFS_SETGET_FUNCS(block_group_v2_remap_bytes, struct btrfs_block_group_item_v2, + remap_bytes, 64); +BTRFS_SETGET_STACK_FUNCS(stack_block_group_v2_identity_remap_count, + struct btrfs_block_group_item_v2, identity_remap_count, 32); +BTRFS_SETGET_FUNCS(block_group_v2_identity_remap_count, struct btrfs_block_group_item_v2, + identity_remap_count, 32); + /* struct btrfs_free_space_info */ BTRFS_SETGET_FUNCS(free_space_extent_count, struct btrfs_free_space_info, extent_count, 32); diff --git a/fs/btrfs/block-group.c b/fs/btrfs/block-group.c index 5709acc84297..a1ab513fa8ea 100644 --- a/fs/btrfs/block-group.c +++ b/fs/btrfs/block-group.c @@ -2371,7 +2371,7 @@ static int check_chunk_block_group_mappings(struct btrfs_fs_info *fs_info) } static int read_one_block_group(struct btrfs_fs_info *info, - struct btrfs_block_group_item *bgi, + struct btrfs_block_group_item_v2 *bgi, const struct btrfs_key *key, int need_clear) { @@ -2386,11 +2386,15 @@ static int read_one_block_group(struct btrfs_fs_info *info, return -ENOMEM; cache->length = key->offset; - cache->used = btrfs_stack_block_group_used(bgi); + cache->used = btrfs_stack_block_group_v2_used(bgi); cache->last_used = cache->used; - cache->flags = btrfs_stack_block_group_flags(bgi); - cache->global_root_id = btrfs_stack_block_group_chunk_objectid(bgi); + cache->flags = btrfs_stack_block_group_v2_flags(bgi); + cache->global_root_id = btrfs_stack_block_group_v2_chunk_objectid(bgi); cache->space_info = btrfs_find_space_info(info, cache->flags); + cache->remap_bytes = btrfs_stack_block_group_v2_remap_bytes(bgi); + cache->last_remap_bytes = cache->remap_bytes; + cache->identity_remap_count = btrfs_stack_block_group_v2_identity_remap_count(bgi); + cache->last_identity_remap_count = cache->identity_remap_count; btrfs_set_free_space_tree_thresholds(cache); @@ -2455,7 +2459,7 @@ static int read_one_block_group(struct btrfs_fs_info *info, } else if (cache->length == cache->used) { cache->cached = BTRFS_CACHE_FINISHED; btrfs_free_excluded_extents(cache); - } else if (cache->used == 0) { + } else if (cache->used == 0 && cache->remap_bytes == 0) { cache->cached = BTRFS_CACHE_FINISHED; ret = btrfs_add_new_free_space(cache, cache->start, cache->start + cache->length, NULL); @@ -2475,7 +2479,7 @@ static int read_one_block_group(struct btrfs_fs_info *info, set_avail_alloc_bits(info, cache->flags); if (btrfs_chunk_writeable(info, cache->start)) { - if (cache->used == 0) { + if (cache->used == 0 && cache->remap_bytes == 0) { ASSERT(list_empty(&cache->bg_list)); if (btrfs_test_opt(info, DISCARD_ASYNC)) btrfs_discard_queue_work(&info->discard_ctl, cache); @@ -2579,9 +2583,10 @@ int btrfs_read_block_groups(struct btrfs_fs_info *info) need_clear = 1; while (1) { - struct btrfs_block_group_item bgi; + struct btrfs_block_group_item_v2 bgi; struct extent_buffer *leaf; int slot; + size_t size; ret = find_first_block_group(info, path, &key); if (ret > 0) @@ -2592,8 +2597,16 @@ int btrfs_read_block_groups(struct btrfs_fs_info *info) leaf = path->nodes[0]; slot = path->slots[0]; + if (btrfs_fs_incompat(info, REMAP_TREE)) { + size = sizeof(struct btrfs_block_group_item_v2); + } else { + size = sizeof(struct btrfs_block_group_item); + btrfs_set_stack_block_group_v2_remap_bytes(&bgi, 0); + btrfs_set_stack_block_group_v2_identity_remap_count(&bgi, 0); + } + read_extent_buffer(leaf, &bgi, btrfs_item_ptr_offset(leaf, slot), - sizeof(bgi)); + size); btrfs_item_key_to_cpu(leaf, &key, slot); btrfs_release_path(path); @@ -2663,25 +2676,34 @@ static int insert_block_group_item(struct btrfs_trans_handle *trans, struct btrfs_block_group *block_group) { struct btrfs_fs_info *fs_info = trans->fs_info; - struct btrfs_block_group_item bgi; + struct btrfs_block_group_item_v2 bgi; struct btrfs_root *root = btrfs_block_group_root(fs_info); struct btrfs_key key; u64 old_last_used; + size_t size; int ret; spin_lock(&block_group->lock); - btrfs_set_stack_block_group_used(&bgi, block_group->used); - btrfs_set_stack_block_group_chunk_objectid(&bgi, - block_group->global_root_id); - btrfs_set_stack_block_group_flags(&bgi, block_group->flags); + btrfs_set_stack_block_group_v2_used(&bgi, block_group->used); + btrfs_set_stack_block_group_v2_chunk_objectid(&bgi, block_group->global_root_id); + btrfs_set_stack_block_group_v2_flags(&bgi, block_group->flags); + btrfs_set_stack_block_group_v2_remap_bytes(&bgi, block_group->remap_bytes); + btrfs_set_stack_block_group_v2_identity_remap_count(&bgi, block_group->identity_remap_count); old_last_used = block_group->last_used; block_group->last_used = block_group->used; + block_group->last_remap_bytes = block_group->remap_bytes; + block_group->last_identity_remap_count = block_group->identity_remap_count; key.objectid = block_group->start; key.type = BTRFS_BLOCK_GROUP_ITEM_KEY; key.offset = block_group->length; spin_unlock(&block_group->lock); - ret = btrfs_insert_item(trans, root, &key, &bgi, sizeof(bgi)); + if (btrfs_fs_incompat(fs_info, REMAP_TREE)) + size = sizeof(struct btrfs_block_group_item_v2); + else + size = sizeof(struct btrfs_block_group_item); + + ret = btrfs_insert_item(trans, root, &key, &bgi, size); if (ret < 0) { spin_lock(&block_group->lock); block_group->last_used = old_last_used; @@ -3132,10 +3154,12 @@ static int update_block_group_item(struct btrfs_trans_handle *trans, struct btrfs_root *root = btrfs_block_group_root(fs_info); unsigned long bi; struct extent_buffer *leaf; - struct btrfs_block_group_item bgi; + struct btrfs_block_group_item_v2 bgi; struct btrfs_key key; - u64 old_last_used; - u64 used; + u64 old_last_used, old_last_remap_bytes; + u32 old_last_identity_remap_count; + u64 used, remap_bytes; + u32 identity_remap_count; /* * Block group items update can be triggered out of commit transaction @@ -3145,13 +3169,21 @@ static int update_block_group_item(struct btrfs_trans_handle *trans, */ spin_lock(&cache->lock); old_last_used = cache->last_used; + old_last_remap_bytes = cache->last_remap_bytes; + old_last_identity_remap_count = cache->last_identity_remap_count; used = cache->used; - /* No change in used bytes, can safely skip it. */ - if (cache->last_used == used) { + remap_bytes = cache->remap_bytes; + identity_remap_count = cache->identity_remap_count; + /* No change in values, can safely skip it. */ + if (cache->last_used == used && + cache->last_remap_bytes == remap_bytes && + cache->last_identity_remap_count == identity_remap_count) { spin_unlock(&cache->lock); return 0; } cache->last_used = used; + cache->last_remap_bytes = remap_bytes; + cache->last_identity_remap_count = identity_remap_count; spin_unlock(&cache->lock); key.objectid = cache->start; @@ -3167,11 +3199,21 @@ static int update_block_group_item(struct btrfs_trans_handle *trans, leaf = path->nodes[0]; bi = btrfs_item_ptr_offset(leaf, path->slots[0]); - btrfs_set_stack_block_group_used(&bgi, used); - btrfs_set_stack_block_group_chunk_objectid(&bgi, - cache->global_root_id); - btrfs_set_stack_block_group_flags(&bgi, cache->flags); - write_extent_buffer(leaf, &bgi, bi, sizeof(bgi)); + btrfs_set_stack_block_group_v2_used(&bgi, used); + btrfs_set_stack_block_group_v2_chunk_objectid(&bgi, cache->global_root_id); + btrfs_set_stack_block_group_v2_flags(&bgi, cache->flags); + + if (btrfs_fs_incompat(fs_info, REMAP_TREE)) { + btrfs_set_stack_block_group_v2_remap_bytes(&bgi, cache->remap_bytes); + btrfs_set_stack_block_group_v2_identity_remap_count(&bgi, + cache->identity_remap_count); + write_extent_buffer(leaf, &bgi, bi, + sizeof(struct btrfs_block_group_item_v2)); + } else { + write_extent_buffer(leaf, &bgi, bi, + sizeof(struct btrfs_block_group_item)); + } + fail: btrfs_release_path(path); /* @@ -3186,6 +3228,8 @@ fail: if (ret < 0 && ret != -ENOENT) { spin_lock(&cache->lock); cache->last_used = old_last_used; + cache->last_remap_bytes = old_last_remap_bytes; + cache->last_identity_remap_count = old_last_identity_remap_count; spin_unlock(&cache->lock); } return ret; diff --git a/fs/btrfs/block-group.h b/fs/btrfs/block-group.h index b0fb85a36d97..ecabb1a9fc0e 100644 --- a/fs/btrfs/block-group.h +++ b/fs/btrfs/block-group.h @@ -129,6 +129,8 @@ struct btrfs_block_group { u64 flags; u64 cache_generation; u64 global_root_id; + u64 remap_bytes; + u32 identity_remap_count; /* * The last committed used bytes of this block group, if the above @used @@ -136,6 +138,11 @@ struct btrfs_block_group { * group item of this block group. */ u64 last_used; + /* The last committed remap_bytes value of this block group. */ + u64 last_remap_bytes; + /* The last commited identity_remap_count value of this block group. */ + u32 last_identity_remap_count; + /* * If the free space extent count exceeds this number, convert the block * group to bitmaps. @@ -282,7 +289,8 @@ static inline bool btrfs_is_block_group_used(const struct btrfs_block_group *bg) { lockdep_assert_held(&bg->lock); - return (bg->used > 0 || bg->reserved > 0 || bg->pinned > 0); + return (bg->used > 0 || bg->reserved > 0 || bg->pinned > 0 || + bg->remap_bytes > 0); } static inline bool btrfs_is_block_group_data_only(const struct btrfs_block_group *block_group) diff --git a/fs/btrfs/discard.c b/fs/btrfs/discard.c index 89fe85778115..ee5f5b2788e1 100644 --- a/fs/btrfs/discard.c +++ b/fs/btrfs/discard.c @@ -373,7 +373,7 @@ void btrfs_discard_queue_work(struct btrfs_discard_ctl *discard_ctl, if (!block_group || !btrfs_test_opt(block_group->fs_info, DISCARD_ASYNC)) return; - if (block_group->used == 0) + if (block_group->used == 0 && block_group->remap_bytes == 0) add_to_discard_unused_list(discard_ctl, block_group); else add_to_discard_list(discard_ctl, block_group); diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c index ead2e1e2a0bb..452394b34d01 100644 --- a/fs/btrfs/tree-checker.c +++ b/fs/btrfs/tree-checker.c @@ -688,6 +688,7 @@ static int check_block_group_item(struct extent_buffer *leaf, u64 chunk_objectid; u64 flags; u64 type; + size_t exp_size; /* * Here we don't really care about alignment since extent allocator can @@ -699,10 +700,15 @@ static int check_block_group_item(struct extent_buffer *leaf, return -EUCLEAN; } - if (unlikely(item_size != sizeof(bgi))) { + if (btrfs_fs_incompat(fs_info, REMAP_TREE)) + exp_size = sizeof(struct btrfs_block_group_item_v2); + else + exp_size = sizeof(struct btrfs_block_group_item); + + if (unlikely(item_size != exp_size)) { block_group_err(leaf, slot, "invalid item size, have %u expect %zu", - item_size, sizeof(bgi)); + item_size, exp_size); return -EUCLEAN; } diff --git a/include/uapi/linux/btrfs_tree.h b/include/uapi/linux/btrfs_tree.h index 76578426671c..86820a9644e8 100644 --- a/include/uapi/linux/btrfs_tree.h +++ b/include/uapi/linux/btrfs_tree.h @@ -1229,6 +1229,14 @@ struct btrfs_block_group_item { __le64 flags; } __attribute__ ((__packed__)); +struct btrfs_block_group_item_v2 { + __le64 used; + __le64 chunk_objectid; + __le64 flags; + __le64 remap_bytes; + __le32 identity_remap_count; +} __attribute__ ((__packed__)); + struct btrfs_free_space_info { __le32 extent_count; __le32 flags; -- cgit v1.2.3 From 8620da16fb6be1fd9906374fa1c763a10c6918df Mon Sep 17 00:00:00 2001 From: Mark Harmstone Date: Wed, 7 Jan 2026 14:09:08 +0000 Subject: btrfs: allow mounting filesystems with remap-tree incompat flag If we encounter a filesystem with the remap-tree incompat flag set, validate its compatibility with the other flags, and load the remap tree using the values that have been added to the superblock. The remap-tree feature depends on the free-space-tree, but no-holes and block-group-tree have been made dependencies to reduce the testing matrix. Similarly I'm not aware of any reason why mixed-bg and zoned would be incompatible with remap-tree, but this is blocked for the time being until it can be fully tested. Reviewed-by: Boris Burkov Signed-off-by: Mark Harmstone Signed-off-by: David Sterba --- fs/btrfs/Kconfig | 2 + fs/btrfs/accessors.h | 6 +++ fs/btrfs/disk-io.c | 105 +++++++++++++++++++++++++++++++++++----- fs/btrfs/extent-tree.c | 2 + fs/btrfs/fs.h | 4 +- fs/btrfs/transaction.c | 7 +++ include/uapi/linux/btrfs_tree.h | 5 +- 7 files changed, 116 insertions(+), 15 deletions(-) (limited to 'include/uapi/linux') diff --git a/fs/btrfs/Kconfig b/fs/btrfs/Kconfig index 423122786a93..ede184b6eda1 100644 --- a/fs/btrfs/Kconfig +++ b/fs/btrfs/Kconfig @@ -116,4 +116,6 @@ config BTRFS_EXPERIMENTAL - asynchronous checksum generation for data writes + - remap-tree - logical address remapping tree + If unsure, say N. diff --git a/fs/btrfs/accessors.h b/fs/btrfs/accessors.h index 9797f9e8d4e5..8938357fcb40 100644 --- a/fs/btrfs/accessors.h +++ b/fs/btrfs/accessors.h @@ -883,6 +883,12 @@ BTRFS_SETGET_STACK_FUNCS(super_uuid_tree_generation, struct btrfs_super_block, uuid_tree_generation, 64); BTRFS_SETGET_STACK_FUNCS(super_nr_global_roots, struct btrfs_super_block, nr_global_roots, 64); +BTRFS_SETGET_STACK_FUNCS(super_remap_root, struct btrfs_super_block, + remap_root, 64); +BTRFS_SETGET_STACK_FUNCS(super_remap_root_generation, struct btrfs_super_block, + remap_root_generation, 64); +BTRFS_SETGET_STACK_FUNCS(super_remap_root_level, struct btrfs_super_block, + remap_root_level, 8); /* struct btrfs_file_extent_item */ BTRFS_SETGET_STACK_FUNCS(stack_file_extent_type, struct btrfs_file_extent_item, diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c index cd46b9d85880..c69734c74c26 100644 --- a/fs/btrfs/disk-io.c +++ b/fs/btrfs/disk-io.c @@ -1136,6 +1136,8 @@ static struct btrfs_root *btrfs_get_global_root(struct btrfs_fs_info *fs_info, return btrfs_grab_root(btrfs_global_root(fs_info, &key)); case BTRFS_RAID_STRIPE_TREE_OBJECTID: return btrfs_grab_root(fs_info->stripe_root); + case BTRFS_REMAP_TREE_OBJECTID: + return btrfs_grab_root(fs_info->remap_root); default: return NULL; } @@ -1226,6 +1228,7 @@ void btrfs_free_fs_info(struct btrfs_fs_info *fs_info) btrfs_put_root(fs_info->data_reloc_root); btrfs_put_root(fs_info->block_group_root); btrfs_put_root(fs_info->stripe_root); + btrfs_put_root(fs_info->remap_root); btrfs_check_leaked_roots(fs_info); btrfs_extent_buffer_leak_debug_check(fs_info); kfree(fs_info->super_copy); @@ -1778,6 +1781,7 @@ static void free_root_pointers(struct btrfs_fs_info *info, bool free_chunk_root) free_root_extent_buffers(info->data_reloc_root); free_root_extent_buffers(info->block_group_root); free_root_extent_buffers(info->stripe_root); + free_root_extent_buffers(info->remap_root); if (free_chunk_root) free_root_extent_buffers(info->chunk_root); } @@ -2191,21 +2195,44 @@ static int btrfs_read_roots(struct btrfs_fs_info *fs_info) if (ret) goto out; - /* - * This tree can share blocks with some other fs tree during relocation - * and we need a proper setup by btrfs_get_fs_root - */ - root = btrfs_get_fs_root(tree_root->fs_info, - BTRFS_DATA_RELOC_TREE_OBJECTID, true); - if (IS_ERR(root)) { - if (!btrfs_test_opt(fs_info, IGNOREBADROOTS)) { - location.objectid = BTRFS_DATA_RELOC_TREE_OBJECTID; - ret = PTR_ERR(root); - goto out; + if (btrfs_fs_incompat(fs_info, REMAP_TREE)) { + /* The remap_root has already been loaded in load_important_roots(). */ + root = fs_info->remap_root; + + set_bit(BTRFS_ROOT_TRACK_DIRTY, &root->state); + + root->root_key.objectid = BTRFS_REMAP_TREE_OBJECTID; + root->root_key.type = BTRFS_ROOT_ITEM_KEY; + root->root_key.offset = 0; + + /* Check that data reloc tree doesn't also exist. */ + location.objectid = BTRFS_DATA_RELOC_TREE_OBJECTID; + root = btrfs_read_tree_root(fs_info->tree_root, &location); + if (!IS_ERR(root)) { + btrfs_err(fs_info, "data reloc tree exists when remap-tree enabled"); + btrfs_put_root(root); + return -EIO; + } else if (PTR_ERR(root) != -ENOENT) { + btrfs_warn(fs_info, "error %ld when checking for data reloc tree", + PTR_ERR(root)); } } else { - set_bit(BTRFS_ROOT_TRACK_DIRTY, &root->state); - fs_info->data_reloc_root = root; + /* + * This tree can share blocks with some other fs tree during + * relocation and we need a proper setup by btrfs_get_fs_root(). + */ + root = btrfs_get_fs_root(tree_root->fs_info, + BTRFS_DATA_RELOC_TREE_OBJECTID, true); + if (IS_ERR(root)) { + if (!btrfs_test_opt(fs_info, IGNOREBADROOTS)) { + location.objectid = BTRFS_DATA_RELOC_TREE_OBJECTID; + ret = PTR_ERR(root); + goto out; + } + } else { + set_bit(BTRFS_ROOT_TRACK_DIRTY, &root->state); + fs_info->data_reloc_root = root; + } } location.objectid = BTRFS_QUOTA_TREE_OBJECTID; @@ -2445,6 +2472,35 @@ int btrfs_validate_super(const struct btrfs_fs_info *fs_info, ret = -EINVAL; } + if (btrfs_fs_incompat(fs_info, REMAP_TREE)) { + /* + * Reduce test matrix for remap tree by requiring block-group-tree + * and no-holes. Free-space-tree is a hard requirement. + */ + if (!btrfs_fs_compat_ro(fs_info, FREE_SPACE_TREE_VALID) || + !btrfs_fs_incompat(fs_info, NO_HOLES) || + !btrfs_fs_compat_ro(fs_info, BLOCK_GROUP_TREE)) { + btrfs_err(fs_info, +"remap-tree feature requires free-space-tree, no-holes, and block-group-tree"); + ret = -EINVAL; + } + + if (btrfs_fs_incompat(fs_info, MIXED_GROUPS)) { + btrfs_err(fs_info, "remap-tree not supported with mixed-bg"); + ret = -EINVAL; + } + + if (btrfs_fs_incompat(fs_info, ZONED)) { + btrfs_err(fs_info, "remap-tree not supported with zoned devices"); + ret = -EINVAL; + } + + if (sectorsize > PAGE_SIZE) { + btrfs_err(fs_info, "remap-tree not supported when block size > page size"); + ret = -EINVAL; + } + } + /* * Hint to catch really bogus numbers, bitflips or so, more exact checks are * done later @@ -2603,6 +2659,18 @@ static int load_important_roots(struct btrfs_fs_info *fs_info) btrfs_warn(fs_info, "couldn't read tree root"); return ret; } + + if (btrfs_fs_incompat(fs_info, REMAP_TREE)) { + bytenr = btrfs_super_remap_root(sb); + gen = btrfs_super_remap_root_generation(sb); + level = btrfs_super_remap_root_level(sb); + ret = load_super_root(fs_info->remap_root, bytenr, gen, level); + if (ret) { + btrfs_warn(fs_info, "couldn't read remap root"); + return ret; + } + } + return 0; } @@ -3231,6 +3299,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device struct btrfs_fs_info *fs_info = btrfs_sb(sb); struct btrfs_root *tree_root; struct btrfs_root *chunk_root; + struct btrfs_root *remap_root; int ret; int level; @@ -3365,6 +3434,16 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device if (ret < 0) goto fail_alloc; + if (btrfs_super_incompat_flags(disk_super) & BTRFS_FEATURE_INCOMPAT_REMAP_TREE) { + remap_root = btrfs_alloc_root(fs_info, BTRFS_REMAP_TREE_OBJECTID, + GFP_KERNEL); + fs_info->remap_root = remap_root; + if (!remap_root) { + ret = -ENOMEM; + goto fail_alloc; + } + } + /* * At this point our mount options are validated, if we set ->max_inline * to something non-standard make sure we truncate it to sectorsize. diff --git a/fs/btrfs/extent-tree.c b/fs/btrfs/extent-tree.c index 48a453fa3063..ce4bda1f37ad 100644 --- a/fs/btrfs/extent-tree.c +++ b/fs/btrfs/extent-tree.c @@ -2593,6 +2593,8 @@ static u64 get_alloc_profile_by_root(struct btrfs_root *root, int data) flags = BTRFS_BLOCK_GROUP_DATA; else if (root == fs_info->chunk_root) flags = BTRFS_BLOCK_GROUP_SYSTEM; + else if (root == fs_info->remap_root) + flags = BTRFS_BLOCK_GROUP_METADATA_REMAP; else flags = BTRFS_BLOCK_GROUP_METADATA; diff --git a/fs/btrfs/fs.h b/fs/btrfs/fs.h index 195428ecfd75..13b0aa0b9da9 100644 --- a/fs/btrfs/fs.h +++ b/fs/btrfs/fs.h @@ -315,7 +315,8 @@ enum { #define BTRFS_FEATURE_INCOMPAT_SUPP \ (BTRFS_FEATURE_INCOMPAT_SUPP_STABLE | \ BTRFS_FEATURE_INCOMPAT_RAID_STRIPE_TREE | \ - BTRFS_FEATURE_INCOMPAT_EXTENT_TREE_V2) + BTRFS_FEATURE_INCOMPAT_EXTENT_TREE_V2 | \ + BTRFS_FEATURE_INCOMPAT_REMAP_TREE) #else @@ -475,6 +476,7 @@ struct btrfs_fs_info { struct btrfs_root *data_reloc_root; struct btrfs_root *block_group_root; struct btrfs_root *stripe_root; + struct btrfs_root *remap_root; /* The log root tree is a directory of all the other log roots */ struct btrfs_root *log_root_tree; diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c index e2f993b1783f..f4cc9e1a1b93 100644 --- a/fs/btrfs/transaction.c +++ b/fs/btrfs/transaction.c @@ -1967,6 +1967,13 @@ static void update_super_roots(struct btrfs_fs_info *fs_info) super->cache_generation = 0; if (test_bit(BTRFS_FS_UPDATE_UUID_TREE_GEN, &fs_info->flags)) super->uuid_tree_generation = root_item->generation; + + if (btrfs_fs_incompat(fs_info, REMAP_TREE)) { + root_item = &fs_info->remap_root->root_item; + super->remap_root = root_item->bytenr; + super->remap_root_generation = root_item->generation; + super->remap_root_level = root_item->level; + } } int btrfs_transaction_blocked(struct btrfs_fs_info *info) diff --git a/include/uapi/linux/btrfs_tree.h b/include/uapi/linux/btrfs_tree.h index 86820a9644e8..f7843e6bb978 100644 --- a/include/uapi/linux/btrfs_tree.h +++ b/include/uapi/linux/btrfs_tree.h @@ -721,9 +721,12 @@ struct btrfs_super_block { __u8 metadata_uuid[BTRFS_FSID_SIZE]; __u64 nr_global_roots; + __le64 remap_root; + __le64 remap_root_generation; + __u8 remap_root_level; /* Future expansion */ - __le64 reserved[27]; + __u8 reserved[199]; __u8 sys_chunk_array[BTRFS_SYSTEM_CHUNK_ARRAY_SIZE]; struct btrfs_root_backup super_roots[BTRFS_NUM_BACKUP_ROOTS]; -- cgit v1.2.3 From 4fa4ac5e584841c0f9b01c2f7dd0c2e3caa8bca0 Mon Sep 17 00:00:00 2001 From: Chia-Yu Chang Date: Sat, 31 Jan 2026 23:25:13 +0100 Subject: tcp: accecn: add tcpi_ecn_mode and tcpi_option2 in tcp_info Add 2-bit tcpi_ecn_mode feild within tcp_info to indicate which ECN mode is negotiated: ECN_MODE_DISABLED, ECN_MODE_RFC3168, ECN_MODE_ACCECN, or ECN_MODE_PENDING. This is done by utilizing available bits from tcpi_accecn_opt_seen (reduced from 16 bits to 2 bits) and tcpi_accecn_fail_mode (reduced from 16 bits to 4 bits). Also, an extra 24-bit tcpi_options2 field is identified to represent newer options and connection features, as all 8 bits of tcpi_options field have been used. Signed-off-by: Chia-Yu Chang Co-developed-by: Neal Cardwell Signed-off-by: Neal Cardwell Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260131222515.8485-14-chia-yu.chang@nokia-bell-labs.com Signed-off-by: Paolo Abeni --- include/net/tcp_ecn.h | 11 ----------- include/uapi/linux/tcp.h | 26 +++++++++++++++++++++++--- net/ipv4/tcp.c | 8 ++++++++ 3 files changed, 31 insertions(+), 14 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/net/tcp_ecn.h b/include/net/tcp_ecn.h index e01653bbf181..e9a933641636 100644 --- a/include/net/tcp_ecn.h +++ b/include/net/tcp_ecn.h @@ -67,12 +67,6 @@ static inline void tcp_ecn_withdraw_cwr(struct tcp_sock *tp) tp->ecn_flags &= ~TCP_ECN_QUEUE_CWR; } -/* tp->accecn_fail_mode */ -#define TCP_ACCECN_ACE_FAIL_SEND BIT(0) -#define TCP_ACCECN_ACE_FAIL_RECV BIT(1) -#define TCP_ACCECN_OPT_FAIL_SEND BIT(2) -#define TCP_ACCECN_OPT_FAIL_RECV BIT(3) - static inline bool tcp_accecn_ace_fail_send(const struct tcp_sock *tp) { return tp->accecn_fail_mode & TCP_ACCECN_ACE_FAIL_SEND; @@ -98,11 +92,6 @@ static inline void tcp_accecn_fail_mode_set(struct tcp_sock *tp, u8 mode) tp->accecn_fail_mode |= mode; } -#define TCP_ACCECN_OPT_NOT_SEEN 0x0 -#define TCP_ACCECN_OPT_EMPTY_SEEN 0x1 -#define TCP_ACCECN_OPT_COUNTER_SEEN 0x2 -#define TCP_ACCECN_OPT_FAIL_SEEN 0x3 - static inline u8 tcp_accecn_ace(const struct tcphdr *th) { return (th->ae << 2) | (th->cwr << 1) | th->ece; diff --git a/include/uapi/linux/tcp.h b/include/uapi/linux/tcp.h index dce3113787a7..03772dd4d399 100644 --- a/include/uapi/linux/tcp.h +++ b/include/uapi/linux/tcp.h @@ -226,6 +226,24 @@ enum tcp_ca_state { #define TCPF_CA_Loss (1<rto_stamp) info->tcpi_total_rto_time += tcp_clock_ms() - tp->rto_stamp; + if (tcp_ecn_disabled(tp)) + info->tcpi_ecn_mode = TCPI_ECN_MODE_DISABLED; + else if (tcp_ecn_mode_rfc3168(tp)) + info->tcpi_ecn_mode = TCPI_ECN_MODE_RFC3168; + else if (tcp_ecn_mode_accecn(tp)) + info->tcpi_ecn_mode = TCPI_ECN_MODE_ACCECN; + else if (tcp_ecn_mode_pending(tp)) + info->tcpi_ecn_mode = TCPI_ECN_MODE_PENDING; info->tcpi_accecn_fail_mode = tp->accecn_fail_mode; info->tcpi_accecn_opt_seen = tp->saw_accecn_opt; info->tcpi_received_ce = tp->received_ce; -- cgit v1.2.3 From 0ee4ddc1647b8b3b9e7a94d798a1774a764428c1 Mon Sep 17 00:00:00 2001 From: Claudio Imbrenda Date: Wed, 4 Feb 2026 16:02:57 +0100 Subject: KVM: s390: Storage key manipulation IOCTL Add a new IOCTL to allow userspace to manipulate storage keys directly. This will make it easier to write selftests related to storage keys. Acked-by: Heiko Carstens Signed-off-by: Claudio Imbrenda --- Documentation/virt/kvm/api.rst | 42 ++++++++++++++++++++++++++++++ arch/s390/kvm/kvm-s390.c | 58 ++++++++++++++++++++++++++++++++++++++++++ include/uapi/linux/kvm.h | 11 ++++++++ 3 files changed, 111 insertions(+) (limited to 'include/uapi/linux') diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst index 01a3abef8abb..72e04dedb068 100644 --- a/Documentation/virt/kvm/api.rst +++ b/Documentation/virt/kvm/api.rst @@ -6517,6 +6517,40 @@ the capability to be present. `flags` must currently be zero. +4.144 KVM_S390_KEYOP +-------------------- + +:Capability: KVM_CAP_S390_KEYOP +:Architectures: s390 +:Type: vm ioctl +:Parameters: struct kvm_s390_keyop (in/out) +:Returns: 0 in case of success, < 0 on error + +The specified key operation is performed on the given guest address. The +previous storage key (or the relevant part thereof) will be returned in +`key`. + +:: + + struct kvm_s390_keyop { + __u64 guest_addr; + __u8 key; + __u8 operation; + }; + +Currently supported values for ``operation``: + +KVM_S390_KEYOP_ISKE + Returns the storage key for the guest address ``guest_addr`` in ``key``. + +KVM_S390_KEYOP_RRBE + Resets the reference bit for the guest address ``guest_addr``, returning the + R and C bits of the old storage key in ``key``; the remaining fields of + the storage key will be set to 0. + +KVM_S390_KEYOP_SSKE + Sets the storage key for the guest address ``guest_addr`` to the key + specified in ``key``, returning the previous value in ``key``. .. _kvm_run: @@ -9287,6 +9321,14 @@ The presence of this capability indicates that KVM_RUN will update the KVM_RUN_X86_GUEST_MODE bit in kvm_run.flags to indicate whether the vCPU was executing nested guest code when it exited. +8.46 KVM_CAP_S390_KEYOP +----------------------- + +:Architectures: s390 + +The presence of this capability indicates that the KVM_S390_KEYOP ioctl is +available. + KVM exits with the register state of either the L1 or L2 guest depending on which executed at the time of an exit. Userspace must take care to differentiate between these cases. diff --git a/arch/s390/kvm/kvm-s390.c b/arch/s390/kvm/kvm-s390.c index ac7b5f56f0b5..9f24252775dd 100644 --- a/arch/s390/kvm/kvm-s390.c +++ b/arch/s390/kvm/kvm-s390.c @@ -554,6 +554,37 @@ static void __kvm_s390_exit(void) debug_unregister(kvm_s390_dbf_uv); } +static int kvm_s390_keyop(struct kvm_s390_mmu_cache *mc, struct kvm *kvm, int op, + unsigned long addr, union skey skey) +{ + union asce asce = kvm->arch.gmap->asce; + gfn_t gfn = gpa_to_gfn(addr); + int r; + + guard(read_lock)(&kvm->mmu_lock); + + switch (op) { + case KVM_S390_KEYOP_SSKE: + r = dat_cond_set_storage_key(mc, asce, gfn, skey, &skey, 0, 0, 0); + if (r >= 0) + return skey.skey; + break; + case KVM_S390_KEYOP_ISKE: + r = dat_get_storage_key(asce, gfn, &skey); + if (!r) + return skey.skey; + break; + case KVM_S390_KEYOP_RRBE: + r = dat_reset_reference_bit(asce, gfn); + if (r > 0) + return r << 1; + break; + default: + return -EINVAL; + } + return r; +} + /* Section: device related */ long kvm_arch_dev_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg) @@ -598,6 +629,7 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext) case KVM_CAP_S390_DIAG318: case KVM_CAP_IRQFD_RESAMPLE: case KVM_CAP_S390_USER_OPEREXEC: + case KVM_CAP_S390_KEYOP: r = 1; break; case KVM_CAP_SET_GUEST_DEBUG2: @@ -2931,6 +2963,32 @@ int kvm_arch_vm_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg) r = -EFAULT; break; } + case KVM_S390_KEYOP: { + struct kvm_s390_mmu_cache *mc; + struct kvm_s390_keyop kop; + union skey skey; + + if (copy_from_user(&kop, argp, sizeof(kop))) { + r = -EFAULT; + break; + } + skey.skey = kop.key; + + mc = kvm_s390_new_mmu_cache(); + if (!mc) + return -ENOMEM; + + r = kvm_s390_keyop(mc, kvm, kop.operation, kop.guest_addr, skey); + kvm_s390_free_mmu_cache(mc); + if (r < 0) + break; + + kop.key = r; + r = 0; + if (copy_to_user(argp, &kop, sizeof(kop))) + r = -EFAULT; + break; + } case KVM_S390_ZPCI_OP: { struct kvm_s390_zpci_op args; diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h index dddb781b0507..ab3d3d96e75f 100644 --- a/include/uapi/linux/kvm.h +++ b/include/uapi/linux/kvm.h @@ -974,6 +974,7 @@ struct kvm_enable_cap { #define KVM_CAP_GUEST_MEMFD_FLAGS 244 #define KVM_CAP_ARM_SEA_TO_USER 245 #define KVM_CAP_S390_USER_OPEREXEC 246 +#define KVM_CAP_S390_KEYOP 247 struct kvm_irq_routing_irqchip { __u32 irqchip; @@ -1219,6 +1220,15 @@ struct kvm_vfio_spapr_tce { __s32 tablefd; }; +#define KVM_S390_KEYOP_ISKE 0x01 +#define KVM_S390_KEYOP_RRBE 0x02 +#define KVM_S390_KEYOP_SSKE 0x03 +struct kvm_s390_keyop { + __u64 guest_addr; + __u8 key; + __u8 operation; +}; + /* * KVM_CREATE_VCPU receives as a parameter the vcpu slot, and returns * a vcpu fd. @@ -1238,6 +1248,7 @@ struct kvm_vfio_spapr_tce { #define KVM_S390_UCAS_MAP _IOW(KVMIO, 0x50, struct kvm_s390_ucas_mapping) #define KVM_S390_UCAS_UNMAP _IOW(KVMIO, 0x51, struct kvm_s390_ucas_mapping) #define KVM_S390_VCPU_FAULT _IOW(KVMIO, 0x52, unsigned long) +#define KVM_S390_KEYOP _IOWR(KVMIO, 0x53, struct kvm_s390_keyop) /* Device model IOC */ #define KVM_CREATE_IRQCHIP _IO(KVMIO, 0x60) -- cgit v1.2.3 From f7ab71f178d56447e5efb55b65436feb68662f8f Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Fri, 6 Feb 2026 10:17:30 +0100 Subject: KVM: s390: Add explicit padding to struct kvm_s390_keyop The newly added structure causes a warning about implied padding: ./usr/include/linux/kvm.h:1247:1: error: padding struct size to alignment boundary with 6 bytes [-Werror=padded] The padding can lead to leaking kernel data and ABI incompatibilies when used on x86. Neither of these is a problem in this specific patch, but it's best to avoid it and use explicit padding fields in general. Fixes: 0ee4ddc1647b ("KVM: s390: Storage key manipulation IOCTL") Signed-off-by: Arnd Bergmann Reviewed-by: Claudio Imbrenda Signed-off-by: Claudio Imbrenda --- include/uapi/linux/kvm.h | 1 + 1 file changed, 1 insertion(+) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h index ab3d3d96e75f..d4250ab662fe 100644 --- a/include/uapi/linux/kvm.h +++ b/include/uapi/linux/kvm.h @@ -1227,6 +1227,7 @@ struct kvm_s390_keyop { __u64 guest_addr; __u8 key; __u8 operation; + __u8 pad[6]; }; /* -- cgit v1.2.3 From ed82f35b926b2e505c14b7006473614b8f58b4f4 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Thu, 8 Jan 2026 10:18:31 -0700 Subject: io_uring: allow registration of per-task restrictions Currently io_uring supports restricting operations on a per-ring basis. To use those, the ring must be setup in a disabled state by setting IORING_SETUP_R_DISABLED. Then restrictions can be set for the ring, and the ring can then be enabled. This commit adds support for IORING_REGISTER_RESTRICTIONS with ring_fd == -1, like the other "blind" register opcodes which work on the task rather than a specific ring. This allows registration of the same kind of restrictions as can been done on a specific ring, but with the task itself. Once done, any ring created will inherit these restrictions. If a restriction filter is registered with a task, then it's inherited on fork for its children. Children may only further restrict operations, not extend them. Inheriting restrictions include both the classic IORING_REGISTER_RESTRICTIONS based restrictions, as well as the BPF filters that have been registered with the task via IORING_REGISTER_BPF_FILTER. Signed-off-by: Jens Axboe --- include/linux/io_uring_types.h | 2 + include/uapi/linux/io_uring.h | 7 ++++ io_uring/bpf_filter.c | 86 +++++++++++++++++++++++++++++++++++++++++- io_uring/bpf_filter.h | 6 +++ io_uring/io_uring.c | 33 ++++++++++++++++ io_uring/io_uring.h | 1 + io_uring/register.c | 80 +++++++++++++++++++++++++++++++++++++++ io_uring/tctx.c | 17 +++++++++ 8 files changed, 231 insertions(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/include/linux/io_uring_types.h b/include/linux/io_uring_types.h index 7617df247238..510d801b9a55 100644 --- a/include/linux/io_uring_types.h +++ b/include/linux/io_uring_types.h @@ -231,6 +231,8 @@ struct io_restriction { DECLARE_BITMAP(register_op, IORING_REGISTER_LAST); DECLARE_BITMAP(sqe_op, IORING_OP_LAST); struct io_bpf_filters *bpf_filters; + /* ->bpf_filters needs COW on modification */ + bool bpf_filters_cow; u8 sqe_flags_allowed; u8 sqe_flags_required; /* IORING_OP_* restrictions exist */ diff --git a/include/uapi/linux/io_uring.h b/include/uapi/linux/io_uring.h index 94669b77fee8..aeeffcf27fee 100644 --- a/include/uapi/linux/io_uring.h +++ b/include/uapi/linux/io_uring.h @@ -808,6 +808,13 @@ struct io_uring_restriction { __u32 resv2[3]; }; +struct io_uring_task_restriction { + __u16 flags; + __u16 nr_res; + __u32 resv[3]; + __DECLARE_FLEX_ARRAY(struct io_uring_restriction, restrictions); +}; + struct io_uring_clock_register { __u32 clockid; __u32 __resv[3]; diff --git a/io_uring/bpf_filter.c b/io_uring/bpf_filter.c index b94944ab8442..3816883a45ed 100644 --- a/io_uring/bpf_filter.c +++ b/io_uring/bpf_filter.c @@ -249,13 +249,77 @@ static int io_uring_check_cbpf_filter(struct sock_filter *filter, return 0; } +void io_bpf_filter_clone(struct io_restriction *dst, struct io_restriction *src) +{ + if (!src->bpf_filters) + return; + + rcu_read_lock(); + /* + * If the src filter is going away, just ignore it. + */ + if (refcount_inc_not_zero(&src->bpf_filters->refs)) { + dst->bpf_filters = src->bpf_filters; + dst->bpf_filters_cow = true; + } + rcu_read_unlock(); +} + +/* + * Allocate a new struct io_bpf_filters. Used when a filter is cloned and + * modifications need to be made. + */ +static struct io_bpf_filters *io_bpf_filter_cow(struct io_restriction *src) +{ + struct io_bpf_filters *filters; + struct io_bpf_filter *srcf; + int i; + + filters = io_new_bpf_filters(); + if (IS_ERR(filters)) + return filters; + + /* + * Iterate filters from src and assign in destination. Grabbing + * a reference is enough, we don't need to duplicate the memory. + * This is safe because filters are only ever appended to the + * front of the list, hence the only memory ever touched inside + * a filter is the refcount. + */ + rcu_read_lock(); + for (i = 0; i < IORING_OP_LAST; i++) { + srcf = rcu_dereference(src->bpf_filters->filters[i]); + if (!srcf) { + continue; + } else if (srcf == &dummy_filter) { + rcu_assign_pointer(filters->filters[i], &dummy_filter); + continue; + } + + /* + * Getting a ref on the first node is enough, putting the + * filter and iterating nodes to free will stop on the first + * one that doesn't hit zero when dropping. + */ + if (!refcount_inc_not_zero(&srcf->refs)) + goto err; + rcu_assign_pointer(filters->filters[i], srcf); + } + rcu_read_unlock(); + return filters; +err: + rcu_read_unlock(); + __io_put_bpf_filters(filters); + return ERR_PTR(-EBUSY); +} + #define IO_URING_BPF_FILTER_FLAGS IO_URING_BPF_FILTER_DENY_REST int io_register_bpf_filter(struct io_restriction *res, struct io_uring_bpf __user *arg) { + struct io_bpf_filters *filters, *old_filters = NULL; struct io_bpf_filter *filter, *old_filter; - struct io_bpf_filters *filters; struct io_uring_bpf reg; struct bpf_prog *prog; struct sock_fprog fprog; @@ -297,6 +361,17 @@ int io_register_bpf_filter(struct io_restriction *res, ret = PTR_ERR(filters); goto err_prog; } + } else if (res->bpf_filters_cow) { + filters = io_bpf_filter_cow(res); + if (IS_ERR(filters)) { + ret = PTR_ERR(filters); + goto err_prog; + } + /* + * Stash old filters, we'll put them once we know we'll + * succeed. Until then, res->bpf_filters is left untouched. + */ + old_filters = res->bpf_filters; } filter = kzalloc(sizeof(*filter), GFP_KERNEL_ACCOUNT); @@ -306,6 +381,15 @@ int io_register_bpf_filter(struct io_restriction *res, } refcount_set(&filter->refs, 1); filter->prog = prog; + + /* + * Success - install the new filter set now. If we did COW, put + * the old filters as we're replacing them. + */ + if (old_filters) { + __io_put_bpf_filters(old_filters); + res->bpf_filters_cow = false; + } res->bpf_filters = filters; /* diff --git a/io_uring/bpf_filter.h b/io_uring/bpf_filter.h index 9f3cdb92eb16..66a776cf25b4 100644 --- a/io_uring/bpf_filter.h +++ b/io_uring/bpf_filter.h @@ -13,6 +13,8 @@ int io_register_bpf_filter(struct io_restriction *res, void io_put_bpf_filters(struct io_restriction *res); +void io_bpf_filter_clone(struct io_restriction *dst, struct io_restriction *src); + static inline int io_uring_run_bpf_filters(struct io_bpf_filter __rcu **filters, struct io_kiocb *req) { @@ -37,6 +39,10 @@ static inline int io_uring_run_bpf_filters(struct io_bpf_filter __rcu **filters, static inline void io_put_bpf_filters(struct io_restriction *res) { } +static inline void io_bpf_filter_clone(struct io_restriction *dst, + struct io_restriction *src) +{ +} #endif /* CONFIG_IO_URING_BPF */ #endif diff --git a/io_uring/io_uring.c b/io_uring/io_uring.c index 049454278563..e43c5283b23a 100644 --- a/io_uring/io_uring.c +++ b/io_uring/io_uring.c @@ -2880,6 +2880,32 @@ int io_prepare_config(struct io_ctx_config *config) return 0; } +void io_restriction_clone(struct io_restriction *dst, struct io_restriction *src) +{ + memcpy(&dst->register_op, &src->register_op, sizeof(dst->register_op)); + memcpy(&dst->sqe_op, &src->sqe_op, sizeof(dst->sqe_op)); + dst->sqe_flags_allowed = src->sqe_flags_allowed; + dst->sqe_flags_required = src->sqe_flags_required; + dst->op_registered = src->op_registered; + dst->reg_registered = src->reg_registered; + + io_bpf_filter_clone(dst, src); +} + +static void io_ctx_restriction_clone(struct io_ring_ctx *ctx, + struct io_restriction *src) +{ + struct io_restriction *dst = &ctx->restrictions; + + io_restriction_clone(dst, src); + if (dst->bpf_filters) + WRITE_ONCE(ctx->bpf_filters, dst->bpf_filters->filters); + if (dst->op_registered) + ctx->op_restricted = 1; + if (dst->reg_registered) + ctx->reg_restricted = 1; +} + static __cold int io_uring_create(struct io_ctx_config *config) { struct io_uring_params *p = &config->p; @@ -2940,6 +2966,13 @@ static __cold int io_uring_create(struct io_ctx_config *config) else ctx->notify_method = TWA_SIGNAL; + /* + * If the current task has restrictions enabled, then copy them to + * our newly created ring and mark it as registered. + */ + if (current->io_uring_restrict) + io_ctx_restriction_clone(ctx, current->io_uring_restrict); + /* * This is just grabbed for accounting purposes. When a process exits, * the mm is exited and dropped before the files, hence we need to hang diff --git a/io_uring/io_uring.h b/io_uring/io_uring.h index 29b8f90fdabf..a08d78c716f8 100644 --- a/io_uring/io_uring.h +++ b/io_uring/io_uring.h @@ -197,6 +197,7 @@ void io_task_refs_refill(struct io_uring_task *tctx); bool __io_alloc_req_refill(struct io_ring_ctx *ctx); void io_activate_pollwq(struct io_ring_ctx *ctx); +void io_restriction_clone(struct io_restriction *dst, struct io_restriction *src); static inline void io_lockdep_assert_cq_locked(struct io_ring_ctx *ctx) { diff --git a/io_uring/register.c b/io_uring/register.c index 40de9b8924b9..af4815bc11d6 100644 --- a/io_uring/register.c +++ b/io_uring/register.c @@ -190,6 +190,82 @@ static __cold int io_register_restrictions(struct io_ring_ctx *ctx, return 0; } +static int io_register_restrictions_task(void __user *arg, unsigned int nr_args) +{ + struct io_uring_task_restriction __user *ures = arg; + struct io_uring_task_restriction tres; + struct io_restriction *res; + int ret; + + /* Disallow if task already has registered restrictions */ + if (current->io_uring_restrict) + return -EPERM; + /* + * Similar to seccomp, disallow setting a filter if task_no_new_privs + * is true and we're not CAP_SYS_ADMIN. + */ + if (!task_no_new_privs(current) && + !ns_capable_noaudit(current_user_ns(), CAP_SYS_ADMIN)) + return -EACCES; + if (nr_args != 1) + return -EINVAL; + + if (copy_from_user(&tres, arg, sizeof(tres))) + return -EFAULT; + + if (tres.flags) + return -EINVAL; + if (!mem_is_zero(tres.resv, sizeof(tres.resv))) + return -EINVAL; + + res = kzalloc(sizeof(*res), GFP_KERNEL_ACCOUNT); + if (!res) + return -ENOMEM; + + ret = io_parse_restrictions(ures->restrictions, tres.nr_res, res); + if (ret < 0) { + kfree(res); + return ret; + } + current->io_uring_restrict = res; + return 0; +} + +static int io_register_bpf_filter_task(void __user *arg, unsigned int nr_args) +{ + struct io_restriction *res; + int ret; + + /* + * Similar to seccomp, disallow setting a filter if task_no_new_privs + * is true and we're not CAP_SYS_ADMIN. + */ + if (!task_no_new_privs(current) && + !ns_capable_noaudit(current_user_ns(), CAP_SYS_ADMIN)) + return -EACCES; + + if (nr_args != 1) + return -EINVAL; + + /* If no task restrictions exist, setup a new set */ + res = current->io_uring_restrict; + if (!res) { + res = kzalloc(sizeof(*res), GFP_KERNEL_ACCOUNT); + if (!res) + return -ENOMEM; + } + + ret = io_register_bpf_filter(res, arg); + if (ret) { + if (res != current->io_uring_restrict) + kfree(res); + return ret; + } + if (!current->io_uring_restrict) + current->io_uring_restrict = res; + return 0; +} + static int io_register_enable_rings(struct io_ring_ctx *ctx) { if (!(ctx->flags & IORING_SETUP_R_DISABLED)) @@ -912,6 +988,10 @@ static int io_uring_register_blind(unsigned int opcode, void __user *arg, return io_uring_register_send_msg_ring(arg, nr_args); case IORING_REGISTER_QUERY: return io_query(arg, nr_args); + case IORING_REGISTER_RESTRICTIONS: + return io_register_restrictions_task(arg, nr_args); + case IORING_REGISTER_BPF_FILTER: + return io_register_bpf_filter_task(arg, nr_args); } return -EINVAL; } diff --git a/io_uring/tctx.c b/io_uring/tctx.c index d4f7698805e4..e3da31fdf16f 100644 --- a/io_uring/tctx.c +++ b/io_uring/tctx.c @@ -11,6 +11,7 @@ #include "io_uring.h" #include "tctx.h" +#include "bpf_filter.h" static struct io_wq *io_init_wq_offload(struct io_ring_ctx *ctx, struct task_struct *task) @@ -66,6 +67,11 @@ void __io_uring_free(struct task_struct *tsk) kfree(tctx); tsk->io_uring = NULL; } + if (tsk->io_uring_restrict) { + io_put_bpf_filters(tsk->io_uring_restrict); + kfree(tsk->io_uring_restrict); + tsk->io_uring_restrict = NULL; + } } __cold int io_uring_alloc_task_context(struct task_struct *task, @@ -356,5 +362,16 @@ int io_ringfd_unregister(struct io_ring_ctx *ctx, void __user *__arg, int __io_uring_fork(struct task_struct *tsk) { + struct io_restriction *res, *src = tsk->io_uring_restrict; + + /* Don't leave it dangling on error */ + tsk->io_uring_restrict = NULL; + + res = kzalloc(sizeof(*res), GFP_KERNEL_ACCOUNT); + if (!res) + return -ENOMEM; + + tsk->io_uring_restrict = res; + io_restriction_clone(res, src); return 0; } -- cgit v1.2.3 From 42fc7e6543f6d17d2cf9ed3e5021f103a3d11182 Mon Sep 17 00:00:00 2001 From: Günther Noack Date: Thu, 27 Nov 2025 12:51:34 +0100 Subject: landlock: Multithreading support for landlock_restrict_self() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce the LANDLOCK_RESTRICT_SELF_TSYNC flag. With this flag, a given Landlock ruleset is applied to all threads of the calling process, instead of only the current one. Without this flag, multithreaded userspace programs currently resort to using the nptl(7)/libpsx hack for multithreaded policy enforcement, which is also used by libcap and for setuid(2). Using this userspace-based scheme, the threads of a process enforce the same Landlock policy, but the resulting Landlock domains are still separate. The domains being separate causes multiple problems: * When using Landlock's "scoped" access rights, the domain identity is used to determine whether an operation is permitted. As a result, when using LANLDOCK_SCOPE_SIGNAL, signaling between sibling threads stops working. This is a problem for programming languages and frameworks which are inherently multithreaded (e.g. Go). * In audit logging, the domains of separate threads in a process will get logged with different domain IDs, even when they are based on the same ruleset FD, which might confuse users. Cc: Andrew G. Morgan Cc: John Johansen Cc: Paul Moore Suggested-by: Jann Horn Signed-off-by: Günther Noack Link: https://lore.kernel.org/r/20251127115136.3064948-2-gnoack@google.com [mic: Fix restrict_self_flags test, clean up Makefile, allign comments, reduce local variable scope, add missing includes] Closes: https://github.com/landlock-lsm/linux/issues/2 Signed-off-by: Mickaël Salaün --- include/uapi/linux/landlock.h | 13 + security/landlock/Makefile | 11 +- security/landlock/cred.h | 12 + security/landlock/limits.h | 2 +- security/landlock/syscalls.c | 61 +-- security/landlock/tsync.c | 561 +++++++++++++++++++++++++++ security/landlock/tsync.h | 16 + tools/testing/selftests/landlock/base_test.c | 4 +- 8 files changed, 650 insertions(+), 30 deletions(-) create mode 100644 security/landlock/tsync.c create mode 100644 security/landlock/tsync.h (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h index 75fd7f5e6cc3..d5081ab4e5ef 100644 --- a/include/uapi/linux/landlock.h +++ b/include/uapi/linux/landlock.h @@ -117,11 +117,24 @@ struct landlock_ruleset_attr { * future nested domains, not the one being created. It can also be used * with a @ruleset_fd value of -1 to mute subdomain logs without creating a * domain. + * + * The following flag supports policy enforcement in multithreaded processes: + * + * %LANDLOCK_RESTRICT_SELF_TSYNC + * Applies the new Landlock configuration atomically to all threads of the + * current process, including the Landlock domain and logging + * configuration. This overrides the Landlock configuration of sibling + * threads, irrespective of previously established Landlock domains and + * logging configurations on these threads. + * + * If the calling thread is running with no_new_privs, this operation + * enables no_new_privs on the sibling threads as well. */ /* clang-format off */ #define LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF (1U << 0) #define LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON (1U << 1) #define LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF (1U << 2) +#define LANDLOCK_RESTRICT_SELF_TSYNC (1U << 3) /* clang-format on */ /** diff --git a/security/landlock/Makefile b/security/landlock/Makefile index 3160c2bdac1d..ffa7646d99f3 100644 --- a/security/landlock/Makefile +++ b/security/landlock/Makefile @@ -1,7 +1,14 @@ obj-$(CONFIG_SECURITY_LANDLOCK) := landlock.o -landlock-y := setup.o syscalls.o object.o ruleset.o \ - cred.o task.o fs.o +landlock-y := \ + setup.o \ + syscalls.o \ + object.o \ + ruleset.o \ + cred.o \ + task.o \ + fs.o \ + tsync.o landlock-$(CONFIG_INET) += net.o diff --git a/security/landlock/cred.h b/security/landlock/cred.h index c82fe63ec598..c10a06727eb1 100644 --- a/security/landlock/cred.h +++ b/security/landlock/cred.h @@ -26,6 +26,8 @@ * This structure is packed to minimize the size of struct * landlock_file_security. However, it is always aligned in the LSM cred blob, * see lsm_set_blob_size(). + * + * When updating this, also update landlock_cred_copy() if needed. */ struct landlock_cred_security { /** @@ -65,6 +67,16 @@ landlock_cred(const struct cred *cred) return cred->security + landlock_blob_sizes.lbs_cred; } +static inline void landlock_cred_copy(struct landlock_cred_security *dst, + const struct landlock_cred_security *src) +{ + landlock_put_ruleset(dst->domain); + + *dst = *src; + + landlock_get_ruleset(src->domain); +} + static inline struct landlock_ruleset *landlock_get_current_domain(void) { return landlock_cred(current_cred())->domain; diff --git a/security/landlock/limits.h b/security/landlock/limits.h index 65b5ff051674..eb584f47288d 100644 --- a/security/landlock/limits.h +++ b/security/landlock/limits.h @@ -31,7 +31,7 @@ #define LANDLOCK_MASK_SCOPE ((LANDLOCK_LAST_SCOPE << 1) - 1) #define LANDLOCK_NUM_SCOPE __const_hweight64(LANDLOCK_MASK_SCOPE) -#define LANDLOCK_LAST_RESTRICT_SELF LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF +#define LANDLOCK_LAST_RESTRICT_SELF LANDLOCK_RESTRICT_SELF_TSYNC #define LANDLOCK_MASK_RESTRICT_SELF ((LANDLOCK_LAST_RESTRICT_SELF << 1) - 1) /* clang-format on */ diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c index 0116e9f93ffe..3e4e99deb7f9 100644 --- a/security/landlock/syscalls.c +++ b/security/landlock/syscalls.c @@ -36,6 +36,7 @@ #include "net.h" #include "ruleset.h" #include "setup.h" +#include "tsync.h" static bool is_initialized(void) { @@ -161,7 +162,7 @@ static const struct file_operations ruleset_fops = { * Documentation/userspace-api/landlock.rst should be updated to reflect the * UAPI change. */ -const int landlock_abi_version = 7; +const int landlock_abi_version = 8; /** * sys_landlock_create_ruleset - Create a new ruleset @@ -454,9 +455,10 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd, * - %LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF * - %LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON * - %LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF + * - %LANDLOCK_RESTRICT_SELF_TSYNC * - * This system call enables to enforce a Landlock ruleset on the current - * thread. Enforcing a ruleset requires that the task has %CAP_SYS_ADMIN in its + * This system call enforces a Landlock ruleset on the current thread. + * Enforcing a ruleset requires that the task has %CAP_SYS_ADMIN in its * namespace or is running with no_new_privs. This avoids scenarios where * unprivileged tasks can affect the behavior of privileged children. * @@ -478,8 +480,7 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd, SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32, flags) { - struct landlock_ruleset *new_dom, - *ruleset __free(landlock_put_ruleset) = NULL; + struct landlock_ruleset *ruleset __free(landlock_put_ruleset) = NULL; struct cred *new_cred; struct landlock_cred_security *new_llcred; bool __maybe_unused log_same_exec, log_new_exec, log_subdomains, @@ -538,33 +539,43 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32, * We could optimize this case by not calling commit_creds() if this flag * was already set, but it is not worth the complexity. */ - if (!ruleset) - return commit_creds(new_cred); - - /* - * There is no possible race condition while copying and manipulating - * the current credentials because they are dedicated per thread. - */ - new_dom = landlock_merge_ruleset(new_llcred->domain, ruleset); - if (IS_ERR(new_dom)) { - abort_creds(new_cred); - return PTR_ERR(new_dom); - } + if (ruleset) { + /* + * There is no possible race condition while copying and + * manipulating the current credentials because they are + * dedicated per thread. + */ + struct landlock_ruleset *const new_dom = + landlock_merge_ruleset(new_llcred->domain, ruleset); + if (IS_ERR(new_dom)) { + abort_creds(new_cred); + return PTR_ERR(new_dom); + } #ifdef CONFIG_AUDIT - new_dom->hierarchy->log_same_exec = log_same_exec; - new_dom->hierarchy->log_new_exec = log_new_exec; - if ((!log_same_exec && !log_new_exec) || !prev_log_subdomains) - new_dom->hierarchy->log_status = LANDLOCK_LOG_DISABLED; + new_dom->hierarchy->log_same_exec = log_same_exec; + new_dom->hierarchy->log_new_exec = log_new_exec; + if ((!log_same_exec && !log_new_exec) || !prev_log_subdomains) + new_dom->hierarchy->log_status = LANDLOCK_LOG_DISABLED; #endif /* CONFIG_AUDIT */ - /* Replaces the old (prepared) domain. */ - landlock_put_ruleset(new_llcred->domain); - new_llcred->domain = new_dom; + /* Replaces the old (prepared) domain. */ + landlock_put_ruleset(new_llcred->domain); + new_llcred->domain = new_dom; #ifdef CONFIG_AUDIT - new_llcred->domain_exec |= BIT(new_dom->num_layers - 1); + new_llcred->domain_exec |= BIT(new_dom->num_layers - 1); #endif /* CONFIG_AUDIT */ + } + + if (flags & LANDLOCK_RESTRICT_SELF_TSYNC) { + const int err = landlock_restrict_sibling_threads( + current_cred(), new_cred); + if (err) { + abort_creds(new_cred); + return err; + } + } return commit_creds(new_cred); } diff --git a/security/landlock/tsync.c b/security/landlock/tsync.c new file mode 100644 index 000000000000..0d2b9c646030 --- /dev/null +++ b/security/landlock/tsync.c @@ -0,0 +1,561 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Landlock - Cross-thread ruleset enforcement + * + * Copyright © 2025 Google LLC + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cred.h" +#include "tsync.h" + +/* + * Shared state between multiple threads which are enforcing Landlock rulesets + * in lockstep with each other. + */ +struct tsync_shared_context { + /* The old and tentative new creds of the calling thread. */ + const struct cred *old_cred; + const struct cred *new_cred; + + /* True if sibling tasks need to set the no_new_privs flag. */ + bool set_no_new_privs; + + /* An error encountered in preparation step, or 0. */ + atomic_t preparation_error; + + /* + * Barrier after preparation step in restrict_one_thread. + * The calling thread waits for completion. + * + * Re-initialized on every round of looking for newly spawned threads. + */ + atomic_t num_preparing; + struct completion all_prepared; + + /* Sibling threads wait for completion. */ + struct completion ready_to_commit; + + /* + * Barrier after commit step (used by syscall impl to wait for + * completion). + */ + atomic_t num_unfinished; + struct completion all_finished; +}; + +struct tsync_work { + struct callback_head work; + struct task_struct *task; + struct tsync_shared_context *shared_ctx; +}; + +/* + * restrict_one_thread - update a thread's Landlock domain in lockstep with the + * other threads in the same process + * + * When this is run, the same function gets run in all other threads in the same + * process (except for the calling thread which called landlock_restrict_self). + * The concurrently running invocations of restrict_one_thread coordinate + * through the shared ctx object to do their work in lockstep to implement + * all-or-nothing semantics for enforcing the new Landlock domain. + * + * Afterwards, depending on the presence of an error, all threads either commit + * or abort the prepared credentials. The commit operation can not fail any + * more. + */ +static void restrict_one_thread(struct tsync_shared_context *ctx) +{ + int err; + struct cred *cred = NULL; + + if (current_cred() == ctx->old_cred) { + /* + * Switch out old_cred with new_cred, if possible. + * + * In the common case, where all threads initially point to the same + * struct cred, this optimization avoids creating separate redundant + * credentials objects for each, which would all have the same contents. + * + * Note: We are intentionally dropping the const qualifier here, because + * it is required by commit_creds() and abort_creds(). + */ + cred = (struct cred *)get_cred(ctx->new_cred); + } else { + /* Else, prepare new creds and populate them. */ + cred = prepare_creds(); + + if (!cred) { + atomic_set(&ctx->preparation_error, -ENOMEM); + + /* + * Even on error, we need to adhere to the protocol and coordinate + * with concurrently running invocations. + */ + if (atomic_dec_return(&ctx->num_preparing) == 0) + complete_all(&ctx->all_prepared); + + goto out; + } + + landlock_cred_copy(landlock_cred(cred), + landlock_cred(ctx->new_cred)); + } + + /* + * Barrier: Wait until all threads are done preparing. + * After this point, we can have no more failures. + */ + if (atomic_dec_return(&ctx->num_preparing) == 0) + complete_all(&ctx->all_prepared); + + /* + * Wait for signal from calling thread that it's safe to read the + * preparation error now and we are ready to commit (or abort). + */ + wait_for_completion(&ctx->ready_to_commit); + + /* Abort the commit if any of the other threads had an error. */ + err = atomic_read(&ctx->preparation_error); + if (err) { + abort_creds(cred); + goto out; + } + + /* + * Make sure that all sibling tasks fulfill the no_new_privs prerequisite. + * (This is in line with Seccomp's SECCOMP_FILTER_FLAG_TSYNC logic in + * kernel/seccomp.c) + */ + if (ctx->set_no_new_privs) + task_set_no_new_privs(current); + + commit_creds(cred); + +out: + /* Notify the calling thread once all threads are done */ + if (atomic_dec_return(&ctx->num_unfinished) == 0) + complete_all(&ctx->all_finished); +} + +/* + * restrict_one_thread_callback - task_work callback for restricting a thread + * + * Calls restrict_one_thread with the struct landlock_shared_tsync_context. + */ +static void restrict_one_thread_callback(struct callback_head *work) +{ + struct tsync_work *ctx = container_of(work, struct tsync_work, work); + + restrict_one_thread(ctx->shared_ctx); +} + +/* + * struct tsync_works - a growable array of per-task contexts + * + * The zero-initialized struct represents the empty array. + */ +struct tsync_works { + struct tsync_work **works; + size_t size; + size_t capacity; +}; + +/* + * tsync_works_provide - provides a preallocated tsync_work for the given task + * + * This also stores a task pointer in the context and increments the reference + * count of the task. + * + * This function may fail in the case where we did not preallocate sufficient + * capacity. This can legitimately happen if new threads get started after we + * grew the capacity. + * + * Returns: + * A pointer to the preallocated context struct, with task filled in. + * + * NULL, if we ran out of preallocated context structs. + */ +static struct tsync_work *tsync_works_provide(struct tsync_works *s, + struct task_struct *task) +{ + struct tsync_work *ctx; + + if (s->size >= s->capacity) + return NULL; + + ctx = s->works[s->size]; + s->size++; + + ctx->task = get_task_struct(task); + return ctx; +} + +/* + * tsync_works_grow_by - preallocates space for n more contexts in s + * + * On a successful return, the subsequent n calls to tsync_works_provide() are + * guaranteed to succeed. (size + n <= capacity) + * + * Returns: + * -ENOMEM if the (re)allocation fails + + * 0 if the allocation succeeds, partially succeeds, or no reallocation + * was needed + */ +static int tsync_works_grow_by(struct tsync_works *s, size_t n, gfp_t flags) +{ + size_t i; + size_t new_capacity; + struct tsync_work **works; + struct tsync_work *work; + + if (check_add_overflow(s->size, n, &new_capacity)) + return -EOVERFLOW; + + /* No need to reallocate if s already has sufficient capacity. */ + if (new_capacity <= s->capacity) + return 0; + + works = krealloc_array(s->works, new_capacity, sizeof(s->works[0]), + flags); + if (!works) + return -ENOMEM; + + s->works = works; + + for (i = s->capacity; i < new_capacity; i++) { + work = kzalloc(sizeof(*work), flags); + if (!work) { + /* + * Leave the object in a consistent state, + * but return an error. + */ + s->capacity = i; + return -ENOMEM; + } + s->works[i] = work; + } + s->capacity = new_capacity; + return 0; +} + +/* + * tsync_works_contains - checks for presence of task in s + */ +static bool tsync_works_contains_task(const struct tsync_works *s, + struct task_struct *task) +{ + size_t i; + + for (i = 0; i < s->size; i++) + if (s->works[i]->task == task) + return true; + return false; +} + +/* + * tsync_works_release - frees memory held by s and drops all task references + * + * This does not free s itself, only the data structures held by it. + */ +static void tsync_works_release(struct tsync_works *s) +{ + size_t i; + + for (i = 0; i < s->size; i++) { + if (!s->works[i]->task) + continue; + + put_task_struct(s->works[i]->task); + } + + for (i = 0; i < s->capacity; i++) + kfree(s->works[i]); + kfree(s->works); + s->works = NULL; + s->size = 0; + s->capacity = 0; +} + +/* + * count_additional_threads - counts the sibling threads that are not in works + */ +static size_t count_additional_threads(const struct tsync_works *works) +{ + struct task_struct *thread, *caller; + size_t n = 0; + + caller = current; + + guard(rcu)(); + + for_each_thread(caller, thread) { + /* Skip current, since it is initiating the sync. */ + if (thread == caller) + continue; + + /* Skip exited threads. */ + if (thread->flags & PF_EXITING) + continue; + + /* Skip threads that we have already seen. */ + if (tsync_works_contains_task(works, thread)) + continue; + + n++; + } + return n; +} + +/* + * schedule_task_work - adds task_work for all eligible sibling threads + * which have not been scheduled yet + * + * For each added task_work, atomically increments shared_ctx->num_preparing and + * shared_ctx->num_unfinished. + * + * Returns: + * true, if at least one eligible sibling thread was found + */ +static bool schedule_task_work(struct tsync_works *works, + struct tsync_shared_context *shared_ctx) +{ + int err; + struct task_struct *thread, *caller; + struct tsync_work *ctx; + bool found_more_threads = false; + + caller = current; + + guard(rcu)(); + + for_each_thread(caller, thread) { + /* Skip current, since it is initiating the sync. */ + if (thread == caller) + continue; + + /* Skip exited threads. */ + if (thread->flags & PF_EXITING) + continue; + + /* Skip threads that we already looked at. */ + if (tsync_works_contains_task(works, thread)) + continue; + + /* + * We found a sibling thread that is not doing its task_work yet, and + * which might spawn new threads before our task work runs, so we need + * at least one more round in the outer loop. + */ + found_more_threads = true; + + ctx = tsync_works_provide(works, thread); + if (!ctx) { + /* + * We ran out of preallocated contexts -- we need to try again with + * this thread at a later time! + * found_more_threads is already true at this point. + */ + break; + } + + ctx->shared_ctx = shared_ctx; + + atomic_inc(&shared_ctx->num_preparing); + atomic_inc(&shared_ctx->num_unfinished); + + init_task_work(&ctx->work, restrict_one_thread_callback); + err = task_work_add(thread, &ctx->work, TWA_SIGNAL); + if (err) { + /* + * task_work_add() only fails if the task is about to exit. We + * checked that earlier, but it can happen as a race. Resume + * without setting an error, as the task is probably gone in the + * next loop iteration. For consistency, remove the task from ctx + * so that it does not look like we handed it a task_work. + */ + put_task_struct(ctx->task); + ctx->task = NULL; + + atomic_dec(&shared_ctx->num_preparing); + atomic_dec(&shared_ctx->num_unfinished); + } + } + + return found_more_threads; +} + +/* + * cancel_tsync_works - cancel all task works where it is possible + * + * Task works can be canceled as long as they are still queued and have not + * started running. If they get canceled, we decrement + * shared_ctx->num_preparing and shared_ctx->num_unfished and mark the two + * completions if needed, as if the task was never scheduled. + */ +static void cancel_tsync_works(struct tsync_works *works, + struct tsync_shared_context *shared_ctx) +{ + int i; + + for (i = 0; i < works->size; i++) { + if (!task_work_cancel(works->works[i]->task, + &works->works[i]->work)) + continue; + + /* After dequeueing, act as if the task work had executed. */ + + if (atomic_dec_return(&shared_ctx->num_preparing) == 0) + complete_all(&shared_ctx->all_prepared); + + if (atomic_dec_return(&shared_ctx->num_unfinished) == 0) + complete_all(&shared_ctx->all_finished); + } +} + +/* + * restrict_sibling_threads - enables a Landlock policy for all sibling threads + */ +int landlock_restrict_sibling_threads(const struct cred *old_cred, + const struct cred *new_cred) +{ + int err; + struct tsync_shared_context shared_ctx; + struct tsync_works works = {}; + size_t newly_discovered_threads; + bool found_more_threads; + + atomic_set(&shared_ctx.preparation_error, 0); + init_completion(&shared_ctx.all_prepared); + init_completion(&shared_ctx.ready_to_commit); + atomic_set(&shared_ctx.num_unfinished, 1); + init_completion(&shared_ctx.all_finished); + shared_ctx.old_cred = old_cred; + shared_ctx.new_cred = new_cred; + shared_ctx.set_no_new_privs = task_no_new_privs(current); + + /* + * We schedule a pseudo-signal task_work for each of the calling task's + * sibling threads. In the task work, each thread: + * + * 1) runs prepare_creds() and writes back the error to + * shared_ctx.preparation_error, if needed. + * + * 2) signals that it's done with prepare_creds() to the calling task. + * (completion "all_prepared"). + * + * 3) waits for the completion "ready_to_commit". This is sent by the + * calling task after ensuring that all sibling threads have done + * with the "preparation" stage. + * + * After this barrier is reached, it's safe to read + * shared_ctx.preparation_error. + * + * 4) reads shared_ctx.preparation_error and then either does commit_creds() + * or abort_creds(). + * + * 5) signals that it's done altogether (barrier synchronization + * "all_finished") + * + * Unlike seccomp, which modifies sibling tasks directly, we do not need to + * acquire the cred_guard_mutex and sighand->siglock: + * + * - As in our case, all threads are themselves exchanging their own struct + * cred through the credentials API, no locks are needed for that. + * - Our for_each_thread() loops are protected by RCU. + * - We do not acquire a lock to keep the list of sibling threads stable + * between our for_each_thread loops. If the list of available sibling + * threads changes between these for_each_thread loops, we make up for + * that by continuing to look for threads until they are all discovered + * and have entered their task_work, where they are unable to spawn new + * threads. + */ + do { + /* In RCU read-lock, count the threads we need. */ + newly_discovered_threads = count_additional_threads(&works); + + if (newly_discovered_threads == 0) + break; /* done */ + + err = tsync_works_grow_by(&works, newly_discovered_threads, + GFP_KERNEL_ACCOUNT); + if (err) { + atomic_set(&shared_ctx.preparation_error, err); + break; + } + + /* + * The "all_prepared" barrier is used locally to the loop body, this use + * of for_each_thread(). We can reset it on each loop iteration because + * all previous loop iterations are done with it already. + * + * num_preparing is initialized to 1 so that the counter can not go to 0 + * and mark the completion as done before all task works are registered. + * We decrement it at the end of the loop body. + */ + atomic_set(&shared_ctx.num_preparing, 1); + reinit_completion(&shared_ctx.all_prepared); + + /* + * In RCU read-lock, schedule task work on newly discovered sibling + * tasks. + */ + found_more_threads = schedule_task_work(&works, &shared_ctx); + + /* + * Decrement num_preparing for current, to undo that we initialized it + * to 1 a few lines above. + */ + if (atomic_dec_return(&shared_ctx.num_preparing) > 0) { + if (wait_for_completion_interruptible( + &shared_ctx.all_prepared)) { + /* In case of interruption, we need to retry the system call. */ + atomic_set(&shared_ctx.preparation_error, + -ERESTARTNOINTR); + + /* + * Cancel task works for tasks that did not start running yet, + * and decrement all_prepared and num_unfinished accordingly. + */ + cancel_tsync_works(&works, &shared_ctx); + + /* + * The remaining task works have started running, so waiting for + * their completion will finish. + */ + wait_for_completion(&shared_ctx.all_prepared); + } + } + } while (found_more_threads && + !atomic_read(&shared_ctx.preparation_error)); + + /* + * We now have all sibling threads blocking and in "prepared" state in the + * task work. Ask all threads to commit. + */ + complete_all(&shared_ctx.ready_to_commit); + + /* + * Decrement num_unfinished for current, to undo that we initialized it to 1 + * at the beginning. + */ + if (atomic_dec_return(&shared_ctx.num_unfinished) > 0) + wait_for_completion(&shared_ctx.all_finished); + + tsync_works_release(&works); + + return atomic_read(&shared_ctx.preparation_error); +} diff --git a/security/landlock/tsync.h b/security/landlock/tsync.h new file mode 100644 index 000000000000..ef86bb61c2f6 --- /dev/null +++ b/security/landlock/tsync.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Landlock - Cross-thread ruleset enforcement + * + * Copyright © 2025 Google LLC + */ + +#ifndef _SECURITY_LANDLOCK_TSYNC_H +#define _SECURITY_LANDLOCK_TSYNC_H + +#include + +int landlock_restrict_sibling_threads(const struct cred *old_cred, + const struct cred *new_cred); + +#endif /* _SECURITY_LANDLOCK_TSYNC_H */ diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c index 7b69002239d7..fdbb672009ac 100644 --- a/tools/testing/selftests/landlock/base_test.c +++ b/tools/testing/selftests/landlock/base_test.c @@ -76,7 +76,7 @@ TEST(abi_version) const struct landlock_ruleset_attr ruleset_attr = { .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE, }; - ASSERT_EQ(7, landlock_create_ruleset(NULL, 0, + ASSERT_EQ(8, landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION)); ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0, @@ -306,7 +306,7 @@ TEST(restrict_self_fd_flags) TEST(restrict_self_flags) { - const __u32 last_flag = LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF; + const __u32 last_flag = LANDLOCK_RESTRICT_SELF_TSYNC; /* Tests invalid flag combinations. */ -- cgit v1.2.3 From bbb6f53e905ca119f99ccab8496f8921d9db9c50 Mon Sep 17 00:00:00 2001 From: Matthieu Buffet Date: Fri, 12 Dec 2025 17:36:57 +0100 Subject: landlock: Minor reword of docs for TCP access rights MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move ABI requirement next to each access right to prepare adding more access rights; - Mention the possibility to remove the random component of a socket's ephemeral port choice within the netns-wide ephemeral port range, since it allows choosing the "random" ephemeral port. Signed-off-by: Matthieu Buffet Link: https://lore.kernel.org/r/20251212163704.142301-2-matthieu@buffet.re Signed-off-by: Mickaël Salaün --- include/uapi/linux/landlock.h | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h index d5081ab4e5ef..f88fa1f68b77 100644 --- a/include/uapi/linux/landlock.h +++ b/include/uapi/linux/landlock.h @@ -195,11 +195,13 @@ struct landlock_net_port_attr { * It should be noted that port 0 passed to :manpage:`bind(2)` will bind * to an available port from the ephemeral port range. This can be * configured with the ``/proc/sys/net/ipv4/ip_local_port_range`` sysctl - * (also used for IPv6). + * (also used for IPv6), and within that range, on a per-socket basis + * with ``setsockopt(IP_LOCAL_PORT_RANGE)``. * - * A Landlock rule with port 0 and the ``LANDLOCK_ACCESS_NET_BIND_TCP`` + * A Landlock rule with port 0 and the %LANDLOCK_ACCESS_NET_BIND_TCP * right means that requesting to bind on port 0 is allowed and it will - * automatically translate to binding on the related port range. + * automatically translate to binding on a kernel-assigned ephemeral + * port. */ __u64 port; }; @@ -342,13 +344,12 @@ struct landlock_net_port_attr { * These flags enable to restrict a sandboxed process to a set of network * actions. * - * This is supported since Landlock ABI version 4. - * * The following access rights apply to TCP port numbers: * - * - %LANDLOCK_ACCESS_NET_BIND_TCP: Bind a TCP socket to a local port. - * - %LANDLOCK_ACCESS_NET_CONNECT_TCP: Connect an active TCP socket to - * a remote port. + * - %LANDLOCK_ACCESS_NET_BIND_TCP: Bind TCP sockets to the given local + * port. Support added in Landlock ABI version 4. + * - %LANDLOCK_ACCESS_NET_CONNECT_TCP: Connect TCP sockets to the given + * remote port. Support added in Landlock ABI version 4. */ /* clang-format off */ #define LANDLOCK_ACCESS_NET_BIND_TCP (1ULL << 0) -- cgit v1.2.3 From 136f1e168f4941021565f8c10ff4bb81b1f13f2c Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Thu, 5 Feb 2026 18:34:23 +0100 Subject: mptcp: fix kdoc warnings The following warnings were visible: $ ./scripts/kernel-doc -Wall -none \ net/mptcp/ include/net/mptcp.h include/uapi/linux/mptcp*.h \ include/trace/events/mptcp.h Warning: net/mptcp/token.c:108 No description found for return value of 'mptcp_token_new_request' Warning: net/mptcp/token.c:151 No description found for return value of 'mptcp_token_new_connect' Warning: net/mptcp/token.c:246 No description found for return value of 'mptcp_token_get_sock' Warning: net/mptcp/token.c:298 No description found for return value of 'mptcp_token_iter_next' Warning: net/mptcp/protocol.c:4431 No description found for return value of 'mptcp_splice_read' Warning: include/uapi/linux/mptcp_pm.h:13 missing initial short description on line: * enum mptcp_event_type Address all of them: either by using the 'Return:' keyword, or by adding a missing initial short description. The MPTCP CI will soon report issues with kdoc to avoid introducing new issues and being flagged by the Netdev CI. Reviewed-by: Geliang Tang Reviewed-by: Randy Dunlap Signed-off-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260205-net-mptcp-misc-fixes-6-19-rc8-v2-3-c2720ce75c34@kernel.org Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/mptcp_pm.yaml | 1 + include/uapi/linux/mptcp_pm.h | 2 +- net/mptcp/token.c | 16 +++++++++------- 3 files changed, 11 insertions(+), 8 deletions(-) (limited to 'include/uapi/linux') diff --git a/Documentation/netlink/specs/mptcp_pm.yaml b/Documentation/netlink/specs/mptcp_pm.yaml index ba30a40b9dbf..39f3facc38e5 100644 --- a/Documentation/netlink/specs/mptcp_pm.yaml +++ b/Documentation/netlink/specs/mptcp_pm.yaml @@ -15,6 +15,7 @@ definitions: type: enum name: event-type enum-name: mptcp-event-type + doc: Netlink MPTCP event types name-prefix: mptcp-event- entries: - diff --git a/include/uapi/linux/mptcp_pm.h b/include/uapi/linux/mptcp_pm.h index c97d060ee90b..fe9863d75350 100644 --- a/include/uapi/linux/mptcp_pm.h +++ b/include/uapi/linux/mptcp_pm.h @@ -11,7 +11,7 @@ #define MPTCP_PM_VER 1 /** - * enum mptcp_event_type + * enum mptcp_event_type - Netlink MPTCP event types * @MPTCP_EVENT_UNSPEC: unused event * @MPTCP_EVENT_CREATED: A new MPTCP connection has been created. It is the * good time to allocate memory and send ADD_ADDR if needed. Depending on the diff --git a/net/mptcp/token.c b/net/mptcp/token.c index 5bb924534387..f1a50f367add 100644 --- a/net/mptcp/token.c +++ b/net/mptcp/token.c @@ -103,7 +103,7 @@ static void mptcp_crypto_key_gen_sha(u64 *key, u32 *token, u64 *idsn) * It creates a unique token to identify the new mptcp connection, * a secret local key and the initial data sequence number (idsn). * - * Returns 0 on success. + * Return: 0 on success. */ int mptcp_token_new_request(struct request_sock *req) { @@ -146,7 +146,7 @@ int mptcp_token_new_request(struct request_sock *req) * the computed token at a later time, this is needed to process * join requests. * - * returns 0 on success. + * Return: 0 on success. */ int mptcp_token_new_connect(struct sock *ssk) { @@ -241,7 +241,7 @@ found: * This function returns the mptcp connection structure with the given token. * A reference count on the mptcp socket returned is taken. * - * returns NULL if no connection with the given token value exists. + * Return: NULL if no connection with the given token value exists. */ struct mptcp_sock *mptcp_token_get_sock(struct net *net, u32 token) { @@ -288,11 +288,13 @@ EXPORT_SYMBOL_GPL(mptcp_token_get_sock); * @s_slot: start slot number * @s_num: start number inside the given lock * - * This function returns the first mptcp connection structure found inside the - * token container starting from the specified position, or NULL. + * Description: + * On successful iteration, the iterator is moved to the next position and a + * reference to the returned socket is acquired. * - * On successful iteration, the iterator is moved to the next position and - * a reference to the returned socket is acquired. + * Return: + * The first mptcp connection structure found inside the token container + * starting from the specified position, or NULL. */ struct mptcp_sock *mptcp_token_iter_next(const struct net *net, long *s_slot, long *s_num) -- cgit v1.2.3 From 90079798f1d748e97c74e23736491543577b8aee Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Mon, 2 Feb 2026 10:59:00 +0100 Subject: delayacct: fix uapi timespec64 definition The custom definition of 'struct timespec64' is incompatible with both the kernel's internal definition and the glibc type, at least on big-endian targets that have the tv_nsec field in a different place, and the definition clashes with any userspace that also defines a timespec64 structure. Running the header check with -Wpadding enabled produces this output that warns about the incorrect padding: usr/include/linux/taskstats.h:25:1: error: padding struct size to alignment boundary with 4 bytes [-Werror=padded] Remove the hack and instead use the regular __kernel_timespec type that is meant to be used in uapi definitions. Link: https://lkml.kernel.org/r/20260202095906.1344100-1-arnd@kernel.org Fixes: 29b63f6eff0e ("delayacct: add timestamp of delay max") Signed-off-by: Arnd Bergmann Cc: Fan Yu Cc: Jonathan Corbet Cc: xu xin Cc: Yang Yang Cc: Balbir Singh Cc: Jiang Kun Signed-off-by: Andrew Morton --- include/uapi/linux/taskstats.h | 27 +++++++++------------------ kernel/delayacct.c | 6 ++++-- 2 files changed, 13 insertions(+), 20 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/taskstats.h b/include/uapi/linux/taskstats.h index 1b31e8e14d2f..3ae25f3ce067 100644 --- a/include/uapi/linux/taskstats.h +++ b/include/uapi/linux/taskstats.h @@ -18,16 +18,7 @@ #define _LINUX_TASKSTATS_H #include -#ifdef __KERNEL__ -#include -#else -#ifndef _LINUX_TIME64_H -struct timespec64 { - __s64 tv_sec; /* seconds */ - long tv_nsec; /* nanoseconds */ -}; -#endif -#endif +#include /* Format for per-task data returned to userland when * - a task exits @@ -242,14 +233,14 @@ struct taskstats { __u64 irq_delay_min; /*v17: delay max timestamp record*/ - struct timespec64 cpu_delay_max_ts; - struct timespec64 blkio_delay_max_ts; - struct timespec64 swapin_delay_max_ts; - struct timespec64 freepages_delay_max_ts; - struct timespec64 thrashing_delay_max_ts; - struct timespec64 compact_delay_max_ts; - struct timespec64 wpcopy_delay_max_ts; - struct timespec64 irq_delay_max_ts; + struct __kernel_timespec cpu_delay_max_ts; + struct __kernel_timespec blkio_delay_max_ts; + struct __kernel_timespec swapin_delay_max_ts; + struct __kernel_timespec freepages_delay_max_ts; + struct __kernel_timespec thrashing_delay_max_ts; + struct __kernel_timespec compact_delay_max_ts; + struct __kernel_timespec wpcopy_delay_max_ts; + struct __kernel_timespec irq_delay_max_ts; }; diff --git a/kernel/delayacct.c b/kernel/delayacct.c index d58ffc63bcba..2e55c493c98b 100644 --- a/kernel/delayacct.c +++ b/kernel/delayacct.c @@ -18,7 +18,8 @@ do { \ d->type##_delay_max = tsk->delays->type##_delay_max; \ d->type##_delay_min = tsk->delays->type##_delay_min; \ - d->type##_delay_max_ts = tsk->delays->type##_delay_max_ts; \ + d->type##_delay_max_ts.tv_sec = tsk->delays->type##_delay_max_ts.tv_sec; \ + d->type##_delay_max_ts.tv_nsec = tsk->delays->type##_delay_max_ts.tv_nsec; \ tmp = d->type##_delay_total + tsk->delays->type##_delay; \ d->type##_delay_total = (tmp < d->type##_delay_total) ? 0 : tmp; \ d->type##_count += tsk->delays->type##_count; \ @@ -175,7 +176,8 @@ int delayacct_add_tsk(struct taskstats *d, struct task_struct *tsk) d->cpu_delay_max = tsk->sched_info.max_run_delay; d->cpu_delay_min = tsk->sched_info.min_run_delay; - d->cpu_delay_max_ts = tsk->sched_info.max_run_delay_ts; + d->cpu_delay_max_ts.tv_sec = tsk->sched_info.max_run_delay_ts.tv_sec; + d->cpu_delay_max_ts.tv_nsec = tsk->sched_info.max_run_delay_ts.tv_nsec; tmp = (s64)d->cpu_delay_total + t2; d->cpu_delay_total = (tmp < (s64)d->cpu_delay_total) ? 0 : tmp; tmp = (s64)d->cpu_run_virtual_total + t3; -- cgit v1.2.3 From ebcff9dacaf2c1418f8bc927388186d7d3674603 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Mon, 2 Feb 2026 23:48:07 +0100 Subject: vduse: avoid adding implicit padding The vduse_iova_range_v2 and vduse_iotlb_entry_v2 structures are both defined in a way that adds implicit padding and is incompatible between i386 and x86_64 userspace because of the different structure alignment requirements. Building the header with -Wpadded shows these new warnings: vduse.h:305:1: error: padding struct size to alignment boundary with 4 bytes [-Werror=padded] vduse.h:374:1: error: padding struct size to alignment boundary with 4 bytes [-Werror=padded] Change the amount of padding in these two structures to align them to 64 bit words and avoid those problems. Since the v1 vduse_iotlb_entry already has an inconsistent size, do not attempt to reuse the structure but rather list the members indiviudally, with a fixed amount of padding. Fixes: 079212f6877e ("vduse: add vq group asid support") Signed-off-by: Arnd Bergmann Signed-off-by: Michael S. Tsirkin Message-Id: <20260202224835.559538-1-arnd@kernel.org> --- drivers/vdpa/vdpa_user/vduse_dev.c | 40 +++++++++++++------------------------- include/uapi/linux/vduse.h | 9 +++++++-- 2 files changed, 21 insertions(+), 28 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/vdpa/vdpa_user/vduse_dev.c b/drivers/vdpa/vdpa_user/vduse_dev.c index 73d1d517dc6c..405d59610f76 100644 --- a/drivers/vdpa/vdpa_user/vduse_dev.c +++ b/drivers/vdpa/vdpa_user/vduse_dev.c @@ -1301,7 +1301,7 @@ static int vduse_dev_iotlb_entry(struct vduse_dev *dev, int r = -EINVAL; struct vhost_iotlb_map *map; - if (entry->v1.start > entry->v1.last || entry->asid >= dev->nas) + if (entry->start > entry->last || entry->asid >= dev->nas) return -EINVAL; asid = array_index_nospec(entry->asid, dev->nas); @@ -1312,18 +1312,18 @@ static int vduse_dev_iotlb_entry(struct vduse_dev *dev, spin_lock(&dev->as[asid].domain->iotlb_lock); map = vhost_iotlb_itree_first(dev->as[asid].domain->iotlb, - entry->v1.start, entry->v1.last); + entry->start, entry->last); if (map) { if (f) { const struct vdpa_map_file *map_file; map_file = (struct vdpa_map_file *)map->opaque; - entry->v1.offset = map_file->offset; + entry->offset = map_file->offset; *f = get_file(map_file->file); } - entry->v1.start = map->start; - entry->v1.last = map->last; - entry->v1.perm = map->perm; + entry->start = map->start; + entry->last = map->last; + entry->perm = map->perm; if (capability) { *capability = 0; @@ -1363,14 +1363,8 @@ static long vduse_dev_ioctl(struct file *file, unsigned int cmd, break; ret = -EFAULT; - if (cmd == VDUSE_IOTLB_GET_FD2) { - if (copy_from_user(&entry, argp, sizeof(entry))) - break; - } else { - if (copy_from_user(&entry.v1, argp, - sizeof(entry.v1))) - break; - } + if (copy_from_user(&entry, argp, _IOC_SIZE(cmd))) + break; ret = -EINVAL; if (!is_mem_zero((const char *)entry.reserved, @@ -1385,19 +1379,13 @@ static long vduse_dev_ioctl(struct file *file, unsigned int cmd, if (!f) break; - if (cmd == VDUSE_IOTLB_GET_FD2) - ret = copy_to_user(argp, &entry, - sizeof(entry)); - else - ret = copy_to_user(argp, &entry.v1, - sizeof(entry.v1)); - + ret = copy_to_user(argp, &entry, _IOC_SIZE(cmd)); if (ret) { ret = -EFAULT; fput(f); break; } - ret = receive_fd(f, NULL, perm_to_file_flags(entry.v1.perm)); + ret = receive_fd(f, NULL, perm_to_file_flags(entry.perm)); fput(f); break; } @@ -1603,16 +1591,16 @@ static long vduse_dev_ioctl(struct file *file, unsigned int cmd, } else if (info.asid >= dev->nas) break; - entry.v1.start = info.start; - entry.v1.last = info.last; + entry.start = info.start; + entry.last = info.last; entry.asid = info.asid; ret = vduse_dev_iotlb_entry(dev, &entry, NULL, &info.capability); if (ret < 0) break; - info.start = entry.v1.start; - info.last = entry.v1.last; + info.start = entry.start; + info.last = entry.last; info.asid = entry.asid; ret = -EFAULT; diff --git a/include/uapi/linux/vduse.h b/include/uapi/linux/vduse.h index 68b4287f9fac..361eea511c21 100644 --- a/include/uapi/linux/vduse.h +++ b/include/uapi/linux/vduse.h @@ -293,9 +293,13 @@ struct vduse_iova_info { * Structure used by VDUSE_IOTLB_GET_FD2 ioctl to find an overlapped IOVA region. */ struct vduse_iotlb_entry_v2 { - struct vduse_iotlb_entry v1; + __u64 offset; + __u64 start; + __u64 last; + __u8 perm; + __u8 padding[7]; __u32 asid; - __u32 reserved[12]; + __u32 reserved[11]; }; /* @@ -365,6 +369,7 @@ struct vduse_iova_range_v2 { __u64 start; __u64 last; __u32 asid; + __u32 padding; }; /** -- cgit v1.2.3 From c29214677a9fc1a3a4ee65e189afeb5fd10d676f Mon Sep 17 00:00:00 2001 From: Pavel Begunkov Date: Sun, 15 Feb 2026 21:34:28 +0000 Subject: io_uring/query: return support for custom rx page size Add an ability to query if the zcrx rx page size setting is available. Note, even when the API is supported by io_uring, the registration can still get rejected for various reasons, e.g. when the NIC or the driver doesn't support it, when the particular specified size is unsupported, when the memory area doesn't satisfy all requirements, etc. Signed-off-by: Pavel Begunkov Signed-off-by: Jens Axboe --- include/uapi/linux/io_uring.h | 8 ++++++++ include/uapi/linux/io_uring/query.h | 3 ++- io_uring/query.c | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/io_uring.h b/include/uapi/linux/io_uring.h index da5156954731..c462bdf3c42c 100644 --- a/include/uapi/linux/io_uring.h +++ b/include/uapi/linux/io_uring.h @@ -1090,6 +1090,14 @@ enum zcrx_reg_flags { ZCRX_REG_IMPORT = 1, }; +enum zcrx_features { + /* + * The user can ask for the desired rx page size by passing the + * value in struct io_uring_zcrx_ifq_reg::rx_buf_len. + */ + ZCRX_FEATURE_RX_PAGE_SIZE = 1 << 0, +}; + /* * Argument for IORING_REGISTER_ZCRX_IFQ */ diff --git a/include/uapi/linux/io_uring/query.h b/include/uapi/linux/io_uring/query.h index 2456e6c5ebb5..0b6248175e26 100644 --- a/include/uapi/linux/io_uring/query.h +++ b/include/uapi/linux/io_uring/query.h @@ -50,7 +50,8 @@ struct io_uring_query_zcrx { __u64 area_flags; /* The number of supported ZCRX_CTRL_* opcodes */ __u32 nr_ctrl_opcodes; - __u32 __resv1; + /* Bitmask of ZCRX_FEATURE_* indicating which features are available */ + __u32 features; /* The refill ring header size */ __u32 rq_hdr_size; /* The alignment for the header */ diff --git a/io_uring/query.c b/io_uring/query.c index abdd6f3e1223..63cc30c9803d 100644 --- a/io_uring/query.c +++ b/io_uring/query.c @@ -39,7 +39,7 @@ static ssize_t io_query_zcrx(union io_query_data *data) e->nr_ctrl_opcodes = __ZCRX_CTRL_LAST; e->rq_hdr_size = sizeof(struct io_uring); e->rq_hdr_alignment = L1_CACHE_BYTES; - e->__resv1 = 0; + e->features = ZCRX_FEATURE_RX_PAGE_SIZE; e->__resv2 = 0; return sizeof(*e); } -- cgit v1.2.3 From 6b34f8edf8b807b7f87901623aa52dfa1b29ef93 Mon Sep 17 00:00:00 2001 From: Pavel Begunkov Date: Sun, 15 Feb 2026 21:38:09 +0000 Subject: io_uring/query: add query.h copyright notice Add a copyright notice to io_uring's query uapi header. Signed-off-by: Pavel Begunkov Signed-off-by: Jens Axboe --- include/uapi/linux/io_uring/query.h | 3 +++ 1 file changed, 3 insertions(+) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/io_uring/query.h b/include/uapi/linux/io_uring/query.h index 0b6248175e26..95500759cc13 100644 --- a/include/uapi/linux/io_uring/query.h +++ b/include/uapi/linux/io_uring/query.h @@ -1,6 +1,9 @@ /* SPDX-License-Identifier: (GPL-2.0 WITH Linux-syscall-note) OR MIT */ /* * Header file for the io_uring query interface. + * + * Copyright (C) 2026 Pavel Begunkov + * Copyright (C) Meta Platforms, Inc. */ #ifndef LINUX_IO_URING_QUERY_H #define LINUX_IO_URING_QUERY_H -- cgit v1.2.3 From be3573124e630736d2d39650b12f5ef220b47ac1 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Tue, 10 Feb 2026 10:00:44 -0700 Subject: io_uring/bpf_filter: pass in expected filter payload size It's quite possible that opcodes that have payloads attached to them, like IORING_OP_OPENAT/OPENAT2 or IORING_OP_SOCKET, that these paylods can change over time. For example, on the openat/openat2 side, the struct open_how argument is extensible, and could be extended in the future to allow further arguments to be passed in. Allow registration of a cBPF filter to give the size of the filter as seen by userspace. If that filter is for an opcode that takes extra payload data, allow it if the application payload expectation is the same size than the kernels. If that is the case, the kernel supports filtering on the payload that the application expects. If the size differs, the behavior depends on the IO_URING_BPF_FILTER_SZ_STRICT flag: 1) If IO_URING_BPF_FILTER_SZ_STRICT is set and the size expectation differs, fail the attempt to load the filter. 2) If IO_URING_BPF_FILTER_SZ_STRICT isn't set, allow the filter if the userspace pdu size is smaller than what the kernel offers. 3) Regardless if IO_URING_BPF_FILTER_SZ_STRICT, fail loading the filter if the userspace pdu size is bigger than what the kernel supports. An attempt to load a filter due to sizing will error with -EMSGSIZE. For that error, the registration struct will have filter->pdu_size populated with the pdu size that the kernel uses. Reported-by: Christian Brauner Signed-off-by: Jens Axboe --- include/uapi/linux/io_uring/bpf_filter.h | 8 +++- io_uring/bpf_filter.c | 65 ++++++++++++++++++++++++-------- 2 files changed, 56 insertions(+), 17 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/io_uring/bpf_filter.h b/include/uapi/linux/io_uring/bpf_filter.h index 220351b81bc0..1b461d792a7b 100644 --- a/include/uapi/linux/io_uring/bpf_filter.h +++ b/include/uapi/linux/io_uring/bpf_filter.h @@ -35,13 +35,19 @@ enum { * If set, any currently unset opcode will have a deny filter attached */ IO_URING_BPF_FILTER_DENY_REST = 1, + /* + * If set, if kernel and application don't agree on pdu_size for + * the given opcode, fail the registration of the filter. + */ + IO_URING_BPF_FILTER_SZ_STRICT = 2, }; struct io_uring_bpf_filter { __u32 opcode; /* io_uring opcode to filter */ __u32 flags; __u32 filter_len; /* number of BPF instructions */ - __u32 resv; + __u8 pdu_size; /* expected pdu size for opcode */ + __u8 resv[3]; __u64 filter_ptr; /* pointer to BPF filter */ __u64 resv2[5]; }; diff --git a/io_uring/bpf_filter.c b/io_uring/bpf_filter.c index 8ac7d06de122..28a23e92ee81 100644 --- a/io_uring/bpf_filter.c +++ b/io_uring/bpf_filter.c @@ -308,36 +308,69 @@ err: return ERR_PTR(-EBUSY); } -#define IO_URING_BPF_FILTER_FLAGS IO_URING_BPF_FILTER_DENY_REST +#define IO_URING_BPF_FILTER_FLAGS (IO_URING_BPF_FILTER_DENY_REST | \ + IO_URING_BPF_FILTER_SZ_STRICT) -int io_register_bpf_filter(struct io_restriction *res, - struct io_uring_bpf __user *arg) +static int io_bpf_filter_import(struct io_uring_bpf *reg, + struct io_uring_bpf __user *arg) { - struct io_bpf_filters *filters, *old_filters = NULL; - struct io_bpf_filter *filter, *old_filter; - struct io_uring_bpf reg; - struct bpf_prog *prog; - struct sock_fprog fprog; + const struct io_issue_def *def; int ret; - if (copy_from_user(®, arg, sizeof(reg))) + if (copy_from_user(reg, arg, sizeof(*reg))) return -EFAULT; - if (reg.cmd_type != IO_URING_BPF_CMD_FILTER) + if (reg->cmd_type != IO_URING_BPF_CMD_FILTER) return -EINVAL; - if (reg.cmd_flags || reg.resv) + if (reg->cmd_flags || reg->resv) return -EINVAL; - if (reg.filter.opcode >= IORING_OP_LAST) + if (reg->filter.opcode >= IORING_OP_LAST) return -EINVAL; - if (reg.filter.flags & ~IO_URING_BPF_FILTER_FLAGS) + if (reg->filter.flags & ~IO_URING_BPF_FILTER_FLAGS) return -EINVAL; - if (reg.filter.resv) + if (!mem_is_zero(reg->filter.resv, sizeof(reg->filter.resv))) return -EINVAL; - if (!mem_is_zero(reg.filter.resv2, sizeof(reg.filter.resv2))) + if (!mem_is_zero(reg->filter.resv2, sizeof(reg->filter.resv2))) return -EINVAL; - if (!reg.filter.filter_len || reg.filter.filter_len > BPF_MAXINSNS) + if (!reg->filter.filter_len || reg->filter.filter_len > BPF_MAXINSNS) return -EINVAL; + /* Verify filter size */ + def = &io_issue_defs[array_index_nospec(reg->filter.opcode, IORING_OP_LAST)]; + + /* same size, always ok */ + ret = 0; + if (reg->filter.pdu_size == def->filter_pdu_size) + ; + /* size differs, fail in strict mode */ + else if (reg->filter.flags & IO_URING_BPF_FILTER_SZ_STRICT) + ret = -EMSGSIZE; + /* userspace filter is bigger, always disallow */ + else if (reg->filter.pdu_size > def->filter_pdu_size) + ret = -EMSGSIZE; + + /* copy back kernel filter size */ + reg->filter.pdu_size = def->filter_pdu_size; + if (copy_to_user(&arg->filter, ®->filter, sizeof(reg->filter))) + return -EFAULT; + + return ret; +} + +int io_register_bpf_filter(struct io_restriction *res, + struct io_uring_bpf __user *arg) +{ + struct io_bpf_filters *filters, *old_filters = NULL; + struct io_bpf_filter *filter, *old_filter; + struct io_uring_bpf reg; + struct bpf_prog *prog; + struct sock_fprog fprog; + int ret; + + ret = io_bpf_filter_import(®, arg); + if (ret) + return ret; + fprog.len = reg.filter.filter_len; fprog.filter = u64_to_user_ptr(reg.filter.filter_ptr); -- cgit v1.2.3 From 4edd4ba71ce0df015303dba75ea9d20d1a217546 Mon Sep 17 00:00:00 2001 From: Phil Sutter Date: Sat, 14 Feb 2026 15:54:06 +0100 Subject: include: uapi: netfilter_bridge.h: Cover for musl libc Musl defines its own struct ethhdr and thus defines __UAPI_DEF_ETHHDR to zero. To avoid struct redefinition errors, user space is therefore supposed to include netinet/if_ether.h before (or instead of) linux/if_ether.h. To relieve them from this burden, include the libc header here if not building for kernel space. Reported-by: Alyssa Ross Suggested-by: Florian Westphal Signed-off-by: Phil Sutter Signed-off-by: Florian Westphal --- include/uapi/linux/netfilter_bridge.h | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/netfilter_bridge.h b/include/uapi/linux/netfilter_bridge.h index f6e8d1e05c97..758de72b2764 100644 --- a/include/uapi/linux/netfilter_bridge.h +++ b/include/uapi/linux/netfilter_bridge.h @@ -5,6 +5,10 @@ /* bridge-specific defines for netfilter. */ +#ifndef __KERNEL__ +#include /* for __UAPI_DEF_ETHHDR if defined */ +#endif + #include #include #include -- cgit v1.2.3 From a284dbc96a47891a7a595a1c81b1e2da4d309cf6 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Wed, 18 Feb 2026 14:47:59 +0000 Subject: mshv: Add nested virtualization creation flag Introduce HV_PARTITION_CREATION_FLAG_NESTED_VIRTUALIZATION_CAPABLE to indicate support for nested virtualization during partition creation. This enables clearer configuration and capability checks for nested virtualization scenarios. Signed-off-by: Stanislav Kinsburskii Signed-off-by: Muminul Islam Signed-off-by: Wei Liu --- drivers/hv/mshv_root_main.c | 2 ++ include/hyperv/hvhdk.h | 1 + include/uapi/linux/mshv.h | 1 + 3 files changed, 4 insertions(+) (limited to 'include/uapi/linux') diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c index e5d94398528e..e490f8e5a8a5 100644 --- a/drivers/hv/mshv_root_main.c +++ b/drivers/hv/mshv_root_main.c @@ -1947,6 +1947,8 @@ static long mshv_ioctl_process_pt_flags(void __user *user_arg, u64 *pt_flags, *pt_flags |= HV_PARTITION_CREATION_FLAG_X2APIC_CAPABLE; if (args.pt_flags & BIT_ULL(MSHV_PT_BIT_GPA_SUPER_PAGES)) *pt_flags |= HV_PARTITION_CREATION_FLAG_GPA_SUPER_PAGES_ENABLED; + if (args.pt_flags & BIT(MSHV_PT_BIT_NESTED_VIRTUALIZATION)) + *pt_flags |= HV_PARTITION_CREATION_FLAG_NESTED_VIRTUALIZATION_CAPABLE; isol_props->as_uint64 = 0; diff --git a/include/hyperv/hvhdk.h b/include/hyperv/hvhdk.h index 79d1f16a850a..f139c7c5bb2d 100644 --- a/include/hyperv/hvhdk.h +++ b/include/hyperv/hvhdk.h @@ -335,6 +335,7 @@ union hv_partition_isolation_properties { #define HV_PARTITION_ISOLATION_HOST_TYPE_RESERVED 0x2 /* Note: Exo partition is enabled by default */ +#define HV_PARTITION_CREATION_FLAG_NESTED_VIRTUALIZATION_CAPABLE BIT(1) #define HV_PARTITION_CREATION_FLAG_GPA_SUPER_PAGES_ENABLED BIT(4) #define HV_PARTITION_CREATION_FLAG_EXO_PARTITION BIT(8) #define HV_PARTITION_CREATION_FLAG_LAPIC_ENABLED BIT(13) diff --git a/include/uapi/linux/mshv.h b/include/uapi/linux/mshv.h index dee3ece28ce5..7ef5dd67a232 100644 --- a/include/uapi/linux/mshv.h +++ b/include/uapi/linux/mshv.h @@ -27,6 +27,7 @@ enum { MSHV_PT_BIT_X2APIC, MSHV_PT_BIT_GPA_SUPER_PAGES, MSHV_PT_BIT_CPU_AND_XSAVE_FEATURES, + MSHV_PT_BIT_NESTED_VIRTUALIZATION, MSHV_PT_BIT_COUNT, }; -- cgit v1.2.3 From 8927a108a7662eb83eb667bc0c5a0633397122b1 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Wed, 18 Feb 2026 14:48:02 +0000 Subject: mshv: Add SMT_ENABLED_GUEST partition creation flag Add support for HV_PARTITION_CREATION_FLAG_SMT_ENABLED_GUEST to allow userspace VMMs to enable SMT for guest partitions. Expose this via new MSHV_PT_BIT_SMT_ENABLED_GUEST flag in the UAPI. Without this flag, the hypervisor schedules guest VPs incorrectly, causing SMT unusable. Signed-off-by: Anatol Belski Signed-off-by: Wei Liu --- drivers/hv/mshv_root_main.c | 2 ++ include/hyperv/hvhdk.h | 1 + include/uapi/linux/mshv.h | 1 + 3 files changed, 4 insertions(+) (limited to 'include/uapi/linux') diff --git a/drivers/hv/mshv_root_main.c b/drivers/hv/mshv_root_main.c index e490f8e5a8a5..192467a25f66 100644 --- a/drivers/hv/mshv_root_main.c +++ b/drivers/hv/mshv_root_main.c @@ -1949,6 +1949,8 @@ static long mshv_ioctl_process_pt_flags(void __user *user_arg, u64 *pt_flags, *pt_flags |= HV_PARTITION_CREATION_FLAG_GPA_SUPER_PAGES_ENABLED; if (args.pt_flags & BIT(MSHV_PT_BIT_NESTED_VIRTUALIZATION)) *pt_flags |= HV_PARTITION_CREATION_FLAG_NESTED_VIRTUALIZATION_CAPABLE; + if (args.pt_flags & BIT(MSHV_PT_BIT_SMT_ENABLED_GUEST)) + *pt_flags |= HV_PARTITION_CREATION_FLAG_SMT_ENABLED_GUEST; isol_props->as_uint64 = 0; diff --git a/include/hyperv/hvhdk.h b/include/hyperv/hvhdk.h index f139c7c5bb2d..245f3db53bf1 100644 --- a/include/hyperv/hvhdk.h +++ b/include/hyperv/hvhdk.h @@ -335,6 +335,7 @@ union hv_partition_isolation_properties { #define HV_PARTITION_ISOLATION_HOST_TYPE_RESERVED 0x2 /* Note: Exo partition is enabled by default */ +#define HV_PARTITION_CREATION_FLAG_SMT_ENABLED_GUEST BIT(0) #define HV_PARTITION_CREATION_FLAG_NESTED_VIRTUALIZATION_CAPABLE BIT(1) #define HV_PARTITION_CREATION_FLAG_GPA_SUPER_PAGES_ENABLED BIT(4) #define HV_PARTITION_CREATION_FLAG_EXO_PARTITION BIT(8) diff --git a/include/uapi/linux/mshv.h b/include/uapi/linux/mshv.h index 7ef5dd67a232..e0645a34b55b 100644 --- a/include/uapi/linux/mshv.h +++ b/include/uapi/linux/mshv.h @@ -28,6 +28,7 @@ enum { MSHV_PT_BIT_GPA_SUPER_PAGES, MSHV_PT_BIT_CPU_AND_XSAVE_FEATURES, MSHV_PT_BIT_NESTED_VIRTUALIZATION, + MSHV_PT_BIT_SMT_ENABLED_GUEST, MSHV_PT_BIT_COUNT, }; -- cgit v1.2.3 From 3b68df978133ac3d46d570af065a73debbb68248 Mon Sep 17 00:00:00 2001 From: Mathieu Desnoyers Date: Fri, 20 Feb 2026 15:06:41 -0500 Subject: rseq: slice ext: Ensure rseq feature size differs from original rseq size Before rseq became extensible, its original size was 32 bytes even though the active rseq area was only 20 bytes. This had the following impact in terms of userspace ecosystem evolution: * The GNU libc between 2.35 and 2.39 expose a __rseq_size symbol set to 32, even though the size of the active rseq area is really 20. * The GNU libc 2.40 changes this __rseq_size to 20, thus making it express the active rseq area. * Starting from glibc 2.41, __rseq_size corresponds to the AT_RSEQ_FEATURE_SIZE from getauxval(3). This means that users of __rseq_size can always expect it to correspond to the active rseq area, except for the value 32, for which the active rseq area is 20 bytes. Exposing a 32 bytes feature size would make life needlessly painful for userspace. Therefore, add a reserved field at the end of the rseq area to bump the feature size to 33 bytes. This reserved field is expected to be replaced with whatever field will come next, expecting that this field will be larger than 1 byte. The effect of this change is to increase the size from 32 to 64 bytes before we actually have fields using that memory. Clarify the allocation size and alignment requirements in the struct rseq uapi comment. Change the value returned by getauxval(AT_RSEQ_ALIGN) to return the value of the active rseq area size rounded up to next power of 2, which guarantees that the rseq structure will always be aligned on the nearest power of two large enough to contain it, even as it grows. Change the alignment check in the rseq registration accordingly. This will minimize the amount of ABI corner-cases we need to document and require userspace to play games with. The rule stays simple when __rseq_size != 32: #define rseq_field_available(field) (__rseq_size >= offsetofend(struct rseq_abi, field)) Signed-off-by: Mathieu Desnoyers Signed-off-by: Peter Zijlstra (Intel) Link: https://patch.msgid.link/20260220200642.1317826-3-mathieu.desnoyers@efficios.com --- fs/binfmt_elf.c | 3 ++- include/linux/rseq.h | 12 ++++++++++++ include/uapi/linux/rseq.h | 26 ++++++++++++++++++++++---- kernel/rseq.c | 3 ++- 4 files changed, 38 insertions(+), 6 deletions(-) (limited to 'include/uapi/linux') diff --git a/fs/binfmt_elf.c b/fs/binfmt_elf.c index 8e89cc5b2820..fb857faaf0d6 100644 --- a/fs/binfmt_elf.c +++ b/fs/binfmt_elf.c @@ -47,6 +47,7 @@ #include #include #include +#include #include #include @@ -286,7 +287,7 @@ create_elf_tables(struct linux_binprm *bprm, const struct elfhdr *exec, } #ifdef CONFIG_RSEQ NEW_AUX_ENT(AT_RSEQ_FEATURE_SIZE, offsetof(struct rseq, end)); - NEW_AUX_ENT(AT_RSEQ_ALIGN, __alignof__(struct rseq)); + NEW_AUX_ENT(AT_RSEQ_ALIGN, rseq_alloc_align()); #endif #undef NEW_AUX_ENT /* AT_NULL is zero; clear the rest too */ diff --git a/include/linux/rseq.h b/include/linux/rseq.h index 7a01a0760405..b9d62fc2140d 100644 --- a/include/linux/rseq.h +++ b/include/linux/rseq.h @@ -146,6 +146,18 @@ static inline void rseq_fork(struct task_struct *t, u64 clone_flags) t->rseq = current->rseq; } +/* + * Value returned by getauxval(AT_RSEQ_ALIGN) and expected by rseq + * registration. This is the active rseq area size rounded up to next + * power of 2, which guarantees that the rseq structure will always be + * aligned on the nearest power of two large enough to contain it, even + * as it grows. + */ +static inline unsigned int rseq_alloc_align(void) +{ + return 1U << get_count_order(offsetof(struct rseq, end)); +} + #else /* CONFIG_RSEQ */ static inline void rseq_handle_slowpath(struct pt_regs *regs) { } static inline void rseq_signal_deliver(struct ksignal *ksig, struct pt_regs *regs) { } diff --git a/include/uapi/linux/rseq.h b/include/uapi/linux/rseq.h index 863c4a00a66b..f69344fe6c08 100644 --- a/include/uapi/linux/rseq.h +++ b/include/uapi/linux/rseq.h @@ -87,10 +87,17 @@ struct rseq_slice_ctrl { }; /* - * struct rseq is aligned on 4 * 8 bytes to ensure it is always - * contained within a single cache-line. + * The original size and alignment of the allocation for struct rseq is + * 32 bytes. * - * A single struct rseq per thread is allowed. + * The allocation size needs to be greater or equal to + * max(getauxval(AT_RSEQ_FEATURE_SIZE), 32), and the allocation needs to + * be aligned on max(getauxval(AT_RSEQ_ALIGN), 32). + * + * As an alternative, userspace is allowed to use both the original size + * and alignment of 32 bytes for backward compatibility. + * + * A single active struct rseq registration per thread is allowed. */ struct rseq { /* @@ -180,10 +187,21 @@ struct rseq { */ struct rseq_slice_ctrl slice_ctrl; + /* + * Before rseq became extensible, its original size was 32 bytes even + * though the active rseq area was only 20 bytes. + * Exposing a 32 bytes feature size would make life needlessly painful + * for userspace. Therefore, add a reserved byte after byte 32 + * to bump the rseq feature size from 32 to 33. + * The next field to be added to the rseq area will be larger + * than one byte, and will replace this reserved byte. + */ + __u8 __reserved; + /* * Flexible array member at end of structure, after last feature field. */ char end[]; -} __attribute__((aligned(4 * sizeof(__u64)))); +} __attribute__((aligned(32))); #endif /* _UAPI_LINUX_RSEQ_H */ diff --git a/kernel/rseq.c b/kernel/rseq.c index e349f86cc945..38d3ef540760 100644 --- a/kernel/rseq.c +++ b/kernel/rseq.c @@ -80,6 +80,7 @@ #include #include #include +#include #include #define CREATE_TRACE_POINTS @@ -456,7 +457,7 @@ SYSCALL_DEFINE4(rseq, struct rseq __user *, rseq, u32, rseq_len, int, flags, u32 */ if (rseq_len < ORIG_RSEQ_SIZE || (rseq_len == ORIG_RSEQ_SIZE && !IS_ALIGNED((unsigned long)rseq, ORIG_RSEQ_SIZE)) || - (rseq_len != ORIG_RSEQ_SIZE && (!IS_ALIGNED((unsigned long)rseq, __alignof__(*rseq)) || + (rseq_len != ORIG_RSEQ_SIZE && (!IS_ALIGNED((unsigned long)rseq, rseq_alloc_align()) || rseq_len < offsetof(struct rseq, end)))) return -EINVAL; if (!access_ok(rseq, rseq_len)) -- cgit v1.2.3 From 39195990e4c093c9eecf88f29811c6de29265214 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Fri, 27 Feb 2026 06:10:08 -0600 Subject: PCI: Correct PCI_CAP_EXP_ENDPOINT_SIZEOF_V2 value MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fb82437fdd8c ("PCI: Change capability register offsets to hex") incorrectly converted the PCI_CAP_EXP_ENDPOINT_SIZEOF_V2 value from decimal 52 to hex 0x32: -#define PCI_CAP_EXP_ENDPOINT_SIZEOF_V2 52 /* v2 endpoints with link end here */ +#define PCI_CAP_EXP_ENDPOINT_SIZEOF_V2 0x32 /* end of v2 EPs w/ link */ This broke PCI capabilities in a VMM because subsequent ones weren't DWORD-aligned. Change PCI_CAP_EXP_ENDPOINT_SIZEOF_V2 to the correct value of 0x34. fb82437fdd8c was from Baruch Siach , but this was not Baruch's fault; it's a mistake I made when applying the patch. Fixes: fb82437fdd8c ("PCI: Change capability register offsets to hex") Reported-by: David Woodhouse Closes: https://lore.kernel.org/all/3ae392a0158e9d9ab09a1d42150429dd8ca42791.camel@infradead.org Signed-off-by: Bjorn Helgaas Reviewed-by: Krzysztof Wilczyński --- include/uapi/linux/pci_regs.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/pci_regs.h b/include/uapi/linux/pci_regs.h index ec1c54b5a310..14f634ab9350 100644 --- a/include/uapi/linux/pci_regs.h +++ b/include/uapi/linux/pci_regs.h @@ -712,7 +712,7 @@ #define PCI_EXP_LNKCTL2_HASD 0x0020 /* HW Autonomous Speed Disable */ #define PCI_EXP_LNKSTA2 0x32 /* Link Status 2 */ #define PCI_EXP_LNKSTA2_FLIT 0x0400 /* Flit Mode Status */ -#define PCI_CAP_EXP_ENDPOINT_SIZEOF_V2 0x32 /* end of v2 EPs w/ link */ +#define PCI_CAP_EXP_ENDPOINT_SIZEOF_V2 0x34 /* end of v2 EPs w/ link */ #define PCI_EXP_SLTCAP2 0x34 /* Slot Capabilities 2 */ #define PCI_EXP_SLTCAP2_IBPD 0x00000001 /* In-band PD Disable Supported */ #define PCI_EXP_SLTCTL2 0x38 /* Slot Control 2 */ -- cgit v1.2.3 From 0ed2e8bf61d6d5df1d78f4e24b682dff4c394e17 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Sat, 28 Feb 2026 04:56:20 -0700 Subject: io_uring: correct comment for IORING_SETUP_TASKRUN_FLAG Sync with a recent liburing fix, which corrects the comment explaining when the IORING_SETUP_TASKRUN_FLAG setup flag is valid to use. May be use with COOP_TASKRUN or DEFER_TASKRUN, not useful without either of this task_work mechanisms being used. Link: https://github.com/axboe/liburing/pull/1543 Signed-off-by: Jens Axboe --- include/uapi/linux/io_uring.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/io_uring.h b/include/uapi/linux/io_uring.h index 6750c383a2ab..1ff16141c8a5 100644 --- a/include/uapi/linux/io_uring.h +++ b/include/uapi/linux/io_uring.h @@ -188,7 +188,8 @@ enum io_uring_sqe_flags_bit { /* * If COOP_TASKRUN is set, get notified if task work is available for * running and a kernel transition would be needed to run it. This sets - * IORING_SQ_TASKRUN in the sq ring flags. Not valid with COOP_TASKRUN. + * IORING_SQ_TASKRUN in the sq ring flags. Not valid without COOP_TASKRUN + * or DEFER_TASKRUN. */ #define IORING_SETUP_TASKRUN_FLAG (1U << 9) #define IORING_SETUP_SQE128 (1U << 10) /* SQEs are 128 byte */ -- cgit v1.2.3 From a116bac87118903925108e57781bbfc7a7eea27b Mon Sep 17 00:00:00 2001 From: "Isaac J. Manjarres" Date: Mon, 2 Mar 2026 16:23:09 -0800 Subject: dma-buf: Include ioctl.h in UAPI header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit include/uapi/linux/dma-buf.h uses several macros from ioctl.h to define its ioctl commands. However, it does not include ioctl.h itself. So, if userspace source code tries to include the dma-buf.h file without including ioctl.h, it can result in build failures. Therefore, include ioctl.h in the dma-buf UAPI header. Signed-off-by: Isaac J. Manjarres Reviewed-by: T.J. Mercier Reviewed-by: Christian König Signed-off-by: Christian König Link: https://lore.kernel.org/r/20260303002309.1401849-1-isaacmanjarres@google.com --- include/uapi/linux/dma-buf.h | 1 + 1 file changed, 1 insertion(+) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/dma-buf.h b/include/uapi/linux/dma-buf.h index 5a6fda66d9ad..e827c9d20c5d 100644 --- a/include/uapi/linux/dma-buf.h +++ b/include/uapi/linux/dma-buf.h @@ -20,6 +20,7 @@ #ifndef _DMA_BUF_UAPI_H_ #define _DMA_BUF_UAPI_H_ +#include #include /** -- cgit v1.2.3