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 5f20bc206beb902e32b77216cb7935b46ca00b0a Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Thu, 23 Oct 2025 12:46:14 -0700 Subject: platform/x86: ISST: isst_if.h: fix all kernel-doc warnings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix all kernel-doc warnings in : - don't use "[]" in the variable name in kernel-doc - add a few missing entries - change "power_domain" to "power_domain_id" in kernel-doc to match the struct member name - add a leading '@' on a few existing kernel-doc lines - use '_' instead of '-' in struct member names Examples (but not all 27 warnings): Warning: include/uapi/linux/isst_if.h:63 struct member 'cpu_map' not described in 'isst_if_cpu_maps' Warning: ../include/uapi/linux/isst_if.h:95 struct member 'req_count' not described in 'isst_if_io_regs' Warning: include/uapi/linux/isst_if.h:132 struct member 'mbox_cmd' not described in 'isst_if_mbox_cmds' Warning: ../include/uapi/linux/isst_if.h:183 struct member 'supported' not described in 'isst_core_power' Warning: ../include/uapi/linux/isst_if.h:206 struct member 'power_domain_id' not described in 'isst_clos_param' Warning: ../include/uapi/linux/isst_if.h:239 struct member 'assoc_info' not described in 'isst_if_clos_assoc_cmds' Warning: ../include/uapi/linux/isst_if.h:286 struct member 'sst_tf_support' not described in 'isst_perf_level_info' Warning: ../include/uapi/linux/isst_if.h:375 struct member 'trl_freq_mhz' not described in 'isst_perf_level_data_info' Warning: ../include/uapi/linux/isst_if.h:475 struct member 'max_buckets' not described in 'isst_turbo_freq_info' Signed-off-by: Randy Dunlap Link: https://patch.msgid.link/20251023194615.180824-1-rdunlap@infradead.org Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- include/uapi/linux/isst_if.h | 50 ++++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 23 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/isst_if.h b/include/uapi/linux/isst_if.h index 8197a4800604..40aa545101a3 100644 --- a/include/uapi/linux/isst_if.h +++ b/include/uapi/linux/isst_if.h @@ -52,7 +52,7 @@ struct isst_if_cpu_map { /** * struct isst_if_cpu_maps - structure for CPU map IOCTL * @cmd_count: Number of CPU mapping command in cpu_map[] - * @cpu_map[]: Holds one or more CPU map data structure + * @cpu_map: Holds one or more CPU map data structure * * This structure used with ioctl ISST_IF_GET_PHY_ID to send * one or more CPU mapping commands. Here IOCTL return value indicates @@ -82,8 +82,8 @@ struct isst_if_io_reg { /** * struct isst_if_io_regs - structure for IO register commands - * @cmd_count: Number of io reg commands in io_reg[] - * @io_reg[]: Holds one or more io_reg command structure + * @req_count: Number of io reg commands in io_reg[] + * @io_reg: Holds one or more io_reg command structure * * This structure used with ioctl ISST_IF_IO_CMD to send * one or more read/write commands to PUNIT. Here IOCTL return value @@ -120,7 +120,7 @@ struct isst_if_mbox_cmd { /** * struct isst_if_mbox_cmds - structure for mailbox commands * @cmd_count: Number of mailbox commands in mbox_cmd[] - * @mbox_cmd[]: Holds one or more mbox commands + * @mbox_cmd: Holds one or more mbox commands * * This structure used with ioctl ISST_IF_MBOX_COMMAND to send * one or more mailbox commands to PUNIT. Here IOCTL return value @@ -152,7 +152,7 @@ struct isst_if_msr_cmd { /** * struct isst_if_msr_cmds - structure for msr commands * @cmd_count: Number of mailbox commands in msr_cmd[] - * @msr_cmd[]: Holds one or more msr commands + * @msr_cmd: Holds one or more msr commands * * This structure used with ioctl ISST_IF_MSR_COMMAND to send * one or more MSR commands. IOCTL return value indicates number of @@ -167,8 +167,9 @@ struct isst_if_msr_cmds { * struct isst_core_power - Structure to get/set core_power feature * @get_set: 0: Get, 1: Set * @socket_id: Socket/package id - * @power_domain: Power Domain id + * @power_domain_id: Power Domain id * @enable: Feature enable status + * @supported: Power domain supports SST_CP interface * @priority_type: Priority type for the feature (ordered/proportional) * * Structure to get/set core_power feature state using IOCTL @@ -187,11 +188,11 @@ struct isst_core_power { * struct isst_clos_param - Structure to get/set clos praram * @get_set: 0: Get, 1: Set * @socket_id: Socket/package id - * @power_domain: Power Domain id - * clos: Clos ID for the parameters - * min_freq_mhz: Minimum frequency in MHz - * max_freq_mhz: Maximum frequency in MHz - * prop_prio: Proportional priority from 0-15 + * @power_domain_id: Power Domain id + * @clos: Clos ID for the parameters + * @min_freq_mhz: Minimum frequency in MHz + * @max_freq_mhz: Maximum frequency in MHz + * @prop_prio: Proportional priority from 0-15 * * Structure to get/set per clos property using IOCTL * ISST_IF_CLOS_PARAM. @@ -209,7 +210,7 @@ struct isst_clos_param { /** * struct isst_if_clos_assoc - Structure to assign clos to a CPU * @socket_id: Socket/package id - * @power_domain: Power Domain id + * @power_domain_id: Power Domain id * @logical_cpu: CPU number * @clos: Clos ID to assign to the logical CPU * @@ -228,6 +229,7 @@ struct isst_if_clos_assoc { * @get_set: Request is for get or set * @punit_cpu_map: Set to 1 if the CPU number is punit numbering not * Linux CPU number + * @assoc_info: CLOS data for this CPU * * Structure used to get/set associate CPUs to clos using IOCTL * ISST_IF_CLOS_ASSOC. @@ -257,7 +259,7 @@ struct isst_tpmi_instance_count { /** * struct isst_perf_level_info - Structure to get information on SST-PP levels * @socket_id: Socket/package id - * @power_domain: Power Domain id + * @power_domain_id: Power Domain id * @logical_cpu: CPU number * @clos: Clos ID to assign to the logical CPU * @max_level: Maximum performance level supported by the platform @@ -267,8 +269,8 @@ struct isst_tpmi_instance_count { * @feature_state: SST-BF and SST-TF (enabled/disabled) status at current level * @locked: SST-PP performance level change is locked/unlocked * @enabled: SST-PP feature is enabled or not - * @sst-tf_support: SST-TF support status at this level - * @sst-bf_support: SST-BF support status at this level + * @sst_tf_support: SST-TF support status at this level + * @sst_bf_support: SST-BF support status at this level * * Structure to get SST-PP details using IOCTL ISST_IF_PERF_LEVELS. */ @@ -289,7 +291,7 @@ struct isst_perf_level_info { /** * struct isst_perf_level_control - Structure to set SST-PP level * @socket_id: Socket/package id - * @power_domain: Power Domain id + * @power_domain_id: Power Domain id * @level: level to set * * Structure used change SST-PP level using IOCTL ISST_IF_PERF_SET_LEVEL. @@ -303,7 +305,7 @@ struct isst_perf_level_control { /** * struct isst_perf_feature_control - Structure to activate SST-BF/SST-TF * @socket_id: Socket/package id - * @power_domain: Power Domain id + * @power_domain_id: Power Domain id * @feature: bit 0 = SST-BF state, bit 1 = SST-TF state * * Structure used to enable SST-BF/SST-TF using IOCTL ISST_IF_PERF_SET_FEATURE. @@ -320,7 +322,7 @@ struct isst_perf_feature_control { /** * struct isst_perf_level_data_info - Structure to get SST-PP level details * @socket_id: Socket/package id - * @power_domain: Power Domain id + * @power_domain_id: Power Domain id * @level: SST-PP level for which caller wants to get information * @tdp_ratio: TDP Ratio * @base_freq_mhz: Base frequency in MHz @@ -341,8 +343,8 @@ struct isst_perf_feature_control { * @pm_fabric_freq_mhz: Fabric (Uncore) minimum frequency * @max_buckets: Maximum trl buckets * @max_trl_levels: Maximum trl levels - * @bucket_core_counts[TRL_MAX_BUCKETS]: Number of cores per bucket - * @trl_freq_mhz[TRL_MAX_LEVELS][TRL_MAX_BUCKETS]: maximum frequency + * @bucket_core_counts: Number of cores per bucket + * @trl_freq_mhz: maximum frequency * for a bucket and trl level * * Structure used to get information on frequencies and TDP for a SST-PP @@ -402,7 +404,7 @@ struct isst_perf_level_fabric_info { /** * struct isst_perf_level_cpu_mask - Structure to get SST-PP level CPU mask * @socket_id: Socket/package id - * @power_domain: Power Domain id + * @power_domain_id: Power Domain id * @level: SST-PP level for which caller wants to get information * @punit_cpu_map: Set to 1 if the CPU number is punit numbering not * Linux CPU number. If 0 CPU buffer is copied to user space @@ -430,7 +432,7 @@ struct isst_perf_level_cpu_mask { /** * struct isst_base_freq_info - Structure to get SST-BF frequencies * @socket_id: Socket/package id - * @power_domain: Power Domain id + * @power_domain_id: Power Domain id * @level: SST-PP level for which caller wants to get information * @high_base_freq_mhz: High priority CPU base frequency * @low_base_freq_mhz: Low priority CPU base frequency @@ -453,9 +455,11 @@ struct isst_base_freq_info { /** * struct isst_turbo_freq_info - Structure to get SST-TF frequencies * @socket_id: Socket/package id - * @power_domain: Power Domain id + * @power_domain_id: Power Domain id * @level: SST-PP level for which caller wants to get information * @max_clip_freqs: Maximum number of low priority core clipping frequencies + * @max_buckets: Maximum trl buckets + * @max_trl_levels: Maximum trl levels * @lp_clip_freq_mhz: Clip frequencies per trl level * @bucket_core_counts: Maximum number of cores for a bucket * @trl_freq_mhz: Frequencies per trl level for each bucket -- 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 aaf46c6a6df6052881c2e75cba65aeb6f1cfa88a Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Thu, 23 Oct 2025 22:21:02 -0700 Subject: tee: : - add ending ':' to some struct members as needed for kernel-doc - change struct name in kernel-doc to match the actual struct name (2x) - add a @params: kernel-doc entry multiple times Warning: tee.h:265 struct member 'ret_origin' not described in 'tee_ioctl_open_session_arg' Warning: tee.h:265 struct member 'num_params' not described in 'tee_ioctl_open_session_arg' Warning: tee.h:265 struct member 'params' not described in 'tee_ioctl_open_session_arg' Warning: tee.h:351 struct member 'num_params' not described in 'tee_iocl_supp_recv_arg' Warning: tee.h:351 struct member 'params' not described in 'tee_iocl_supp_recv_arg' Warning: tee.h:372 struct member 'num_params' not described in 'tee_iocl_supp_send_arg' Warning: tee.h:372 struct member 'params' not described in 'tee_iocl_supp_send_arg' Warning: tee.h:298: expecting prototype for struct tee_ioctl_invoke_func_arg. Prototype was for struct tee_ioctl_invoke_arg instead Warning: tee.h:473: expecting prototype for struct tee_ioctl_invoke_func_arg. Prototype was for struct tee_ioctl_object_invoke_arg instead Signed-off-by: Randy Dunlap Reviewed-by: Sumit Garg Signed-off-by: Jens Wiklander --- include/uapi/linux/tee.h | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/tee.h b/include/uapi/linux/tee.h index 386ad36f1a0a..cab5cadca8ef 100644 --- a/include/uapi/linux/tee.h +++ b/include/uapi/linux/tee.h @@ -249,8 +249,9 @@ struct tee_ioctl_param { * @cancel_id: [in] Cancellation id, a unique value to identify this request * @session: [out] Session id * @ret: [out] return value - * @ret_origin [out] origin of the return value - * @num_params [in] number of parameters following this struct + * @ret_origin: [out] origin of the return value + * @num_params: [in] number of &struct tee_ioctl_param entries in @params + * @params: array of ioctl parameters */ struct tee_ioctl_open_session_arg { __u8 uuid[TEE_IOCTL_UUID_LEN]; @@ -276,14 +277,14 @@ struct tee_ioctl_open_session_arg { struct tee_ioctl_buf_data) /** - * struct tee_ioctl_invoke_func_arg - Invokes a function in a Trusted - * Application + * struct tee_ioctl_invoke_arg - Invokes a function in a Trusted Application * @func: [in] Trusted Application function, specific to the TA * @session: [in] Session id * @cancel_id: [in] Cancellation id, a unique value to identify this request * @ret: [out] return value - * @ret_origin [out] origin of the return value - * @num_params [in] number of parameters following this struct + * @ret_origin: [out] origin of the return value + * @num_params: [in] number of parameters following this struct + * @params: array of ioctl parameters */ struct tee_ioctl_invoke_arg { __u32 func; @@ -338,7 +339,8 @@ struct tee_ioctl_close_session_arg { /** * struct tee_iocl_supp_recv_arg - Receive a request for a supplicant function * @func: [in] supplicant function - * @num_params [in/out] number of parameters following this struct + * @num_params: [in/out] number of &struct tee_ioctl_param entries in @params + * @params: array of ioctl parameters * * @num_params is the number of params that tee-supplicant has room to * receive when input, @num_params is the number of actual params @@ -363,7 +365,8 @@ struct tee_iocl_supp_recv_arg { /** * struct tee_iocl_supp_send_arg - Send a response to a received request * @ret: [out] return value - * @num_params [in] number of parameters following this struct + * @num_params: [in] number of &struct tee_ioctl_param entries in @params + * @params: array of ioctl parameters */ struct tee_iocl_supp_send_arg { __u32 ret; @@ -454,11 +457,13 @@ struct tee_ioctl_shm_register_fd_data { */ /** - * struct tee_ioctl_invoke_func_arg - Invokes an object in a Trusted Application + * struct tee_ioctl_object_invoke_arg - Invokes an object in a + * Trusted Application * @id: [in] Object id * @op: [in] Object operation, specific to the object * @ret: [out] return value * @num_params: [in] number of parameters following this struct + * @params: array of ioctl parameters */ struct tee_ioctl_object_invoke_arg { __u64 id; -- 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 78f0e33cd6c939a555aa80dbed2fec6b333a7660 Mon Sep 17 00:00:00 2001 From: Andrei Vagin Date: Tue, 11 Nov 2025 06:28:15 +0000 Subject: fs/namespace: correctly handle errors returned by grab_requested_mnt_ns grab_requested_mnt_ns was changed to return error codes on failure, but its callers were not updated to check for error pointers, still checking only for a NULL return value. This commit updates the callers to use IS_ERR() or IS_ERR_OR_NULL() and PTR_ERR() to correctly check for and propagate errors. This also makes sure that the logic actually works and mount namespace file descriptors can be used to refere to mounts. Christian Brauner says: Rework the patch to be more ergonomic and in line with our overall error handling patterns. Fixes: 7b9d14af8777 ("fs: allow mount namespace fd") Cc: Christian Brauner Signed-off-by: Andrei Vagin Link: https://patch.msgid.link/20251111062815.2546189-1-avagin@google.com Reviewed-by: Jan Kara Signed-off-by: Christian Brauner --- fs/namespace.c | 32 ++++++++++++++++---------------- include/uapi/linux/mount.h | 2 +- 2 files changed, 17 insertions(+), 17 deletions(-) (limited to 'include/uapi/linux') diff --git a/fs/namespace.c b/fs/namespace.c index cc6e00e72437..2bad25709b2c 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -141,7 +141,8 @@ static void mnt_ns_release(struct mnt_namespace *ns) kfree(ns); } } -DEFINE_FREE(mnt_ns_release, struct mnt_namespace *, if (_T) mnt_ns_release(_T)) +DEFINE_FREE(mnt_ns_release, struct mnt_namespace *, + if (!IS_ERR(_T)) mnt_ns_release(_T)) static void mnt_ns_release_rcu(struct rcu_head *rcu) { @@ -5726,7 +5727,7 @@ 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->spare != 0) + 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) @@ -5743,16 +5744,12 @@ static struct mnt_namespace *grab_requested_mnt_ns(const struct mnt_id_req *kreq { struct mnt_namespace *mnt_ns; - if (kreq->mnt_ns_id && kreq->spare) - return ERR_PTR(-EINVAL); - - if (kreq->mnt_ns_id) - return lookup_mnt_ns(kreq->mnt_ns_id); - - if (kreq->spare) { + if (kreq->mnt_ns_id) { + mnt_ns = lookup_mnt_ns(kreq->mnt_ns_id); + } else if (kreq->mnt_ns_fd) { struct ns_common *ns; - CLASS(fd, f)(kreq->spare); + CLASS(fd, f)(kreq->mnt_ns_fd); if (fd_empty(f)) return ERR_PTR(-EBADF); @@ -5767,6 +5764,8 @@ static struct mnt_namespace *grab_requested_mnt_ns(const struct mnt_id_req *kreq } else { mnt_ns = current->nsproxy->mnt_ns; } + if (!mnt_ns) + return ERR_PTR(-ENOENT); refcount_inc(&mnt_ns->passive); return mnt_ns; @@ -5791,8 +5790,8 @@ SYSCALL_DEFINE4(statmount, const struct mnt_id_req __user *, req, return ret; ns = grab_requested_mnt_ns(&kreq); - if (!ns) - return -ENOENT; + 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)) @@ -5902,8 +5901,8 @@ static void __free_klistmount_free(const struct klistmount *kls) static inline int prepare_klistmount(struct klistmount *kls, struct mnt_id_req *kreq, size_t nr_mnt_ids) { - u64 last_mnt_id = kreq->param; + struct mnt_namespace *ns; /* The first valid unique mount id is MNT_UNIQUE_ID_OFFSET + 1. */ if (last_mnt_id != 0 && last_mnt_id <= MNT_UNIQUE_ID_OFFSET) @@ -5917,9 +5916,10 @@ static inline int prepare_klistmount(struct klistmount *kls, struct mnt_id_req * if (!kls->kmnt_ids) return -ENOMEM; - kls->ns = grab_requested_mnt_ns(kreq); - if (!kls->ns) - return -ENOENT; + ns = grab_requested_mnt_ns(kreq); + if (IS_ERR(ns)) + return PTR_ERR(ns); + kls->ns = ns; kls->mnt_parent_id = kreq->mnt_id; return 0; diff --git a/include/uapi/linux/mount.h b/include/uapi/linux/mount.h index 7fa67c2031a5..5d3f8c9e3a62 100644 --- a/include/uapi/linux/mount.h +++ b/include/uapi/linux/mount.h @@ -197,7 +197,7 @@ struct statmount { */ struct mnt_id_req { __u32 size; - __u32 spare; + __u32 mnt_ns_fd; __u64 mnt_id; __u64 param; __u64 mnt_ns_id; -- 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 ae8966b7b5bd69b86209cc34bcca1ba9f18b68e6 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Thu, 6 Nov 2025 21:45:34 +1000 Subject: Input: rename INPUT_PROP_HAPTIC_TOUCHPAD to INPUT_PROP_PRESSUREPAD And expand it to encompass all pressure pads. Definition: "pressure pad" as used here as includes all touchpads that use physical pressure to convert to click, without physical hinges. Also called haptic touchpads in general parlance, Synaptics calls them ForcePads. Most (all?) pressure pads are currently advertised as INPUT_PROP_BUTTONPAD. The suggestion to identify them as pressure pads by defining the resolution on ABS_MT_PRESSURE has been in the docs since commit 20ccc8dd38a3 ("Documentation: input: define ABS_PRESSURE/ABS_MT_PRESSURE resolution as grams") but few devices provide this information. In userspace it's thus impossible to determine whether a device is a true pressure pad (pressure equals pressure) or a normal clickpad with (pressure equals finger size). Commit 7075ae4ac9db ("Input: add INPUT_PROP_HAPTIC_TOUCHPAD") introduces INPUT_PROP_HAPTIC_TOUCHPAD but restricted it to those touchpads that have support for userspace-controlled effects. Let's expand and rename that definition to include all pressure pad touchpads since those that do support FF effects can be identified by the presence of the FF_HAPTIC bit. This means: - clickpad: INPUT_PROP_BUTTONPAD - pressurepad: INPUT_PROP_BUTTONPAD + INPUT_PROP_PRESSUREPAD - pressurepad with configurable haptics: INPUT_PROP_BUTTONPAD + INPUT_PROP_PRESSUREPAD + FF_HAPTIC Signed-off-by: Peter Hutterer Acked-by: Benjamin Tissoires Link: https://patch.msgid.link/20251106114534.GA405512@tassie Signed-off-by: Dmitry Torokhov --- Documentation/input/event-codes.rst | 25 ++++++++++++++++++------- drivers/hid/hid-haptic.c | 2 +- include/uapi/linux/input-event-codes.h | 2 +- 3 files changed, 20 insertions(+), 9 deletions(-) (limited to 'include/uapi/linux') diff --git a/Documentation/input/event-codes.rst b/Documentation/input/event-codes.rst index 1ead9bb8d9c6..4424cbff251f 100644 --- a/Documentation/input/event-codes.rst +++ b/Documentation/input/event-codes.rst @@ -400,19 +400,30 @@ can report through the rotational axes (absolute and/or relative rx, ry, rz). All other axes retain their meaning. A device must not mix regular directional axes and accelerometer axes on the same event node. -INPUT_PROP_HAPTIC_TOUCHPAD --------------------------- +INPUT_PROP_PRESSUREPAD +---------------------- + +The INPUT_PROP_PRESSUREPAD property indicates that the device provides +simulated haptic feedback (e.g. a vibrator motor situated below the surface) +instead of physical haptic feedback (e.g. a hinge). This property is only set +if the device: -The INPUT_PROP_HAPTIC_TOUCHPAD property indicates that device: -- supports simple haptic auto and manual triggering - can differentiate between at least 5 fingers - uses correct resolution for the X/Y (units and value) -- reports correct force per touch, and correct units for them (newtons or grams) - follows the MT protocol type B +If the simulated haptic feedback is controllable by userspace the device must: + +- support simple haptic auto and manual triggering, and +- report correct force per touch, and correct units for them (newtons or grams), and +- provide the EV_FF FF_HAPTIC force feedback effect. + Summing up, such devices follow the MS spec for input devices in -Win8 and Win8.1, and in addition support the Simple haptic controller HID table, -and report correct units for the pressure. +Win8 and Win8.1, and in addition may support the Simple haptic controller HID +table, and report correct units for the pressure. + +Where applicable, this property is set in addition to INPUT_PROP_BUTTONPAD, it +does not replace that property. Guidelines ========== diff --git a/drivers/hid/hid-haptic.c b/drivers/hid/hid-haptic.c index aa090684c1f2..fc8a9997f815 100644 --- a/drivers/hid/hid-haptic.c +++ b/drivers/hid/hid-haptic.c @@ -86,7 +86,7 @@ int hid_haptic_input_configured(struct hid_device *hdev, if (hi->application == HID_DG_TOUCHPAD) { if (haptic->auto_trigger_report && haptic->manual_trigger_report) { - __set_bit(INPUT_PROP_HAPTIC_TOUCHPAD, hi->input->propbit); + __set_bit(INPUT_PROP_PRESSUREPAD, hi->input->propbit); return 1; } return 0; diff --git a/include/uapi/linux/input-event-codes.h b/include/uapi/linux/input-event-codes.h index 9cd89bcc1d9c..30f3c9eaafaa 100644 --- a/include/uapi/linux/input-event-codes.h +++ b/include/uapi/linux/input-event-codes.h @@ -27,7 +27,7 @@ #define INPUT_PROP_TOPBUTTONPAD 0x04 /* softbuttons at top of pad */ #define INPUT_PROP_POINTING_STICK 0x05 /* is a pointing stick */ #define INPUT_PROP_ACCELEROMETER 0x06 /* has accelerometer */ -#define INPUT_PROP_HAPTIC_TOUCHPAD 0x07 /* is a haptic touchpad */ +#define INPUT_PROP_PRESSUREPAD 0x07 /* pressure triggers clicks */ #define INPUT_PROP_MAX 0x1f #define INPUT_PROP_CNT (INPUT_PROP_MAX + 1) -- 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