Currently, the only hd controller supported by Greybus is the es2
controller which only support is mainly a bridge between USB and UniPro.
In order to use Greybus on devices that do not support UniPro,
add a the Greybus netlink hd controller.
By using Generic Netlink, userspace can act as a bridge between Greybus
and any kind of bus supported by the platform (e.g. Bluetooth).
In addition, this add an easy way to implement some component such as
SVC which is required by Greybus though it may not be available on
every platforms.
Signed-off-by: Alexandre Bailon <abailon@xxxxxxxxxxxx>
---
drivers/staging/greybus/Kconfig | 9 ++
drivers/staging/greybus/Makefile | 2 +
drivers/staging/greybus/gb_netlink.h | 37 ++++++
drivers/staging/greybus/netlink.c | 221 +++++++++++++++++++++++++++++++++++
4 files changed, 269 insertions(+)
create mode 100644 drivers/staging/greybus/gb_netlink.h
create mode 100644 drivers/staging/greybus/netlink.c
diff --git a/drivers/staging/greybus/Kconfig b/drivers/staging/greybus/Kconfig
index 50de2d7..f9f3526 100644
--- a/drivers/staging/greybus/Kconfig
+++ b/drivers/staging/greybus/Kconfig
@@ -27,6 +27,15 @@ config GREYBUS_ES2
To compile this code as a module, chose M here: the module
will be called gb-es2.ko
+config GREYBUS_NETLINK
+ tristate "Greybus netlink host controller"
+ ---help---
+ Select this option if you want to implement a Greybus
+ "host controller" in userspace.
+
+ To compile this code as a module, chose M here: the module
+ will be called gb-netlink.ko
+
config GREYBUS_AUDIO
tristate "Greybus Audio Class driver"
depends on SOUND
diff --git a/drivers/staging/greybus/Makefile b/drivers/staging/greybus/Makefile
index b26b9a3..d057f1d 100644
--- a/drivers/staging/greybus/Makefile
+++ b/drivers/staging/greybus/Makefile
@@ -20,8 +20,10 @@ ccflags-y += -I$(src)
# Greybus Host controller drivers
gb-es2-y := es2.o
+gb-netlink-y := netlink.o
obj-$(CONFIG_GREYBUS_ES2) += gb-es2.o
+obj-$(CONFIG_GREYBUS_NETLINK) += gb-netlink.o
# Greybus class drivers
gb-bootrom-y := bootrom.o
diff --git a/drivers/staging/greybus/gb_netlink.h b/drivers/staging/greybus/gb_netlink.h
new file mode 100644
index 0000000..4af6fe5
--- /dev/null
+++ b/drivers/staging/greybus/gb_netlink.h
@@ -0,0 +1,37 @@
+/*
+ * Greybus Netlink driver for userspace controller
+ *
+ * Copyright (c) 2017 BayLibre SAS
+ *
+ * Released under the GPLv2 only.
+ */
+
+#ifndef __GB_NETLINK_H
+#define __GB_NETLINK_H
+
+/* Maximum packet size */
+#define GB_NETLINK_MTU 2048
+/* Maximum number of Cports */
+#define GB_NETLINK_NUM_CPORT 32
+
+#define GB_NL_NAME "GREYBUS"
+#define GB_NL_PID 1
+
+enum {
+ GB_NL_A_UNSPEC,
+ GB_NL_A_DATA,
+ GB_NL_A_CPORT,
+ __GB_NL_A_MAX,
+};
+
+#define GB_NL_A_MAX (__GB_NL_A_MAX - 1)
+
+enum {
+ GB_NL_C_UNSPEC,
+ GB_NL_C_MSG,
+ __GB_NL_C_MAX,
+};
+
+#define GB_NL_C_MAX (__GB_NL_C_MAX - 1)
+
+#endif /* __GB_NETLINK_H */
diff --git a/drivers/staging/greybus/netlink.c b/drivers/staging/greybus/netlink.c
new file mode 100644
index 0000000..84f3018
--- /dev/null
+++ b/drivers/staging/greybus/netlink.c
@@ -0,0 +1,221 @@
+/*
+ * Greybus Netlink driver for userspace controller
+ *
+ * Copyright (c) 2017 BayLibre SAS
+ *
+ * Released under the GPLv2 only.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <net/genetlink.h>
+
+#include "greybus.h"
+#include "gb_netlink.h"
+
+static dev_t major_dev;
+static struct class *gb_nl_class;
+static struct genl_family gb_nl_family;
+static struct gb_host_device *gb_nl_hd;
+
+#define VERSION_NR 1
+
+#define DEVICE_NAME "gb_netlink"
+#define CLASS_NAME "gb_netlink"
+
+static int gb_netlink_msg(struct sk_buff *skb, struct genl_info *info)
+{
+ struct nlattr *na;
+ u16 cport_id;
+ void *data;
+
+ if (!info)
+ return -EPROTO;
+
+ na = info->attrs[GB_NL_A_CPORT];
+ if (!na) {
+ dev_err(&gb_nl_hd->dev,
+ "Received message without cport id attribute\n");
+ return -EPROTO;
+ }
+
+ cport_id = nla_get_u32(na);
+ if (!cport_id_valid(gb_nl_hd, cport_id)) {
+ dev_err(&gb_nl_hd->dev, "invalid cport id %u received",
+ cport_id);
+ return -EINVAL;
+ }
+
+ na = info->attrs[GB_NL_A_DATA];
+ if (!na) {
+ dev_err(&gb_nl_hd->dev,
+ "Received message without data attribute\n");
+ return -EPROTO;
+ }
+
+ data = nla_data(na);
+ if (!data) {
+ dev_err(&gb_nl_hd->dev,
+ "Received message without data\n");
+ return -EINVAL;
+ }
+
+ greybus_data_rcvd(gb_nl_hd, cport_id, data, nla_len(na));
+
+ return 0;
+}
+
+static struct nla_policy gb_nl_policy[GB_NL_A_MAX + 1] = {
+ [GB_NL_A_DATA] = { .type = NLA_BINARY, .len = GB_NETLINK_MTU },
+ [GB_NL_A_CPORT] = { .type = NLA_U16},
+};
+
+static struct genl_ops gb_nl_ops[] = {
+ {
+ .cmd = GB_NL_C_MSG,
+ .policy = gb_nl_policy,
+ .doit = gb_netlink_msg,
+ },
+};
+
+static struct genl_family gb_nl_family = {
+ .hdrsize = 0,
+ .name = GB_NL_NAME,
+ .version = VERSION_NR,
+ .maxattr = GB_NL_A_MAX,
+ .ops = gb_nl_ops,
+ .n_ops = ARRAY_SIZE(gb_nl_ops),
+};
+
+static int message_send(struct gb_host_device *hd, u16 cport_id,
+ struct gb_message *message, gfp_t gfp_mask)
+{
+ struct nl_msg *nl_msg;
+ struct sk_buff *skb;
+ int retval = -ENOMEM;
+
+ skb = genlmsg_new(sizeof(*message->header) + sizeof(u32) +
+ message->payload_size, GFP_KERNEL);
+ if (!skb)
+ goto err_out;
+
+ nl_msg = genlmsg_put(skb, GB_NL_PID, 0,
+ &gb_nl_family, 0, GB_NL_C_MSG);
+ if (!nl_msg)
+ goto err_free;
+
+ retval = nla_put_u32(skb, GB_NL_A_CPORT, cport_id);
+ if (retval)
+ goto err_cancel;
+
+ retval = nla_put(skb, GB_NL_A_DATA,
+ sizeof(*message->header) + message->payload_size,
+ message->header);
+ if (retval)
+ goto err_cancel;
+
+ genlmsg_end(skb, nl_msg);
+
+ retval = genlmsg_unicast(&init_net, skb, GB_NL_PID);
+ if (retval)
+ goto err_cancel;
+
+ greybus_message_sent(hd, message, 0);
+
+ return 0;
+
+err_cancel:
+ genlmsg_cancel(skb, nl_msg);
+err_free:
+ nlmsg_free(skb);
+err_out:
+ return retval;
+}
+
+static void message_cancel(struct gb_message *message)
+{
+}
+
+static struct gb_hd_driver tcpip_driver = {
+ .message_send = message_send,
+ .message_cancel = message_cancel,
+};
+
+static void __exit gb_netlink_exit(void)
+{
+ if (!gb_nl_hd)
+ return;
+
+ gb_hd_del(gb_nl_hd);
+ gb_hd_put(gb_nl_hd);
+
+ gb_nl_hd = NULL;
+
+ unregister_chrdev_region(major_dev, 1);
+ device_destroy(gb_nl_class, major_dev);
+ class_destroy(gb_nl_class);
+
+ genl_unregister_family(&gb_nl_family);
+}
+
+static int __init gb_netlink_init(void)
+{
+ int retval;
+ struct device *dev;
+ struct gb_host_device *gb_nl_hd;
+
+ retval = genl_register_family(&gb_nl_family);
+ if (retval)
+ return retval;
+
+ retval = alloc_chrdev_region(&major_dev, 0, 1, DEVICE_NAME);
+ if (retval)
+ goto err_genl_unregister;
+
+ gb_nl_class = class_create(THIS_MODULE, CLASS_NAME);
+ if (IS_ERR(gb_nl_class)) {
+ retval = PTR_ERR(gb_nl_class);
+ goto err_chrdev_unregister;
+ }
+
+ dev = device_create(gb_nl_class, NULL, major_dev, NULL, DEVICE_NAME);
+ if (IS_ERR(dev)) {
+ retval = PTR_ERR(dev);
+ goto err_class_destroy;
+ }