From patchwork Sun Jan 20 18:33:15 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Andreas_F=C3=A4rber?= X-Patchwork-Id: 156135 Delivered-To: patch@linaro.org Received: by 2002:a02:48:0:0:0:0:0 with SMTP id 69csp5567134jaa; Sun, 20 Jan 2019 10:34:00 -0800 (PST) X-Google-Smtp-Source: ALg8bN4NkFLpFpxopBADhXiLZ0oIByJFTS93OgM0G2xxMkWSxWxHVt2pk8p9EIkBELe+s/uH2B20 X-Received: by 2002:aa7:8286:: with SMTP id s6mr26595075pfm.63.1548009240719; Sun, 20 Jan 2019 10:34:00 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1548009240; cv=none; d=google.com; s=arc-20160816; b=KdsfP2dEYgYipkLwslSDotVyHDa/pvkdCk3RCaWkpYn3BWB7nlmBlqFCHEz16RQ8XQ rMGg1QI1H4h2xT+ziGRuK3VRF6CcmkZ28SsdK6vR71jkVgP1i5QyHwEYcxALmEWXGmsX H6/t76jH/kzb27KyO3yzB6me4HtNhY/kT5P64GNy3kxDPi41qA2IFOcbkzsCXsnlRut9 zyGi3a681S4hmD7Ze7ob+kjnihT1vZrz/eKumPCoIz/M+JEhownLdap2Rm7OVnO8SEEo 1Xl3UDv9BYvhiJJmcOubfzgj2v2hZWAWD3xWtdXLHhGBQjFS1yrU8ZTr1P8pSKcoV8eM qluw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:content-transfer-encoding:mime-version :message-id:date:subject:cc:to:from; bh=9TSEQxwPJOmzE/Er5kS92WGmjrYOuQnLV8bZSJbrnho=; b=OO7Zwu4CS+bUCNi2TV6DDfc4g11WaxGD9GnCBG0+ikVQ81rKVddxcfZCs7DaP6uY+z vZ1Or5P+TcsVxi7W/xpX1p1kEVcdwgRY3JwpWSX1qldk7vI7sjCYyfj9GVedNMf5zUhs fTXJV7TQyPxxWXHHIzp2QfYueTsGtrm7/c6KxtQGLYqv7bKtmqN0+VCwtjq1BpjGrEyV u8wxyFCLjhUsOEoAB4fQmM70YGRKzxV1X1A7zOs2adBqv5GNFCFOab3vnjMHFo2GGfRg v64ms38UVJ6qqSR5dhg6R3YQw+VPn78YhbIjlqlvMLRTXVH26psRz6shSZfYXebkoTN7 kziA== ARC-Authentication-Results: i=1; mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id h1si9893941plt.44.2019.01.20.10.34.00; Sun, 20 Jan 2019 10:34:00 -0800 (PST) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727567AbfATSdt (ORCPT + 23 others); Sun, 20 Jan 2019 13:33:49 -0500 Received: from mx2.suse.de ([195.135.220.15]:38290 "EHLO mx1.suse.de" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1726541AbfATSdt (ORCPT ); Sun, 20 Jan 2019 13:33:49 -0500 X-Virus-Scanned: by amavisd-new at test-mx.suse.de Received: from relay2.suse.de (unknown [195.135.220.254]) by mx1.suse.de (Postfix) with ESMTP id F17E6AD00; Sun, 20 Jan 2019 18:33:45 +0000 (UTC) From: =?utf-8?q?Andreas_F=C3=A4rber?= To: linux-lpwan@lists.infradead.org, linux-wpan@vger.kernel.org Cc: Alexander Aring , Stefan Schmidt , netdev@vger.kernel.org, linux-kernel@vger.kernel.org, =?utf-8?q?Andreas_F=C3=A4rber?= , TL Lim , "David S. Miller" Subject: [RFC net-next] net: Z-Wave driver prototype Date: Sun, 20 Jan 2019 19:33:15 +0100 Message-Id: <20190120183315.22743-1-afaerber@suse.de> X-Mailer: git-send-email 2.16.4 MIME-Version: 1.0 Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Inspired from my LoRa and FSK driver project, I've hacked together a PoC for an in-kernel Z-Wave driver. Tested with Pine64 Z-Wave module. The by now SiLabs ZM5304 module exposes a UART interface that this serdev driver can be attached to, using Device Tree. There didn't appear to be any schematics for the Pine64 Z-Wave adapter, so the reset GPIO handling is still missing. Helpers are implemented for dealing with command, response and respective checksum-based acknowledgements. While by now there is extensive public documentation on Command Classes for individual types of Z-Wave devices, I still couldn't find any proper documentation of the UART protocol below and relied on a former tutorial by Henrik Leidecker Jørgensen documenting commands for device discovery. Tested as external kernel module, so Kconfig/Makefile integration missing. Some ideas where to take this from here: 1) Try to expose this as a net device and leave node communication to userspace via sockets (command/response as skb?) or other means. 2) Make this its own, e.g., drivers/zwave/ subsystem as bus and create individual drivers (iio, gpio, led, etc.) for the various node types. Optionally, implement a zwave-dev driver for generic userspace access to one particular node, similar to spidev. Note: Z-Wave is based on GFSK modulation, so someone with a lot of time might implement it based on the FSK drivers being created alongside LoRa. That speaks against relying on the serial protocol, and it would not work with any tty based userspace applications. Cc: TL Lim Signed-off-by: Andreas Färber --- drivers/net/zwave/Makefile | 1 + drivers/net/zwave/zwave.c | 236 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 237 insertions(+) create mode 100644 drivers/net/zwave/Makefile create mode 100644 drivers/net/zwave/zwave.c -- 2.16.4 diff --git a/drivers/net/zwave/Makefile b/drivers/net/zwave/Makefile new file mode 100644 index 000000000000..1a4171308c79 --- /dev/null +++ b/drivers/net/zwave/Makefile @@ -0,0 +1 @@ +obj-m += zwave.o diff --git a/drivers/net/zwave/zwave.c b/drivers/net/zwave/zwave.c new file mode 100644 index 000000000000..8ac680bbb969 --- /dev/null +++ b/drivers/net/zwave/zwave.c @@ -0,0 +1,236 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Z-Wave + * + * Copyright (c) 2019 Andreas Färber + */ + +#include +#include +#include +#include +#include +#include + +struct zwave_msg_dispatcher { + struct list_head list; + u8 id; + void (*dispatch)(const u8 *data, u8 len, struct zwave_msg_dispatcher *d); +}; + +struct zwave_device { + struct serdev_device *serdev; + + struct completion ack_comp; + struct list_head msg_dispatchers; + + struct zwave_msg_dispatcher node_list_disp; +}; + +static void zwave_add_dispatcher(struct zwave_device *zdev, + struct zwave_msg_dispatcher *entry) +{ + list_add_tail_rcu(&entry->list, &zdev->msg_dispatchers); +} + +static void zwave_remove_dispatcher(struct zwave_device *zdev, + struct zwave_msg_dispatcher *entry) +{ + list_del_rcu(&entry->list); +} + +static u8 zwave_msg_checksum_first(const u8 first, const u8 *data, int len) +{ + u8 chksum; + int i; + + chksum = first; + for (i = 0; i < len; i++) { + chksum ^= data[i]; + } + return ~chksum; +} + +static u8 zwave_msg_checksum(const u8 *data, int len) +{ + return zwave_msg_checksum_first(data[0], data + 1, len - 1); +} + +static int zwave_send_msg(struct zwave_device *zdev, const u8 *data, int data_len, + unsigned long timeout) +{ + u8 chksum; + u8 buf[2]; + int ret; + + reinit_completion(&zdev->ack_comp); + + buf[0] = 0x01; + buf[1] = data_len + 1; + + chksum = zwave_msg_checksum_first(buf[1], data, data_len); + dev_dbg(&zdev->serdev->dev, "checksum: 0x%02x\n", (unsigned int)chksum); + + ret = serdev_device_write(zdev->serdev, buf, 2, timeout); + if (ret < 0) + return ret; + if (ret > 0 && ret < 2) + return -EIO; + + ret = serdev_device_write(zdev->serdev, data, data_len, timeout); + if (ret < 0) + return ret; + if (ret > 0 && ret < data_len) + return -EIO; + + ret = serdev_device_write(zdev->serdev, &chksum, 1, timeout); + if (ret < 0) + return ret; + + timeout = wait_for_completion_timeout(&zdev->ack_comp, timeout); + if (!timeout) + return -ETIMEDOUT; + + return 0; +} + +static void zwave_node_list_report(const u8 *data, u8 len, + struct zwave_msg_dispatcher *d) +{ + struct zwave_device *zdev = container_of(d, struct zwave_device, node_list_disp); + struct device *dev = &zdev->serdev->dev; + int i, j; + + if (len != 36) { + dev_err(dev, "node list report unexpected length (%u)\n", (unsigned int)len); + return; + }; + + dev_info(dev, "node list report\n"); + for (i = 0; i < data[4]; i++) { + u8 tmp = data[5 + i]; + for (j = 0; j < 8; j++) { + if (tmp & BIT(j)) + dev_info(dev, "node list data %u: node id %u\n", i + 1, i * 8 + j + 1); + } + } +} + +static int zwave_receive_buf(struct serdev_device *sdev, const u8 *data, size_t count) +{ + struct zwave_device *zdev = serdev_device_get_drvdata(sdev); + struct zwave_msg_dispatcher *e; + u8 buf[1] = { 0x06 }; + u8 msglen; + u8 chksum; + + dev_dbg(&sdev->dev, "Receive (%zu)\n", count); + + if (data[0] == 0x06) { + dev_info(&sdev->dev, "ACK received\n"); + complete(&zdev->ack_comp); + return 1; + } else { + if (count < 2) + return 0; + msglen = data[1]; + if (count < 2 + msglen) { + dev_dbg(&sdev->dev, "%s: received %zu, expecting %u\n", __func__, count, 2 + (unsigned int)msglen); + return 0; + } + print_hex_dump_bytes("received: ", DUMP_PREFIX_OFFSET, data, 2 + msglen); + if (msglen > 0) { + chksum = zwave_msg_checksum(data + 1, msglen); + if (data[1 + msglen] == chksum) { + dev_info(&sdev->dev, "sending ACK\n"); + serdev_device_write_buf(sdev, buf, 1); + } else { + dev_warn(&sdev->dev, "checksum mismatch\n"); + return 2 + msglen; + } + } + list_for_each_entry(e, &zdev->msg_dispatchers, list) { + if (msglen > 2 && e->id == data[3]) + e->dispatch(data + 2, msglen ? msglen - 1 : 0, e); + } + return 2 + msglen; + } +} + +static const struct serdev_device_ops zwave_serdev_client_ops = { + .receive_buf = zwave_receive_buf, + .write_wakeup = serdev_device_write_wakeup, +}; + +static int zwave_probe(struct serdev_device *sdev) +{ + struct zwave_device *zdev; + int ret; + const u8 msg[] = { 0x00, 0x02 }; + //const u8 msg[] = { 0x00, 0x41, 1 }; + + dev_dbg(&sdev->dev, "Probing"); + + zdev = devm_kzalloc(&sdev->dev, sizeof(*zdev), GFP_KERNEL); + if (!zdev) + return -ENOMEM; + + zdev->serdev = sdev; + init_completion(&zdev->ack_comp); + INIT_LIST_HEAD(&zdev->msg_dispatchers); + serdev_device_set_drvdata(sdev, zdev); + + ret = serdev_device_open(sdev); + if (ret) { + dev_err(&sdev->dev, "Failed to open (%d)\n", ret); + return ret; + } + + serdev_device_set_baudrate(sdev, 115200); + serdev_device_set_flow_control(sdev, false); + serdev_device_set_client_ops(sdev, &zwave_serdev_client_ops); + + zdev->node_list_disp.id = 0x02; + zdev->node_list_disp.dispatch = zwave_node_list_report; + zwave_add_dispatcher(zdev, &zdev->node_list_disp); + + ret = zwave_send_msg(zdev, msg, sizeof(msg), HZ); + if (ret) { + dev_warn(&sdev->dev, "Failed to send (%d)\n", ret); + } + + dev_dbg(&sdev->dev, "Done.\n"); + + return 0; +} + +static void zwave_remove(struct serdev_device *sdev) +{ + struct zwave_device *zdev = serdev_device_get_drvdata(sdev); + + serdev_device_close(sdev); + + zwave_remove_dispatcher(zdev, &zdev->node_list_disp); + + dev_dbg(&sdev->dev, "Removed\n"); +} + +static const struct of_device_id zwave_of_match[] = { + { .compatible = "zwave,zwave" }, /* XXX */ + {} +}; +MODULE_DEVICE_TABLE(of, zwave_of_match); + +static struct serdev_device_driver zwave_serdev_driver = { + .probe = zwave_probe, + .remove = zwave_remove, + .driver = { + .name = "zwave", + .of_match_table = zwave_of_match, + }, +}; +module_serdev_device_driver(zwave_serdev_driver); + +MODULE_DESCRIPTION("Z-Wave serdev driver"); +MODULE_AUTHOR("Andreas Färber "); +MODULE_LICENSE("GPL");