[PATCH v2 14/15] misc: nnpi: Create command channel from userspace

From: Guy Zadicario
Date: Thu May 13 2021 - 05:03:48 EST


Expose a character device for each NNP-I device (/dev/nnpi%d) with
IOCTL interface. Using this character device, user-space can create a
command channel object, through which it can send and receive messages
to and from the device.

Signed-off-by: Guy Zadicario <guy.zadicario@xxxxxxxxx>
Reviewed-by: Alexander Shishkin <alexander.shishkin@xxxxxxxxxxxxxxx>
---
drivers/misc/intel-nnpi/Makefile | 2 +-
drivers/misc/intel-nnpi/cmd_chan.c | 11 +
drivers/misc/intel-nnpi/cmd_chan.h | 1 +
drivers/misc/intel-nnpi/device.c | 50 ++++-
drivers/misc/intel-nnpi/device.h | 11 +
drivers/misc/intel-nnpi/device_chardev.c | 348 +++++++++++++++++++++++++++++++
drivers/misc/intel-nnpi/device_chardev.h | 14 ++
include/uapi/misc/intel_nnpi.h | 43 ++++
8 files changed, 478 insertions(+), 2 deletions(-)
create mode 100644 drivers/misc/intel-nnpi/device_chardev.c
create mode 100644 drivers/misc/intel-nnpi/device_chardev.h

diff --git a/drivers/misc/intel-nnpi/Makefile b/drivers/misc/intel-nnpi/Makefile
index b3bab2a..a294cf0c 100644
--- a/drivers/misc/intel-nnpi/Makefile
+++ b/drivers/misc/intel-nnpi/Makefile
@@ -6,7 +6,7 @@
obj-$(CONFIG_INTEL_NNPI) := intel_nnpi.o intel_nnpi_pcie.o

intel_nnpi-y := device.o msg_scheduler.o hostres.o host_chardev.o nnp_user.o \
- bootimage.o cmd_chan.o
+ bootimage.o cmd_chan.o device_chardev.o

intel_nnpi_pcie-y := nnp_pcie.o

diff --git a/drivers/misc/intel-nnpi/cmd_chan.c b/drivers/misc/intel-nnpi/cmd_chan.c
index 89ae604..0ad281a 100644
--- a/drivers/misc/intel-nnpi/cmd_chan.c
+++ b/drivers/misc/intel-nnpi/cmd_chan.c
@@ -557,6 +557,17 @@ void nnp_chan_disconnect(struct nnp_chan *cmd_chan)
cmd_chan->state = NNP_CHAN_DESTROYED;
mutex_unlock(&cmd_chan->dev_mutex);

+ /*
+ * If the channel is not in critical state,
+ * put it in critical state and wake any user
+ * which might wait for the device.
+ */
+ if (!chan_drv_fatal(cmd_chan)) {
+ cmd_chan->card_critical_error_msg = FIELD_PREP(NNP_C2H_EVENT_REPORT_CODE_MASK,
+ NNP_IPC_ERROR_CHANNEL_KILLED);
+ wake_up_all(&nnpdev->waitq);
+ }
+
wake_up_all(&cmd_chan->resp_waitq);
nnp_msched_queue_sync(cmd_chan->cmdq);
nnp_msched_queue_destroy(cmd_chan->cmdq);
diff --git a/drivers/misc/intel-nnpi/cmd_chan.h b/drivers/misc/intel-nnpi/cmd_chan.h
index d60abf4..3eb5c1c 100644
--- a/drivers/misc/intel-nnpi/cmd_chan.h
+++ b/drivers/misc/intel-nnpi/cmd_chan.h
@@ -80,6 +80,7 @@ struct nnp_chan {
};

#define chan_broken(chan) FIELD_GET(NNP_C2H_EVENT_REPORT_CODE_MASK, (chan)->card_critical_error_msg)
+#define chan_drv_fatal(chan) (is_card_fatal_drv_event(chan_broken(chan)))

struct nnp_chan *nnpdev_chan_create(struct nnp_device *nnpdev, int host_fd,
unsigned int min_id, unsigned int max_id,
diff --git a/drivers/misc/intel-nnpi/device.c b/drivers/misc/intel-nnpi/device.c
index ece19c0..17746d2 100644
--- a/drivers/misc/intel-nnpi/device.c
+++ b/drivers/misc/intel-nnpi/device.c
@@ -13,6 +13,7 @@
#include "bootimage.h"
#include "cmd_chan.h"
#include "device.h"
+#include "device_chardev.h"
#include "host_chardev.h"
#include "ipc_c2h_events.h"
#include "msg_scheduler.h"
@@ -259,6 +260,22 @@ static void nnpdev_submit_device_event_to_channels(struct nnp_device *nnpdev,
disconnect_all_channels(nnpdev);
}

+static void handle_channel_create_response(struct nnp_device *nnpdev, u64 event_msg)
+{
+ struct nnp_chan *cmd_chan;
+ unsigned int chan_id;
+
+ chan_id = FIELD_GET(NNP_C2H_EVENT_REPORT_OBJ_ID_MASK, event_msg);
+
+ cmd_chan = nnpdev_find_channel(nnpdev, chan_id);
+ if (!cmd_chan)
+ return;
+
+ cmd_chan->event_msg = event_msg;
+ nnp_chan_put(cmd_chan);
+ wake_up_all(&nnpdev->waitq);
+}
+
static void handle_channel_destroy(struct nnp_device *nnpdev, u64 event_msg)
{
struct nnp_chan *cmd_chan;
@@ -300,6 +317,10 @@ static void process_device_event(struct nnp_device *nnpdev, u64 event_msg)

if (!is_card_fatal_event(event_code)) {
switch (event_code) {
+ case NNP_IPC_CREATE_CHANNEL_SUCCESS:
+ case NNP_IPC_CREATE_CHANNEL_FAILED:
+ handle_channel_create_response(nnpdev, event_msg);
+ break;
case NNP_IPC_DESTROY_CHANNEL_FAILED:
obj_id = FIELD_GET(NNP_C2H_EVENT_REPORT_OBJ_ID_MASK, event_msg);
event_val = FIELD_GET(NNP_C2H_EVENT_REPORT_VAL_MASK, event_msg);
@@ -796,6 +817,11 @@ int nnpdev_init(struct nnp_device *nnpdev, struct device *dev,
goto err_wq;
}

+ /* Create the character device interface to this device */
+ ret = nnpdev_cdev_create(nnpdev);
+ if (ret)
+ goto err_sys_info;
+
/* set host driver state to "Not ready" */
nnpdev->ops->set_host_doorbell_value(nnpdev, 0);

@@ -805,6 +831,9 @@ int nnpdev_init(struct nnp_device *nnpdev, struct device *dev,

return 0;

+err_sys_info:
+ dma_free_coherent(nnpdev->dev, NNP_PAGE_SIZE, nnpdev->bios_system_info,
+ nnpdev->bios_system_info_dma_addr);
err_wq:
destroy_workqueue(nnpdev->wq);
err_cmdq:
@@ -946,6 +975,10 @@ void nnpdev_destroy(struct nnp_device *nnpdev)
destroy_workqueue(nnpdev->wq);

disconnect_all_channels(nnpdev);
+
+ /* destroy character device */
+ nnpdev_cdev_destroy(nnpdev);
+
dma_free_coherent(nnpdev->dev, NNP_PAGE_SIZE, nnpdev->bios_system_info,
nnpdev->bios_system_info_dma_addr);

@@ -960,13 +993,28 @@ void nnpdev_destroy(struct nnp_device *nnpdev)

static int __init nnp_init(void)
{
- return nnp_init_host_interface();
+ int ret;
+
+ ret = nnp_init_host_interface();
+ if (ret)
+ return ret;
+
+ ret = nnpdev_cdev_class_init();
+ if (ret)
+ goto err_class;
+
+ return 0;
+
+err_class:
+ nnp_release_host_interface();
+ return ret;
}
subsys_initcall(nnp_init);

static void __exit nnp_cleanup(void)
{
nnp_release_host_interface();
+ nnpdev_cdev_class_cleanup();
/* dev_ida is already empty here - no point calling ida_destroy */
}
module_exit(nnp_cleanup);
diff --git a/drivers/misc/intel-nnpi/device.h b/drivers/misc/intel-nnpi/device.h
index c37f1da..e7e66d6 100644
--- a/drivers/misc/intel-nnpi/device.h
+++ b/drivers/misc/intel-nnpi/device.h
@@ -4,8 +4,10 @@
#ifndef _NNPDRV_DEVICE_H
#define _NNPDRV_DEVICE_H

+#include <linux/cdev.h>
#include <linux/hashtable.h>
#include <linux/idr.h>
+#include <linux/list.h>
#include <linux/spinlock.h>
#include <linux/workqueue.h>

@@ -53,6 +55,9 @@
#define NNP_DEVICE_RESPONSE_FIFO_LEN 16
#define NNP_DEVICE_RESPONSE_BUFFER_LEN (NNP_DEVICE_RESPONSE_FIFO_LEN * 2)

+#define NNP_MAX_CHANNEL_ID 1023 /* has 10 bits in ipc protocol */
+#define NNP_MAX_INF_CONTEXT_CHANNEL_ID 255 /* [0, 255] are reserved for inference contexts */
+
struct query_version_work {
struct work_struct work;
u64 chan_resp_op_size;
@@ -92,6 +97,9 @@ struct query_version_work {
* @boot_image: boot image object used to boot the card
* @query_version_work: work struct used to schedule processing of version
* reply response message arrived from card.
+ * @cdev: cdev object of NNP-I device char dev.
+ * @chardev: character device for this device
+ * @cdev_clients: list of opened struct file to the chardev of this device.
* @ipc_chan_resp_op_size: holds response size for each possible channel
* response.
* @ipc_chan_cmd_op_size: holds command size for each possible channel command.
@@ -130,6 +138,9 @@ struct nnp_device {

struct query_version_work query_version_work;

+ struct cdev cdev;
+ struct device *chardev;
+ struct list_head cdev_clients;
u8 ipc_chan_resp_op_size[NNP_IPC_NUM_USER_OPS];
u8 ipc_chan_cmd_op_size[NNP_IPC_NUM_USER_OPS];
};
diff --git a/drivers/misc/intel-nnpi/device_chardev.c b/drivers/misc/intel-nnpi/device_chardev.c
new file mode 100644
index 0000000..e4bb168
--- /dev/null
+++ b/drivers/misc/intel-nnpi/device_chardev.c
@@ -0,0 +1,348 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (C) 2019-2021 Intel Corporation */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/bitfield.h>
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/kref.h>
+#include <linux/list.h>
+#include <linux/printk.h>
+#include <linux/slab.h>
+
+#include <uapi/misc/intel_nnpi.h>
+
+#include "cmd_chan.h"
+#include "device_chardev.h"
+#include "ipc_c2h_events.h"
+
+static dev_t devnum;
+static struct class *class;
+
+/**
+ * struct device_client - structure for opened device char device file
+ * @node: list node to include this struct in a list of clients
+ * (nnpdev->cdev_clients).
+ * @nnpdev: the NNP-I device associated with the opened chardev
+ * @mutex: protects @nnpdev
+ *
+ * NOTE: @nnpdev may become NULL if the underlying NNP-I device has removed.
+ * Any ioctl request on the char device in this state will fail with
+ * -ENODEV
+ */
+struct device_client {
+ struct list_head node;
+ struct nnp_device *nnpdev;
+ struct mutex mutex;
+};
+
+/* protects nnpdev->cdev_clients list (for all nnp devices) */
+static DEFINE_MUTEX(clients_mutex);
+
+#define NNPDRV_DEVICE_DEV_NAME "nnpi"
+
+static inline bool is_nnp_device_file(struct file *f);
+
+static int nnp_device_open(struct inode *inode, struct file *f)
+{
+ struct device_client *client;
+ struct nnp_device *nnpdev;
+
+ if (!is_nnp_device_file(f))
+ return -EINVAL;
+
+ if (!inode->i_cdev)
+ return -EINVAL;
+
+ client = kzalloc(sizeof(*client), GFP_KERNEL);
+ if (!client)
+ return -ENOMEM;
+
+ nnpdev = container_of(inode->i_cdev, struct nnp_device, cdev);
+ client->nnpdev = nnpdev;
+ mutex_init(&client->mutex);
+ f->private_data = client;
+
+ mutex_lock(&clients_mutex);
+ list_add_tail(&client->node, &nnpdev->cdev_clients);
+ mutex_unlock(&clients_mutex);
+
+ return 0;
+}
+
+static void disconnect_client_locked(struct device_client *client)
+{
+ lockdep_assert_held(&clients_mutex);
+
+ mutex_lock(&client->mutex);
+ if (!client->nnpdev) {
+ mutex_unlock(&client->mutex);
+ return;
+ }
+ client->nnpdev = NULL;
+ list_del(&client->node);
+ mutex_unlock(&client->mutex);
+}
+
+static int nnp_device_release(struct inode *inode, struct file *f)
+{
+ struct device_client *client = f->private_data;
+
+ if (!is_nnp_device_file(f))
+ return -EINVAL;
+
+ mutex_lock(&clients_mutex);
+ disconnect_client_locked(client);
+ mutex_unlock(&clients_mutex);
+ kfree(client);
+ f->private_data = NULL;
+
+ return 0;
+}
+
+static int event_val_to_nnp_error(enum event_val event_val)
+{
+ switch (event_val) {
+ case NNP_IPC_NO_ERROR:
+ return 0;
+ case NNP_IPC_NO_MEMORY:
+ return -ENOMEM;
+ default:
+ return -EFAULT;
+ }
+}
+
+static int send_create_chan_req(struct nnp_device *nnpdev, struct nnp_chan *chan)
+{
+ unsigned int event_code, event_val;
+ u64 cmd;
+ int ret;
+
+ cmd = FIELD_PREP(NNP_H2C_OP_MASK, NNP_IPC_H2C_OP_CHANNEL_OP);
+ cmd |= FIELD_PREP(NNP_H2C_CHANNEL_OP_CHAN_ID_MASK, chan->chan_id);
+ cmd |= FIELD_PREP(NNP_H2C_CHANNEL_OP_UID_MASK, 0);
+ cmd |= FIELD_PREP(NNP_H2C_CHANNEL_OP_PRIV_MASK, 1);
+
+ ret = nnp_msched_queue_msg(nnpdev->cmdq, cmd);
+ if (ret < 0)
+ return NNPER_DEVICE_ERROR;
+
+ /*
+ * wait until card has respond to the create request or fatal
+ * card error has been detected.
+ */
+ wait_event(nnpdev->waitq, chan->event_msg || chan_drv_fatal(chan));
+ if (!chan->event_msg)
+ return NNPER_DEVICE_ERROR;
+
+ event_code = FIELD_GET(NNP_C2H_EVENT_REPORT_CODE_MASK, chan->event_msg);
+ event_val = FIELD_GET(NNP_C2H_EVENT_REPORT_VAL_MASK, chan->event_msg);
+ if (event_code == NNP_IPC_CREATE_CHANNEL_FAILED)
+ return event_val_to_nnp_error(event_val);
+
+ return 0;
+}
+
+static long create_channel(struct device_client *cinfo, void __user *arg,
+ unsigned int size)
+{
+ struct nnp_device *nnpdev = cinfo->nnpdev;
+ struct ioctl_nnpi_create_channel req;
+ unsigned int io_size = sizeof(req);
+ struct nnp_chan *chan;
+ long ret = 0;
+ u32 error_mask;
+
+ /* only single size structure is currently supported */
+ if (size != io_size)
+ return -EINVAL;
+
+ if (copy_from_user(&req, arg, io_size))
+ return -EFAULT;
+
+ /* o_errno must be cleared on entry */
+ if (req.o_errno)
+ return -EINVAL;
+
+ if (req.i_max_id < req.i_min_id ||
+ req.i_max_id > NNP_MAX_CHANNEL_ID)
+ return -EINVAL;
+
+ /*
+ * Do not allow create command channel if device is in
+ * error state.
+ * However allow new non infer context channels in case
+ * of fatal ICE error in order to allow retrieve debug
+ * information.
+ */
+ error_mask = NNP_DEVICE_ERROR_MASK;
+ if (req.i_max_id > NNP_MAX_INF_CONTEXT_CHANNEL_ID)
+ error_mask &= ~(NNP_DEVICE_FATAL_ICE_ERROR);
+
+ if ((nnpdev->state & error_mask) ||
+ !(nnpdev->state & NNP_DEVICE_CARD_DRIVER_READY) ||
+ (req.i_max_id <= NNP_MAX_INF_CONTEXT_CHANNEL_ID &&
+ (nnpdev->state & NNP_DEVICE_ACTIVE_MASK) !=
+ NNP_DEVICE_ACTIVE_MASK)) {
+ req.o_errno = NNPER_DEVICE_NOT_READY;
+ goto done;
+ }
+
+ /* Validate channel protocol version */
+ if (NNP_VERSION_MAJOR(req.i_protocol_version) !=
+ NNP_VERSION_MAJOR(nnpdev->chan_protocol_version) ||
+ NNP_VERSION_MINOR(req.i_protocol_version) !=
+ NNP_VERSION_MINOR(nnpdev->chan_protocol_version)) {
+ req.o_errno = NNPER_VERSIONS_MISMATCH;
+ goto done;
+ }
+
+ /* create the channel object */
+ chan = nnpdev_chan_create(nnpdev, req.i_host_fd, req.i_min_id, req.i_max_id,
+ req.i_get_device_events);
+ if (IS_ERR(chan)) {
+ ret = PTR_ERR(chan);
+ goto done;
+ }
+
+ /* create the channel on card */
+ req.o_errno = send_create_chan_req(nnpdev, chan);
+ if (req.o_errno)
+ goto err_destroy;
+
+ req.o_channel_id = chan->chan_id;
+
+ /* Attach file descriptor to the channel object */
+ req.o_fd = nnp_chan_create_file(chan);
+
+ /* remove channel object if failed */
+ if (req.o_fd < 0) {
+ /* the channel already created on card - send a destroy request */
+ nnp_chan_send_destroy(chan);
+ ret = req.o_fd;
+ }
+
+ goto done;
+
+err_destroy:
+ /* the channel was not created on card - destroy it now */
+ if (!nnp_chan_set_destroyed(chan))
+ nnp_chan_put(chan);
+done:
+ if (!ret && copy_to_user(arg, &req, io_size))
+ return -EFAULT;
+
+ return ret;
+}
+
+static long nnp_device_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
+{
+ struct device_client *client = f->private_data;
+ unsigned int ioc_nr, size;
+ long ret;
+
+ if (!is_nnp_device_file(f))
+ return -ENOTTY;
+
+ if (_IOC_TYPE(cmd) != 'D')
+ return -EINVAL;
+
+ mutex_lock(&client->mutex);
+ if (!client->nnpdev) {
+ mutex_unlock(&client->mutex);
+ return -ENODEV;
+ }
+
+ ioc_nr = _IOC_NR(cmd);
+ size = _IOC_SIZE(cmd);
+
+ switch (ioc_nr) {
+ case _IOC_NR(IOCTL_NNPI_DEVICE_CREATE_CHANNEL):
+ ret = create_channel(client, (void __user *)arg, size);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ mutex_unlock(&client->mutex);
+
+ return ret;
+}
+
+static const struct file_operations nnp_device_fops = {
+ .owner = THIS_MODULE,
+ .open = nnp_device_open,
+ .release = nnp_device_release,
+ .unlocked_ioctl = nnp_device_ioctl,
+ .compat_ioctl = nnp_device_ioctl,
+};
+
+static inline bool is_nnp_device_file(struct file *f)
+{
+ return f->f_op == &nnp_device_fops;
+}
+
+int nnpdev_cdev_create(struct nnp_device *nnpdev)
+{
+ int ret;
+
+ INIT_LIST_HEAD(&nnpdev->cdev_clients);
+
+ cdev_init(&nnpdev->cdev, &nnp_device_fops);
+ nnpdev->cdev.owner = THIS_MODULE;
+ ret = cdev_add(&nnpdev->cdev, MKDEV(MAJOR(devnum), nnpdev->id), 1);
+ if (ret)
+ return ret;
+
+ nnpdev->chardev = device_create(class, NULL, MKDEV(MAJOR(devnum), nnpdev->id),
+ nnpdev, NNPI_DEVICE_DEV_FMT, nnpdev->id);
+ if (IS_ERR(nnpdev->chardev)) {
+ cdev_del(&nnpdev->cdev);
+ return PTR_ERR(nnpdev->chardev);
+ }
+
+ return 0;
+}
+
+void nnpdev_cdev_destroy(struct nnp_device *nnpdev)
+{
+ struct device_client *client, *tmp;
+
+ device_destroy(class, MKDEV(MAJOR(devnum), nnpdev->id));
+
+ /* disconnect all chardev clients from the device */
+ mutex_lock(&clients_mutex);
+ list_for_each_entry_safe(client, tmp, &nnpdev->cdev_clients, node)
+ disconnect_client_locked(client);
+ mutex_unlock(&clients_mutex);
+
+ cdev_del(&nnpdev->cdev);
+}
+
+int nnpdev_cdev_class_init(void)
+{
+ int ret;
+
+ ret = alloc_chrdev_region(&devnum, 0, NNP_MAX_DEVS,
+ NNPDRV_DEVICE_DEV_NAME);
+ if (ret < 0)
+ return ret;
+
+ class = class_create(THIS_MODULE, NNPDRV_DEVICE_DEV_NAME);
+ if (IS_ERR(class)) {
+ ret = PTR_ERR(class);
+ unregister_chrdev_region(devnum, NNP_MAX_DEVS);
+ return ret;
+ }
+
+ return 0;
+}
+
+void nnpdev_cdev_class_cleanup(void)
+{
+ class_destroy(class);
+ unregister_chrdev_region(devnum, NNP_MAX_DEVS);
+}
+
diff --git a/drivers/misc/intel-nnpi/device_chardev.h b/drivers/misc/intel-nnpi/device_chardev.h
new file mode 100644
index 0000000..0db919d
--- /dev/null
+++ b/drivers/misc/intel-nnpi/device_chardev.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright (C) 2019-2021 Intel Corporation */
+
+#ifndef _NNPDRV_DEVICE_CHARDEV_H
+#define _NNPDRV_DEVICE_CHARDEV_H
+
+#include "device.h"
+
+int nnpdev_cdev_create(struct nnp_device *nnpdev);
+void nnpdev_cdev_destroy(struct nnp_device *nnpdev);
+int nnpdev_cdev_class_init(void);
+void nnpdev_cdev_class_cleanup(void);
+
+#endif
diff --git a/include/uapi/misc/intel_nnpi.h b/include/uapi/misc/intel_nnpi.h
index 5114aea..620a5d4 100644
--- a/include/uapi/misc/intel_nnpi.h
+++ b/include/uapi/misc/intel_nnpi.h
@@ -134,6 +134,49 @@ struct nnpdrv_ioctl_destroy_hostres {
__u32 o_errno;
};

+/*
+ * ioctls for /dev/nnpi%d device
+ */
+#define NNPI_DEVICE_DEV_FMT "nnpi%u"
+
+/**
+ * IOCTL_NNPI_DEVICE_CREATE_CHANNEL:
+ *
+ * A request to create a new communication "channel" with an NNP-I device.
+ * This channel can be used to send command and receive responses from the
+ * device.
+ */
+#define IOCTL_NNPI_DEVICE_CREATE_CHANNEL \
+ _IOWR('D', 0, struct ioctl_nnpi_create_channel)
+
+/**
+ * struct ioctl_nnpi_create_channel - IOCTL_NNPI_DEVICE_CREATE_CHANNEL payload
+ * @i_host_fd: opened file descriptor to /dev/nnpi_host
+ * @i_min_id: minimum range for channel id allocation
+ * @i_max_id: maximum range for channel id allocation
+ * @i_get_device_events: if true, device-level event responses will be
+ * delivered to be read from the channel.
+ * @i_protocol_version: The NNP_IPC_CHAN_PROTOCOL_VERSION the user-space has
+ * compiled with.
+ * @o_fd: returns file-descriptor through which commands/responses can be
+ * write/read.
+ * @o_errno: On input, must be set to 0.
+ * On output, 0 on success, one of the NNPERR_* error codes on error.
+ * @o_channel_id: returns the unique id of the channel
+ *
+ * Argument structure for IOCTL_NNPI_DEVICE_CREATE_CHANNEL ioctl.
+ */
+struct ioctl_nnpi_create_channel {
+ __s32 i_host_fd;
+ __u32 i_min_id;
+ __u32 i_max_id;
+ __s32 i_get_device_events;
+ __u32 i_protocol_version;
+ __s32 o_fd;
+ __u32 o_errno;
+ __u16 o_channel_id;
+};
+
/****************************************************************
* Error code values - errors returned in o_errno fields of
* above structures.
--
1.8.3.1