[RFC PATCH 13/13] HACK: drivers: ultraeth: add char device

Nikolay Aleksandrov
2025-03-06T23:04:22Z

From: Alex Badea <alex.badea@keysight.com>

Add a character device so we can send and receive packets from
user-space. It also implements a private ioctl for associating with a job.
This patch is just a quick hack to allow using the Ultra Ethernet
software device from user-space until proper user<->kernel APIs are
defined.

Signed-off-by: Alex Badea <alex.badea@keysight.com>
Signed-off-by: Nikolay Aleksandrov <nikolay@enfabrica.net>
---
Documentation/netlink/specs/ultraeth.yaml | 9 +
drivers/ultraeth/Makefile | 2 +-
drivers/ultraeth/uet_chardev.c | 264 ++++++++++++++++++++++
drivers/ultraeth/uet_context.c | 31 ++-
include/net/ultraeth/uet_chardev.h | 11 +
include/net/ultraeth/uet_context.h | 3 +
include/uapi/linux/ultraeth.h | 21 ++
include/uapi/linux/ultraeth_nl.h | 3 +
8 files changed, 342 insertions(+), 2 deletions(-)
create mode 100644 drivers/ultraeth/uet_chardev.c
create mode 100644 include/net/ultraeth/uet_chardev.h

diff --git a/Documentation/netlink/specs/ultraeth.yaml b/Documentation/netlink/specs/ultraeth.yaml
index 847f748efa52..3dc10e52131e 100644
--- a/Documentation/netlink/specs/ultraeth.yaml
+++ b/Documentation/netlink/specs/ultraeth.yaml
@@ -22,6 +22,15 @@ attribute-sets:
-
name: netdev-name
type: string
+ -
+ name: chardev-name
+ type: string
+ -
+ name: chardev-major
+ type: s32
+ -
+ name: chardev-minor
+ type: s32
-
name: contexts
attributes:
diff --git a/drivers/ultraeth/Makefile b/drivers/ultraeth/Makefile
index f2d6a8569dbf..bee8e7aa00bb 100644
--- a/drivers/ultraeth/Makefile
+++ b/drivers/ultraeth/Makefile
@@ -1,4 +1,4 @@
obj-$(CONFIG_ULTRAETH) += ultraeth.o

ultraeth-objs := uet_main.o uet_context.o uet_netlink.o uet_job.o \
- uecon.o uet_pdc.o uet_pds.o
+ uecon.o uet_pdc.o uet_pds.o uet_chardev.o
diff --git a/drivers/ultraeth/uet_chardev.c b/drivers/ultraeth/uet_chardev.c
new file mode 100644
index 000000000000..f02f2c1e1afd
--- /dev/null
+++ b/drivers/ultraeth/uet_chardev.c
@@ -0,0 +1,264 @@
+// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/miscdevice.h>
+#include <linux/netdevice.h>
+
+#include <rdma/ib_umem.h>
+
+#include <uapi/linux/ultraeth.h>
+#include <net/ultraeth/uet_context.h>
+#include <net/ultraeth/uet_chardev.h>
+
+#define MAX_PDS_HDRLEN 64 /* -ish? */
+
+static int uet_char_open(struct inode *inode, struct file *file)
+{
+ struct uet_context *ctx;
+ struct uet_fep *fep;
+ int rv;
+
+ ctx = uet_context_get_by_minor(iminor(inode));
+ if (!ctx)
+ return -ENOENT;
+
+ fep = kzalloc(sizeof(*fep), GFP_KERNEL);
+ if (!fep) {
+ rv = -ENOMEM;
+ goto err_alloc;
+ }
+
+ fep->context = ctx;
+ fep->ack_gen_min_pkt_add = UET_DEFAULT_ACK_GEN_MIN_PKT_ADD;
+ fep->ack_gen_trigger = UET_DEFAULT_ACK_GEN_TRIGGER;
+ skb_queue_head_init(&fep->rxq);
+ file->private_data = fep;
+ rv = nonseekable_open(inode, file);
+ if (rv < 0)
+ goto err_open;
+
+ return rv;
+
+err_open:
+ kfree(fep);
+err_alloc:
+ uet_context_put(ctx);
+
+ return rv;
+}
+
+static int uet_char_release(struct inode *inode, struct file *file)
+{
+ struct uet_fep *fep = file->private_data;
+
+ uet_job_reg_disassociate(&fep->context->job_reg, fep->job_id);
+ skb_queue_purge(&fep->rxq);
+ uet_context_put(fep->context);
+ kfree(fep);
+
+ return 0;
+}
+
+static long uet_char_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct uet_fep *fep = file->private_data;
+ void __user *p = (void __user *)arg;
+ int ret = 0;
+
+ switch (cmd) {
+ case UET_ADDR_REQ: {
+ struct uet_job_addr_req areq;
+
+ if (copy_from_user(&areq, p, sizeof(areq)))
+ return -EFAULT;
+ // XXX: validate address
+
+ areq.service_name[UET_SVC_MAX_LEN - 1] = '\0';
+ memcpy(&fep->addr.in_address, &areq.address,
+ sizeof(fep->addr.in_address));
+
+ ret = uet_job_reg_associate(&fep->context->job_reg, fep,
+ areq.service_name);
+ if (!ret) {
+ if (areq.ack_gen_trigger > 0)
+ fep->ack_gen_trigger = areq.ack_gen_trigger;
+ if (areq.ack_gen_min_pkt_add > 0)
+ fep->ack_gen_min_pkt_add = areq.ack_gen_min_pkt_add;
+ }
+ break;
+ }
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return ret;
+}
+
+static ssize_t uet_char_read(struct file *file, char __user *ubuf,
+ size_t usize, loff_t *off)
+{
+ struct uet_fep *fep = file->private_data;
+ struct uet_prologue_hdr *prologue;
+ struct uet_pds_meta meta = {};
+ struct sk_buff *skb = NULL;
+ int ret = -ENOTCONN;
+ int hdrlen = 0;
+ size_t userlen;
+
+ pr_debug("%s file=%p fep=%p size=%zu\n", __func__, file, fep, usize);
+
+ ret = -EAGAIN;
+ skb = skb_dequeue(&fep->rxq);
+ if (!skb)
+ goto out_err;
+
+ ret = skb_linearize(skb);
+ if (ret)
+ goto out_err;
+
+ prologue = pds_prologue_hdr(skb);
+ meta.next_hdr = uet_prologue_next_hdr(prologue);
+ meta.addr = UET_SKB_CB(skb)->remote_fep_addr;
+ switch (meta.next_hdr) {
+ case UET_PDS_NEXT_HDR_RSP_DATA:
+ case UET_PDS_NEXT_HDR_RSP_DATA_SMALL:
+ /* TODO */
+ ret = -EOPNOTSUPP;
+ goto out_err;
+ case UET_PDS_NEXT_HDR_RSP:
+ hdrlen = sizeof(struct uet_pds_ack_hdr);
+ break;
+ default:
+ hdrlen = sizeof(struct uet_pds_req_hdr);
+ break;
+ }
+ userlen = sizeof(meta) + skb->len - hdrlen;
+ if (userlen > usize) {
+ ret = -EMSGSIZE;
+ goto out_err;
+ }
+
+ if (copy_to_user(ubuf, &meta, sizeof(meta))) {
+ ret = -EFAULT;
+ goto out_err;
+ }
+ if (copy_to_user(ubuf + sizeof(meta), skb->data + hdrlen, skb->len - hdrlen)) {
+ ret = -EFAULT;
+ goto out_err;
+ }
+
+ consume_skb(skb);
+ ret = userlen;
+
+ return ret;
+
+out_err:
+ kfree_skb(skb);
+
+ return ret;
+}
+
+static ssize_t uet_char_write(struct file *file, const char __user *ubuf,
+ size_t usize, loff_t *off)
+{
+ struct uet_fep *fep = file->private_data;
+ struct sk_buff *skb = NULL;
+ struct uet_pds_meta *meta;
+ struct uet_job *job;
+ __be32 daddr, saddr;
+ int ret = -ENODEV;
+ __be16 dport;
+ void *buf;
+
+ pr_debug("%s file=%p fep=%p size=%zu\n", __func__, file, fep, usize);
+
+ rcu_read_lock();
+ job = uet_job_find(&fep->context->job_reg, fep->job_id);
+ if (!job)
+ goto out_err;
+
+ ret = -ENOMEM;
+ skb = alloc_skb(MAX_HEADER + MAX_PDS_HDRLEN + usize, GFP_ATOMIC);
+ if (!skb)
+ goto out_err;
+ skb_reserve(skb, MAX_HEADER + MAX_PDS_HDRLEN);
+ buf = skb_put(skb, usize);
+ ret = -EFAULT;
+ if (copy_from_user(buf, ubuf, usize))
+ goto out_err;
+
+ print_hex_dump_bytes("pds tx ", DUMP_PREFIX_OFFSET, skb->data, skb->len);
+
+ meta = skb_pull_data(skb, sizeof(*meta));
+ if (!meta) {
+ ret = -EINVAL;
+ goto out_err;
+ }
+ /* TODO: IPv6 */
+ /* TODO: per-packet daddr */
+ saddr = fep->addr.in_address.ip;
+ daddr = meta->addr;
+ dport = meta->port;
+
+ switch (meta->next_hdr) {
+ case UET_PDS_NEXT_HDR_RSP_DATA:
+ case UET_PDS_NEXT_HDR_RSP_DATA_SMALL:
+ ret = -EOPNOTSUPP; /* TODO */
+ goto out_err;
+ case UET_PDS_NEXT_HDR_RSP:
+ ret = 0; /* FIXME: ACK PSN would be wrong */
+ break;
+ default:
+ ret = uet_pds_tx(&fep->context->pds, skb, saddr, daddr, dport,
+ job->id);
+ break;
+ }
+
+ if (ret < 0)
+ goto out_err;
+ rcu_read_unlock();
+
+ return usize;
+
+out_err:
+ rcu_read_unlock();
+ kfree_skb(skb);
+
+ return ret;
+}
+
+static const struct file_operations uet_char_ops = {
+ .owner = THIS_MODULE,
+ .open = uet_char_open,
+ .release = uet_char_release,
+ .read = uet_char_read,
+ .write = uet_char_write,
+ .unlocked_ioctl = uet_char_ioctl,
+};
+
+#define UET_CHAR_MAX_NAME 20
+
+int uet_char_init(struct miscdevice *cdev, int id)
+{
+ int ret = -ENOMEM;
+
+ cdev->minor = MISC_DYNAMIC_MINOR;
+ cdev->name = kzalloc(UET_CHAR_MAX_NAME, GFP_KERNEL);
+ if (!cdev->name)
+ return ret;
+ snprintf((char *)cdev->name, UET_CHAR_MAX_NAME, "ultraeth%d", id);
+ cdev->fops = &uet_char_ops;
+
+ ret = misc_register(cdev);
+ if (ret)
+ kfree(cdev->name);
+
+ return ret;
+}
+
+void uet_char_uninit(struct miscdevice *cdev)
+{
+ kfree(cdev->name);
+ misc_deregister(cdev);
+}
diff --git a/drivers/ultraeth/uet_context.c b/drivers/ultraeth/uet_context.c
index 6bdd72344e01..7bddc810503b 100644
--- a/drivers/ultraeth/uet_context.c
+++ b/drivers/ultraeth/uet_context.c
@@ -2,6 +2,7 @@

#include <net/ultraeth/uet_context.h>
#include <net/ultraeth/uecon.h>
+#include <net/ultraeth/uet_chardev.h>
#include "uet_netlink.h"

#define MAX_CONTEXT_ID 256
@@ -78,6 +79,24 @@ struct uet_context *uet_context_get_by_id(int id)
return ctx;
}

+struct uet_context *uet_context_get_by_minor(int minor)
+{
+ struct uet_context *ctx;
+
+ mutex_lock(&uet_context_lock);
+ list_for_each_entry(ctx, &uet_context_list, list) {
+ if (ctx->cdev.minor == minor) {
+ refcount_inc(&ctx->refcnt);
+ goto out;
+ }
+ }
+ ctx = NULL;
+out:
+ mutex_unlock(&uet_context_lock);
+
+ return ctx;
+}
+
void uet_context_put(struct uet_context *ctx)
{
if (refcount_dec_and_test(&ctx->refcnt))
@@ -111,6 +130,10 @@ int uet_context_create(int id)
if (err)
goto ctx_pds_err;

+ err = uet_char_init(&ctx->cdev, ctx->id);
+ if (err)
+ goto ctx_char_err;
+
err = uecon_netdev_init(ctx);
if (err)
goto ctx_netdev_err;
@@ -120,6 +143,8 @@ int uet_context_create(int id)
return 0;

ctx_netdev_err:
+ uet_char_uninit(&ctx->cdev);
+ctx_char_err:
uet_pds_uninit(&ctx->pds);
ctx_pds_err:
uet_jobs_uninit(&ctx->job_reg);
@@ -135,6 +160,7 @@ static void __uet_context_destroy(struct uet_context *ctx)
{
uet_context_unlink(ctx);
uecon_netdev_uninit(ctx);
+ uet_char_uninit(&ctx->cdev);
uet_pds_uninit(&ctx->pds);
uet_jobs_uninit(&ctx->job_reg);
uet_context_put_id(ctx);
@@ -183,7 +209,10 @@ static int __nl_ctx_fill_one(struct sk_buff *skb,

if (nla_put_s32(skb, ULTRAETH_A_CONTEXT_ID, ctx->id) ||
nla_put_s32(skb, ULTRAETH_A_CONTEXT_NETDEV_IFINDEX, ctx->netdev->ifindex) ||
- nla_put_string(skb, ULTRAETH_A_CONTEXT_NETDEV_NAME, ctx->netdev->name))
+ nla_put_string(skb, ULTRAETH_A_CONTEXT_NETDEV_NAME, ctx->netdev->name) ||
+ nla_put_string(skb, ULTRAETH_A_CONTEXT_CHARDEV_NAME, ctx->cdev.name) ||
+ nla_put_s32(skb, ULTRAETH_A_CONTEXT_CHARDEV_MAJOR, MISC_MAJOR) ||
+ nla_put_s32(skb, ULTRAETH_A_CONTEXT_CHARDEV_MINOR, ctx->cdev.minor))
goto out_err;

genlmsg_end(skb, hdr);
diff --git a/include/net/ultraeth/uet_chardev.h b/include/net/ultraeth/uet_chardev.h
new file mode 100644
index 000000000000..963b3e247630
--- /dev/null
+++ b/include/net/ultraeth/uet_chardev.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
+
+#ifndef _UECON_CHARDEV_H
+#define _UECON_CHAR_H
+
+#include <linux/miscdevice.h>
+
+int uet_char_init(struct miscdevice *cdev, int id);
+void uet_char_uninit(struct miscdevice *cdev);
+
+#endif /* _UECON_CHARDEV_H */
diff --git a/include/net/ultraeth/uet_context.h b/include/net/ultraeth/uet_context.h
index 76077df3bce6..06a5c7f252ac 100644
--- a/include/net/ultraeth/uet_context.h
+++ b/include/net/ultraeth/uet_context.h
@@ -11,6 +11,7 @@
#include <linux/wait.h>
#include <net/ultraeth/uet_job.h>
#include <net/ultraeth/uet_pds.h>
+#include <linux/miscdevice.h>

struct uet_context {
int id;
@@ -21,9 +22,11 @@ struct uet_context {
struct net_device *netdev;
struct uet_job_registry job_reg;
struct uet_pds pds;
+ struct miscdevice cdev;
};

struct uet_context *uet_context_get_by_id(int id);
+struct uet_context *uet_context_get_by_minor(int minor);
void uet_context_put(struct uet_context *ses_pl);

int uet_context_create(int id);
diff --git a/include/uapi/linux/ultraeth.h b/include/uapi/linux/ultraeth.h
index c1d5457073e1..2843bb710f1e 100644
--- a/include/uapi/linux/ultraeth.h
+++ b/include/uapi/linux/ultraeth.h
@@ -512,4 +512,25 @@ struct fep_address {
__u16 padding;
__u8 version;
};
+
+/* char device hacks */
+#define UET_IOCTL_MAGIC 'u'
+#define UET_ADDR_REQ _IO(UET_IOCTL_MAGIC, 1)
+
+struct uet_job_addr_req {
+ struct fep_in_address address;
+ char service_name[UET_SVC_MAX_LEN];
+ __u32 ack_gen_trigger;
+ __u32 ack_gen_min_pkt_add;
+ __u8 flags;
+};
+
+struct uet_pds_meta {
+ __u8 next_hdr:4;
+ __u8 reserved1:4;
+ __u8 reserved2;
+ __be16 port;
+ /* XXX: fep_address */
+ __be32 addr;
+} __attribute__((packed));
#endif /* _UAPI_LINUX_ULTRAETH_H */
diff --git a/include/uapi/linux/ultraeth_nl.h b/include/uapi/linux/ultraeth_nl.h
index 515044022906..884fa165adb6 100644
--- a/include/uapi/linux/ultraeth_nl.h
+++ b/include/uapi/linux/ultraeth_nl.h
@@ -13,6 +13,9 @@ enum {
ULTRAETH_A_CONTEXT_ID = 1,
ULTRAETH_A_CONTEXT_NETDEV_IFINDEX,
ULTRAETH_A_CONTEXT_NETDEV_NAME,
+ ULTRAETH_A_CONTEXT_CHARDEV_NAME,
+ ULTRAETH_A_CONTEXT_CHARDEV_MAJOR,
+ ULTRAETH_A_CONTEXT_CHARDEV_MINOR,

__ULTRAETH_A_CONTEXT_MAX,
ULTRAETH_A_CONTEXT_MAX = (__ULTRAETH_A_CONTEXT_MAX - 1)
--
2.48.1