Remove 'ucmb' packages
git-svn-id: svn://svn.openwrt.org/openwrt/packages@28606 3c298f89-4303-0410-b956-a3cf2f4a3e73
This commit is contained in:
parent
963bb3df2e
commit
041ceda9e3
@ -1,41 +0,0 @@
|
||||
#
|
||||
# Copyright (C) 2009 OpenWrt.org
|
||||
#
|
||||
# This is free software, licensed under the GNU General Public License v2.
|
||||
# See /LICENSE for more information.
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=ucmb-tools
|
||||
PKG_RELEASE:=1
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
|
||||
define Package/ucmb-tools
|
||||
SECTION:=utils
|
||||
CATEGORY:=Utilities
|
||||
TITLE:=Microcontroller Message Bus tools
|
||||
DEPENDS:=+kmod-ucmb
|
||||
MAINTAINER:=Michael Buesch <mb@bu3sch.de>
|
||||
endef
|
||||
|
||||
define Package/ucmb-tools/description
|
||||
Debugging tool for UCMB
|
||||
endef
|
||||
|
||||
define Build/Prepare
|
||||
$(CP) ./tools/* $(PKG_BUILD_DIR)/
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
$(MAKE) -C $(PKG_BUILD_DIR) \
|
||||
$(TARGET_CONFIGURE_OPTS) CFLAGS="$(TARGET_CFLAGS) -I$(LINUX_DIR)/include"
|
||||
endef
|
||||
|
||||
define Package/ucmb-tools/install
|
||||
$(INSTALL_DIR) $(1)/usr/bin
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/ucmb $(1)/usr/bin/
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,ucmb-tools))
|
@ -1,17 +0,0 @@
|
||||
INSTDIR = $(prefix)/usr/bin
|
||||
|
||||
all: ucmb
|
||||
|
||||
ucmb: ucmb.o
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) $^ $(LDLIBS) -o $@
|
||||
$(STRIP) $@
|
||||
|
||||
%.o: %.c
|
||||
$(CC) -c $(CFLAGS) $^ -o $@
|
||||
|
||||
install: ucmb
|
||||
$(INSTALL) -d $(INSTDIR)
|
||||
$(INSTALL) -m 0755 -o 0 -g 0 ucmb $(INSTDIR)
|
||||
|
||||
clean:
|
||||
rm -f ucmb *.o
|
@ -1,120 +0,0 @@
|
||||
/*
|
||||
* Microcontroller Message Bus
|
||||
* Userspace commandline utility
|
||||
*
|
||||
* Copyright (c) 2009 Michael Buesch <mb@bu3sch.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <linux/ioctl.h>
|
||||
|
||||
|
||||
#define UCMB_DEV "/dev/ucmb"
|
||||
|
||||
#define __UCMB_IOCTL ('U'|'C'|'M'|'B')
|
||||
#define UCMB_IOCTL_RESETUC _IO(__UCMB_IOCTL, 0)
|
||||
#define UCMB_IOCTL_GMSGDELAY _IOR(__UCMB_IOCTL, 1, unsigned int)
|
||||
#define UCMB_IOCTL_SMSGDELAY _IOW(__UCMB_IOCTL, 2, unsigned int)
|
||||
|
||||
|
||||
static void usage(int argc, char **argv)
|
||||
{
|
||||
fprintf(stderr, "Usage: %s read|write|reset [" UCMB_DEV "]\n", argv[0]);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *command, *devpath = UCMB_DEV;
|
||||
int res, errcode = 0;
|
||||
int ucmb_fd;
|
||||
char *buf;
|
||||
size_t count, buflen;
|
||||
ssize_t nrbytes;
|
||||
|
||||
if (argc != 2 && argc != 3) {
|
||||
usage(argc, argv);
|
||||
return 1;
|
||||
}
|
||||
if (argc == 3)
|
||||
devpath = argv[2];
|
||||
command = argv[1];
|
||||
|
||||
ucmb_fd = open(devpath, O_RDWR);
|
||||
if (ucmb_fd == -1) {
|
||||
fprintf(stderr, "Failed to open %s\n", UCMB_DEV);
|
||||
errcode = 1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
buflen = 4096;
|
||||
buf = malloc(buflen);
|
||||
if (!buf) {
|
||||
fprintf(stderr, "Out of memory\n");
|
||||
errcode = 1;
|
||||
goto out_close;
|
||||
}
|
||||
|
||||
if (strcasecmp(command, "read") == 0) {
|
||||
nrbytes = read(ucmb_fd, buf, buflen);
|
||||
if (nrbytes < 0) {
|
||||
fprintf(stderr, "Failed to read UCMB: %s (%d)\n",
|
||||
strerror(errno), errno);
|
||||
errcode = 1;
|
||||
goto out_free;
|
||||
}
|
||||
if (fwrite(buf, nrbytes, 1, stdout) != 1) {
|
||||
fprintf(stderr, "Failed to write stdout\n");
|
||||
errcode = 1;
|
||||
goto out_free;
|
||||
}
|
||||
} else if (strcasecmp(command, "write") == 0) {
|
||||
count = fread(buf, 1, buflen, stdin);
|
||||
if (!count) {
|
||||
fprintf(stderr, "Failed to read stdin\n");
|
||||
errcode = 1;
|
||||
goto out_free;
|
||||
}
|
||||
nrbytes = write(ucmb_fd, buf, count);
|
||||
if (nrbytes != count) {
|
||||
fprintf(stderr, "Failed to write UCMB: %s (%d)\n",
|
||||
strerror(errno), errno);
|
||||
errcode = 1;
|
||||
goto out_free;
|
||||
}
|
||||
} else if (strcasecmp(command, "reset") == 0) {
|
||||
res = ioctl(ucmb_fd, UCMB_IOCTL_RESETUC);
|
||||
if (res) {
|
||||
fprintf(stderr, "RESET ioctl failed: %s (%d)\n",
|
||||
strerror(res < 0 ? -res : res), res);
|
||||
errcode = 1;
|
||||
goto out_free;
|
||||
}
|
||||
} else {
|
||||
usage(argc, argv);
|
||||
errcode = 1;
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
out_free:
|
||||
free(buf);
|
||||
out_close:
|
||||
close(ucmb_fd);
|
||||
out:
|
||||
return errcode;
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
#
|
||||
# Copyright (C) 2010 OpenWrt.org
|
||||
#
|
||||
# This is free software, licensed under the GNU General Public License v2.
|
||||
# See /LICENSE for more information.
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
include $(INCLUDE_DIR)/kernel.mk
|
||||
|
||||
PKG_NAME:=ucmb
|
||||
PKG_RELEASE:=2
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
|
||||
define KernelPackage/ucmb
|
||||
SUBMENU:=Other modules
|
||||
DEPENDS:=@!LINUX_2_4 \
|
||||
@!LINUX_2_6_21 @!LINUX_2_6_22 @!LINUX_2_6_23 @!LINUX_2_6_24 \
|
||||
@!LINUX_2_6_25 @!LINUX_2_6_26 @!LINUX_2_6_27 \
|
||||
+kmod-spi-gpio +kmod-crc16
|
||||
KCONFIG:=CONFIG_SPI=y \
|
||||
CONFIG_SPI_MASTER=y
|
||||
TITLE:=Microcontroller Message Bus
|
||||
FILES:=$(PKG_BUILD_DIR)/ucmb.$(LINUX_KMOD_SUFFIX)
|
||||
AUTOLOAD:=$(call AutoLoad,93,ucmb)
|
||||
MAINTAINER:=Michael Buesch <mb@bu3sch.de>
|
||||
endef
|
||||
|
||||
define KernelPackage/ucmb/description
|
||||
The Microcontroller Message Bus is a tiny SPI-GPIO based communication
|
||||
channel used to talk to microcontrollers over GPIO pins.
|
||||
The lowlevel protocol is CRC16 protected, so one can be pretty sure
|
||||
that the data transmitted and received through the /dev/ucmb node is not corrupted.
|
||||
So no further checks should be needed at upper protocol layers.
|
||||
The device node considers every read/write to be one packet. The maximum packet
|
||||
size is either PAGE_SIZE (at least 4kb) or the microcontroller specific packet size
|
||||
limit, which is likely to be a lot smaller than PAGE_SIZE.
|
||||
To register an ucmb device, simply create a struct ucmb_platform_data
|
||||
and register it via ucmb_device_register() from another kernel module.
|
||||
Example implementations for the microcontroller-side code can be found in
|
||||
the utils/ucmb/microcontroller_examples subdirectory of the OpenWRT packages feed.
|
||||
endef
|
||||
|
||||
define Build/Prepare
|
||||
$(CP) ./driver/* $(PKG_BUILD_DIR)/
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
$(MAKE) -C "$(LINUX_DIR)" \
|
||||
CROSS_COMPILE="$(TARGET_CROSS)" \
|
||||
ARCH="$(LINUX_KARCH)" \
|
||||
SUBDIRS="$(PKG_BUILD_DIR)" \
|
||||
EXTRA_CFLAGS="$(BUILDFLAGS)" \
|
||||
modules
|
||||
endef
|
||||
|
||||
define Build/InstallDev
|
||||
$(INSTALL_DIR) $(1)/usr/include
|
||||
$(CP) \
|
||||
$(PKG_BUILD_DIR)/ucmb.h \
|
||||
$(1)/usr/include
|
||||
endef
|
||||
|
||||
$(eval $(call KernelPackage,ucmb))
|
@ -1 +0,0 @@
|
||||
obj-m := ucmb.o
|
@ -1,656 +0,0 @@
|
||||
/*
|
||||
* Microcontroller Message Bus
|
||||
* Linux kernel driver
|
||||
*
|
||||
* Copyright (c) 2009-2010 Michael Buesch <mb@bu3sch.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include "ucmb.h"
|
||||
|
||||
#include <linux/version.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/spi/spi_gpio.h>
|
||||
#include <linux/spi/spi_bitbang.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gfp.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/crc16.h>
|
||||
#include <linux/sched.h>
|
||||
|
||||
#include <asm/uaccess.h>
|
||||
|
||||
|
||||
#define PFX "ucmb: "
|
||||
|
||||
#undef DEBUG
|
||||
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("Microcontroller Message Bus");
|
||||
MODULE_AUTHOR("Michael Buesch");
|
||||
|
||||
|
||||
struct ucmb {
|
||||
struct mutex mutex;
|
||||
|
||||
bool is_open;
|
||||
|
||||
unsigned int chunk_size;
|
||||
unsigned int msg_delay_usec;
|
||||
unsigned int gpio_reset;
|
||||
bool reset_activelow;
|
||||
|
||||
/* Misc character device driver */
|
||||
struct miscdevice mdev;
|
||||
struct file_operations mdev_fops;
|
||||
|
||||
/* SPI driver */
|
||||
struct spi_device *sdev;
|
||||
|
||||
/* SPI-GPIO driver */
|
||||
struct spi_gpio_platform_data spi_gpio_pdata;
|
||||
struct platform_device spi_gpio_pdev;
|
||||
};
|
||||
|
||||
#define UCMB_MAX_MSG_DELAY (10 * 1000 * 1000) /* 10 seconds */
|
||||
|
||||
|
||||
struct ucmb_message_hdr {
|
||||
__le16 magic; /* UCMB_MAGIC */
|
||||
__le16 len; /* Payload length (excluding header and footer) */
|
||||
} __attribute__((packed));
|
||||
|
||||
struct ucmb_message_footer {
|
||||
__le16 crc; /* CRC of the header + payload. */
|
||||
} __attribute__((packed));
|
||||
|
||||
struct ucmb_status {
|
||||
__le16 magic; /* UCMB_MAGIC */
|
||||
__le16 code; /* enum ucmb_status_code */
|
||||
} __attribute__((packed));
|
||||
|
||||
#define UCMB_MAGIC 0x1337
|
||||
|
||||
enum ucmb_status_code {
|
||||
UCMB_STAT_OK = 0,
|
||||
UCMB_STAT_EPROTO, /* Protocol format error */
|
||||
UCMB_STAT_ENOMEM, /* Out of memory */
|
||||
UCMB_STAT_E2BIG, /* Message too big */
|
||||
UCMB_STAT_ECRC, /* CRC error */
|
||||
};
|
||||
|
||||
|
||||
static int ucmb_spi_busnum_count = 1337;
|
||||
static int ucmb_pdev_id_count;
|
||||
|
||||
|
||||
static int __devinit ucmb_spi_probe(struct spi_device *sdev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __devexit ucmb_spi_remove(struct spi_device *sdev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct spi_driver ucmb_spi_driver = {
|
||||
.driver = {
|
||||
.name = "ucmb",
|
||||
.bus = &spi_bus_type,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = ucmb_spi_probe,
|
||||
.remove = __devexit_p(ucmb_spi_remove),
|
||||
};
|
||||
|
||||
static void ucmb_toggle_reset_line(struct ucmb *ucmb, bool active)
|
||||
{
|
||||
if (ucmb->reset_activelow)
|
||||
active = !active;
|
||||
gpio_set_value(ucmb->gpio_reset, active);
|
||||
}
|
||||
|
||||
static int ucmb_reset_microcontroller(struct ucmb *ucmb)
|
||||
{
|
||||
if (ucmb->gpio_reset == UCMB_NO_RESET)
|
||||
return -ENODEV;
|
||||
|
||||
ucmb_toggle_reset_line(ucmb, 1);
|
||||
msleep(50);
|
||||
ucmb_toggle_reset_line(ucmb, 0);
|
||||
msleep(50);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ucmb_status_code_to_errno(enum ucmb_status_code code)
|
||||
{
|
||||
switch (code) {
|
||||
case UCMB_STAT_OK:
|
||||
return 0;
|
||||
case UCMB_STAT_EPROTO:
|
||||
return -EPROTO;
|
||||
case UCMB_STAT_ENOMEM:
|
||||
return -ENOMEM;
|
||||
case UCMB_STAT_E2BIG:
|
||||
return -E2BIG;
|
||||
case UCMB_STAT_ECRC:
|
||||
return -EBADMSG;
|
||||
}
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
static inline struct ucmb * filp_to_ucmb(struct file *filp)
|
||||
{
|
||||
return container_of(filp->f_op, struct ucmb, mdev_fops);
|
||||
}
|
||||
|
||||
static int ucmb_open(struct inode *inode, struct file *filp)
|
||||
{
|
||||
struct ucmb *ucmb = filp_to_ucmb(filp);
|
||||
int err = 0;
|
||||
|
||||
mutex_lock(&ucmb->mutex);
|
||||
|
||||
if (ucmb->is_open) {
|
||||
err = -EBUSY;
|
||||
goto out_unlock;
|
||||
}
|
||||
ucmb->is_open = 1;
|
||||
ucmb->msg_delay_usec = 0;
|
||||
|
||||
out_unlock:
|
||||
mutex_unlock(&ucmb->mutex);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int ucmb_release(struct inode *inode, struct file *filp)
|
||||
{
|
||||
struct ucmb *ucmb = filp_to_ucmb(filp);
|
||||
|
||||
mutex_lock(&ucmb->mutex);
|
||||
WARN_ON(!ucmb->is_open);
|
||||
ucmb->is_open = 0;
|
||||
mutex_unlock(&ucmb->mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)
|
||||
static long ucmb_ioctl(struct file *filp,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
#else
|
||||
static int ucmb_ioctl(struct inode *inode, struct file *filp,
|
||||
unsigned int cmd, unsigned long arg)
|
||||
#endif
|
||||
{
|
||||
struct ucmb *ucmb = filp_to_ucmb(filp);
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&ucmb->mutex);
|
||||
switch (cmd) {
|
||||
case UCMB_IOCTL_RESETUC:
|
||||
ret = ucmb_reset_microcontroller(ucmb);
|
||||
break;
|
||||
case UCMB_IOCTL_GMSGDELAY:
|
||||
if (put_user(ucmb->msg_delay_usec, (unsigned int __user *)arg)) {
|
||||
ret = -EFAULT;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case UCMB_IOCTL_SMSGDELAY: {
|
||||
unsigned int msg_delay_usec;
|
||||
|
||||
if (get_user(msg_delay_usec, (unsigned int __user *)arg)) {
|
||||
ret = -EFAULT;
|
||||
break;
|
||||
}
|
||||
if (msg_delay_usec > UCMB_MAX_MSG_DELAY) {
|
||||
ret = -E2BIG;
|
||||
break;
|
||||
}
|
||||
ucmb->msg_delay_usec = msg_delay_usec;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
mutex_unlock(&ucmb->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ucmb_spi_write(struct ucmb *ucmb, const void *_buf, size_t size)
|
||||
{
|
||||
const u8 *buf = _buf;
|
||||
size_t i, chunk_size, current_size;
|
||||
int err = 0;
|
||||
|
||||
chunk_size = ucmb->chunk_size ? : size;
|
||||
for (i = 0; i < size; i += chunk_size) {
|
||||
current_size = chunk_size;
|
||||
if (i + current_size > size)
|
||||
current_size = size - i;
|
||||
err = spi_write(ucmb->sdev, buf + i, current_size);
|
||||
if (err)
|
||||
goto out;
|
||||
if (ucmb->chunk_size && need_resched())
|
||||
msleep(1);
|
||||
}
|
||||
out:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int ucmb_spi_read(struct ucmb *ucmb, void *_buf, size_t size)
|
||||
{
|
||||
u8 *buf = _buf;
|
||||
size_t i, chunk_size, current_size;
|
||||
int err = 0;
|
||||
|
||||
chunk_size = ucmb->chunk_size ? : size;
|
||||
for (i = 0; i < size; i += chunk_size) {
|
||||
current_size = chunk_size;
|
||||
if (i + current_size > size)
|
||||
current_size = size - i;
|
||||
err = spi_read(ucmb->sdev, buf + i, current_size);
|
||||
if (err)
|
||||
goto out;
|
||||
if (ucmb->chunk_size && need_resched())
|
||||
msleep(1);
|
||||
}
|
||||
out:
|
||||
return err;
|
||||
}
|
||||
|
||||
static ssize_t ucmb_read(struct file *filp, char __user *user_buf,
|
||||
size_t size, loff_t *offp)
|
||||
{
|
||||
struct ucmb *ucmb = filp_to_ucmb(filp);
|
||||
u8 *buf;
|
||||
int res, err;
|
||||
struct ucmb_message_hdr hdr;
|
||||
struct ucmb_message_footer footer;
|
||||
struct ucmb_status status = { .magic = cpu_to_le16(UCMB_MAGIC), };
|
||||
u16 crc = 0xFFFF;
|
||||
|
||||
mutex_lock(&ucmb->mutex);
|
||||
|
||||
size = min_t(size_t, size, PAGE_SIZE);
|
||||
|
||||
err = -ENOMEM;
|
||||
buf = (char *)__get_free_page(GFP_KERNEL);
|
||||
if (!buf)
|
||||
goto out;
|
||||
|
||||
err = ucmb_spi_read(ucmb, &hdr, sizeof(hdr));
|
||||
if (err)
|
||||
goto out_free;
|
||||
#ifdef DEBUG
|
||||
printk(KERN_DEBUG PFX "Received message header 0x%04X 0x%04X\n",
|
||||
le16_to_cpu(hdr.magic), le16_to_cpu(hdr.len));
|
||||
#endif
|
||||
err = -EPROTO;
|
||||
if (hdr.magic != cpu_to_le16(UCMB_MAGIC))
|
||||
goto out_free;
|
||||
err = -ENOBUFS;
|
||||
if (size < le16_to_cpu(hdr.len))
|
||||
goto out_free;
|
||||
size = le16_to_cpu(hdr.len);
|
||||
err = ucmb_spi_read(ucmb, buf, size);
|
||||
if (err)
|
||||
goto out_free;
|
||||
err = ucmb_spi_read(ucmb, &footer, sizeof(footer));
|
||||
if (err)
|
||||
goto out_free;
|
||||
|
||||
crc = crc16(crc, (u8 *)&hdr, sizeof(hdr));
|
||||
crc = crc16(crc, buf, size);
|
||||
crc ^= 0xFFFF;
|
||||
if (crc != le16_to_cpu(footer.crc)) {
|
||||
err = -EPROTO;
|
||||
status.code = UCMB_STAT_ECRC;
|
||||
goto out_send_status;
|
||||
}
|
||||
|
||||
if (copy_to_user(user_buf, buf, size)) {
|
||||
err = -EFAULT;
|
||||
status.code = UCMB_STAT_ENOMEM;
|
||||
goto out_send_status;
|
||||
}
|
||||
|
||||
status.code = UCMB_STAT_OK;
|
||||
err = 0;
|
||||
|
||||
out_send_status:
|
||||
res = ucmb_spi_write(ucmb, &status, sizeof(status));
|
||||
if (res && !err)
|
||||
err = res;
|
||||
out_free:
|
||||
free_page((unsigned long)buf);
|
||||
out:
|
||||
mutex_unlock(&ucmb->mutex);
|
||||
|
||||
return err ? err : size;
|
||||
}
|
||||
|
||||
static ssize_t ucmb_write(struct file *filp, const char __user *user_buf,
|
||||
size_t size, loff_t *offp)
|
||||
{
|
||||
struct ucmb *ucmb = filp_to_ucmb(filp);
|
||||
u8 *buf;
|
||||
int err;
|
||||
struct ucmb_message_hdr hdr = { .magic = cpu_to_le16(UCMB_MAGIC), };
|
||||
struct ucmb_message_footer footer = { .crc = 0xFFFF, };
|
||||
struct ucmb_status status;
|
||||
|
||||
mutex_lock(&ucmb->mutex);
|
||||
|
||||
err = -ENOMEM;
|
||||
buf = (char *)__get_free_page(GFP_KERNEL);
|
||||
if (!buf)
|
||||
goto out;
|
||||
|
||||
size = min_t(size_t, PAGE_SIZE, size);
|
||||
err = -EFAULT;
|
||||
if (copy_from_user(buf, user_buf, size))
|
||||
goto out_free;
|
||||
hdr.len = cpu_to_le16(size);
|
||||
|
||||
footer.crc = crc16(footer.crc, (u8 *)&hdr, sizeof(hdr));
|
||||
footer.crc = crc16(footer.crc, buf, size);
|
||||
footer.crc ^= 0xFFFF;
|
||||
|
||||
err = ucmb_spi_write(ucmb, &hdr, sizeof(hdr));
|
||||
if (err)
|
||||
goto out_free;
|
||||
err = ucmb_spi_write(ucmb, buf, size);
|
||||
if (err)
|
||||
goto out_free;
|
||||
err = ucmb_spi_write(ucmb, &footer, sizeof(footer));
|
||||
if (err)
|
||||
goto out_free;
|
||||
|
||||
if (ucmb->msg_delay_usec) {
|
||||
/* The microcontroller deserves some time to process the message. */
|
||||
if (ucmb->msg_delay_usec >= 1000000) {
|
||||
ssleep(ucmb->msg_delay_usec / 1000000);
|
||||
msleep(DIV_ROUND_UP(ucmb->msg_delay_usec % 1000000, 1000));
|
||||
} else if (ucmb->msg_delay_usec >= 1000) {
|
||||
msleep(DIV_ROUND_UP(ucmb->msg_delay_usec, 1000));
|
||||
} else
|
||||
udelay(ucmb->msg_delay_usec);
|
||||
}
|
||||
|
||||
/* Get the status code. */
|
||||
err = ucmb_spi_read(ucmb, &status, sizeof(status));
|
||||
if (err)
|
||||
goto out_free;
|
||||
#ifdef DEBUG
|
||||
printk(KERN_DEBUG PFX "Sent message. Status report: 0x%04X 0x%04X\n",
|
||||
le16_to_cpu(status.magic), le16_to_cpu(status.code));
|
||||
#endif
|
||||
err = -EPROTO;
|
||||
if (status.magic != cpu_to_le16(UCMB_MAGIC))
|
||||
goto out_free;
|
||||
err = ucmb_status_code_to_errno(le16_to_cpu(status.code));
|
||||
if (err)
|
||||
goto out_free;
|
||||
|
||||
out_free:
|
||||
free_page((unsigned long)buf);
|
||||
out:
|
||||
mutex_unlock(&ucmb->mutex);
|
||||
|
||||
return err ? err : size;
|
||||
}
|
||||
|
||||
static int __devinit ucmb_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct ucmb_platform_data *pdata;
|
||||
struct ucmb *ucmb;
|
||||
int err;
|
||||
const int bus_num = ucmb_spi_busnum_count++;
|
||||
struct spi_bitbang *bb;
|
||||
|
||||
pdata = pdev->dev.platform_data;
|
||||
if (!pdata)
|
||||
return -ENXIO;
|
||||
|
||||
ucmb = kzalloc(sizeof(struct ucmb), GFP_KERNEL);
|
||||
if (!ucmb)
|
||||
return -ENOMEM;
|
||||
mutex_init(&ucmb->mutex);
|
||||
ucmb->gpio_reset = pdata->gpio_reset;
|
||||
ucmb->reset_activelow = pdata->reset_activelow;
|
||||
ucmb->chunk_size = pdata->chunk_size;
|
||||
|
||||
#ifdef CONFIG_PREEMPT
|
||||
/* A preemptible kernel does not need to sleep between
|
||||
* chunks, because it can sleep at desire (if IRQs are enabled).
|
||||
* So transmit/receive it all in one go. */
|
||||
ucmb->chunk_size = 0;
|
||||
#endif
|
||||
|
||||
/* Create the SPI GPIO bus master. */
|
||||
|
||||
#ifdef CONFIG_SPI_GPIO_MODULE
|
||||
err = request_module("spi_gpio");
|
||||
if (err)
|
||||
printk(KERN_WARNING PFX "Failed to request spi_gpio module\n");
|
||||
#endif /* CONFIG_SPI_GPIO_MODULE */
|
||||
|
||||
ucmb->spi_gpio_pdata.sck = pdata->gpio_sck;
|
||||
ucmb->spi_gpio_pdata.mosi = pdata->gpio_mosi;
|
||||
ucmb->spi_gpio_pdata.miso = pdata->gpio_miso;
|
||||
ucmb->spi_gpio_pdata.num_chipselect = 1;
|
||||
|
||||
ucmb->spi_gpio_pdev.name = "spi_gpio";
|
||||
ucmb->spi_gpio_pdev.id = bus_num;
|
||||
ucmb->spi_gpio_pdev.dev.platform_data = &ucmb->spi_gpio_pdata;
|
||||
|
||||
err = platform_device_register(&ucmb->spi_gpio_pdev);
|
||||
if (err) {
|
||||
printk(KERN_ERR PFX "Failed to register SPI-GPIO platform device\n");
|
||||
goto err_free_ucmb;
|
||||
}
|
||||
bb = platform_get_drvdata(&ucmb->spi_gpio_pdev);
|
||||
if (!bb || !bb->master) {
|
||||
printk(KERN_ERR PFX "No bitbanged master device found.\n");
|
||||
goto err_unreg_spi_gpio_pdev;
|
||||
}
|
||||
|
||||
/* Create the SPI device. */
|
||||
|
||||
ucmb->sdev = spi_alloc_device(bb->master);
|
||||
if (!ucmb->sdev) {
|
||||
printk(KERN_ERR PFX "Failed to allocate SPI device\n");
|
||||
goto err_unreg_spi_gpio_pdev;
|
||||
}
|
||||
ucmb->sdev->max_speed_hz = pdata->max_speed_hz;
|
||||
ucmb->sdev->chip_select = 0;
|
||||
ucmb->sdev->mode = pdata->mode;
|
||||
strlcpy(ucmb->sdev->modalias, "ucmb", /* We are the SPI driver. */
|
||||
sizeof(ucmb->sdev->modalias));
|
||||
ucmb->sdev->controller_data = (void *)pdata->gpio_cs;
|
||||
err = spi_add_device(ucmb->sdev);
|
||||
if (err) {
|
||||
printk(KERN_ERR PFX "Failed to add SPI device\n");
|
||||
goto err_free_spi_device;
|
||||
}
|
||||
|
||||
/* Initialize the RESET line. */
|
||||
|
||||
if (pdata->gpio_reset != UCMB_NO_RESET) {
|
||||
err = gpio_request(pdata->gpio_reset, pdata->name);
|
||||
if (err) {
|
||||
printk(KERN_ERR PFX
|
||||
"Failed to request RESET GPIO line\n");
|
||||
goto err_unreg_spi_device;
|
||||
}
|
||||
err = gpio_direction_output(pdata->gpio_reset,
|
||||
pdata->reset_activelow);
|
||||
if (err) {
|
||||
printk(KERN_ERR PFX
|
||||
"Failed to set RESET GPIO direction\n");
|
||||
goto err_free_reset_gpio;
|
||||
}
|
||||
ucmb_reset_microcontroller(ucmb);
|
||||
}
|
||||
|
||||
/* Create the Misc char device. */
|
||||
|
||||
ucmb->mdev.minor = MISC_DYNAMIC_MINOR;
|
||||
ucmb->mdev.name = pdata->name;
|
||||
ucmb->mdev.parent = &pdev->dev;
|
||||
ucmb->mdev_fops.open = ucmb_open;
|
||||
ucmb->mdev_fops.release = ucmb_release;
|
||||
ucmb->mdev_fops.read = ucmb_read;
|
||||
ucmb->mdev_fops.write = ucmb_write;
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)
|
||||
ucmb->mdev_fops.unlocked_ioctl = ucmb_ioctl;
|
||||
#else
|
||||
ucmb->mdev_fops.ioctl = ucmb_ioctl;
|
||||
#endif
|
||||
ucmb->mdev.fops = &ucmb->mdev_fops;
|
||||
|
||||
err = misc_register(&ucmb->mdev);
|
||||
if (err) {
|
||||
printk(KERN_ERR PFX "Failed to register miscdev %s\n",
|
||||
ucmb->mdev.name);
|
||||
goto err_free_reset_gpio;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, ucmb);
|
||||
|
||||
printk(KERN_INFO PFX "Registered message bus \"%s\"\n", pdata->name);
|
||||
|
||||
return 0;
|
||||
|
||||
err_free_reset_gpio:
|
||||
if (pdata->gpio_reset != UCMB_NO_RESET)
|
||||
gpio_free(pdata->gpio_reset);
|
||||
err_unreg_spi_device:
|
||||
spi_unregister_device(ucmb->sdev);
|
||||
err_free_spi_device:
|
||||
spi_dev_put(ucmb->sdev);
|
||||
err_unreg_spi_gpio_pdev:
|
||||
platform_device_unregister(&ucmb->spi_gpio_pdev);
|
||||
err_free_ucmb:
|
||||
kfree(ucmb);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int __devexit ucmb_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct ucmb *ucmb = platform_get_drvdata(pdev);
|
||||
int err;
|
||||
|
||||
err = misc_deregister(&ucmb->mdev);
|
||||
if (err) {
|
||||
printk(KERN_ERR PFX "Failed to unregister miscdev %s\n",
|
||||
ucmb->mdev.name);
|
||||
}
|
||||
if (ucmb->gpio_reset != UCMB_NO_RESET)
|
||||
gpio_free(ucmb->gpio_reset);
|
||||
spi_unregister_device(ucmb->sdev);
|
||||
spi_dev_put(ucmb->sdev);
|
||||
platform_device_unregister(&ucmb->spi_gpio_pdev);
|
||||
|
||||
kfree(ucmb);
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver ucmb_driver = {
|
||||
.driver = {
|
||||
.name = "ucmb",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = ucmb_probe,
|
||||
.remove = __devexit_p(ucmb_remove),
|
||||
};
|
||||
|
||||
int ucmb_device_register(struct ucmb_platform_data *pdata)
|
||||
{
|
||||
struct platform_device *pdev;
|
||||
int err;
|
||||
|
||||
pdev = platform_device_alloc("ucmb", ucmb_pdev_id_count++);
|
||||
if (!pdev) {
|
||||
printk(KERN_ERR PFX "Failed to allocate platform device.\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
err = platform_device_add_data(pdev, pdata, sizeof(*pdata));
|
||||
if (err) {
|
||||
printk(KERN_ERR PFX "Failed to add platform data.\n");
|
||||
platform_device_put(pdev);
|
||||
return err;
|
||||
}
|
||||
err = platform_device_add(pdev);
|
||||
if (err) {
|
||||
printk(KERN_ERR PFX "Failed to register platform device.\n");
|
||||
platform_device_put(pdev);
|
||||
return err;
|
||||
}
|
||||
pdata->pdev = pdev;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(ucmb_device_register);
|
||||
|
||||
void ucmb_device_unregister(struct ucmb_platform_data *pdata)
|
||||
{
|
||||
if (!pdata->pdev)
|
||||
return;
|
||||
platform_device_unregister(pdata->pdev);
|
||||
platform_device_put(pdata->pdev);
|
||||
pdata->pdev = NULL;
|
||||
}
|
||||
EXPORT_SYMBOL(ucmb_device_unregister);
|
||||
|
||||
static int ucmb_modinit(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
printk(KERN_INFO "Microcontroller message bus driver\n");
|
||||
|
||||
err = spi_register_driver(&ucmb_spi_driver);
|
||||
if (err) {
|
||||
printk(KERN_ERR PFX "Failed to register SPI driver\n");
|
||||
return err;
|
||||
}
|
||||
err = platform_driver_register(&ucmb_driver);
|
||||
if (err) {
|
||||
printk(KERN_ERR PFX "Failed to register platform driver\n");
|
||||
spi_unregister_driver(&ucmb_spi_driver);
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
subsys_initcall(ucmb_modinit);
|
||||
|
||||
static void ucmb_modexit(void)
|
||||
{
|
||||
platform_driver_unregister(&ucmb_driver);
|
||||
spi_unregister_driver(&ucmb_spi_driver);
|
||||
}
|
||||
module_exit(ucmb_modexit);
|
@ -1,77 +0,0 @@
|
||||
#ifndef LINUX_UCMB_H_
|
||||
#define LINUX_UCMB_H_
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/ioctl.h>
|
||||
|
||||
|
||||
/* IOCTLs */
|
||||
#define __UCMB_IOCTL ('U'|'C'|'M'|'B')
|
||||
|
||||
/** UCMB_IOCTL_RESETUC - Reset the microcontroller. */
|
||||
#define UCMB_IOCTL_RESETUC _IO(__UCMB_IOCTL, 0)
|
||||
/** UCMB_IOCTL_GMSGDELAY - Get the delay to wait before fetching the status. */
|
||||
#define UCMB_IOCTL_GMSGDELAY _IOR(__UCMB_IOCTL, 1, unsigned int)
|
||||
/** UCMB_IOCTL_SMSGDELAY - Set the delay to wait before fetching the status. */
|
||||
#define UCMB_IOCTL_SMSGDELAY _IOW(__UCMB_IOCTL, 2, unsigned int)
|
||||
|
||||
|
||||
#ifdef __KERNEL__
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/spi/spi_gpio.h>
|
||||
|
||||
/**
|
||||
* struct ucmb_platform_data - UCMB device descriptor
|
||||
*
|
||||
* @name: The name of the device. This will also be the name of
|
||||
* the misc char device.
|
||||
*
|
||||
* @gpio_cs: The chipselect GPIO pin. Can be SPI_GPIO_NO_CHIPSELECT,
|
||||
* if chipselect is not used.
|
||||
* @gpio_sck: The clock GPIO pin.
|
||||
* @gpio_miso: The master-in slave-out GPIO pin.
|
||||
* @gpio_mosi: The master-out slave-in GPIO pin.
|
||||
*
|
||||
* @gpio_reset: The GPIO pin to the microcontroller reset line.
|
||||
* Can be UCMB_NO_RESET, if reset GPIO is not used.
|
||||
* @reset_activelow: If true, @gpio_reset is considered to be active
|
||||
* on logical 0 (inverted).
|
||||
*
|
||||
* @mode: The SPI bus mode. SPI_MODE_*
|
||||
* @max_speed_hz: The bus speed, in Hz. If zero the speed is not limited.
|
||||
* @chunk_size: The maximum chunk size to transmit/receive in one go
|
||||
* without sleeping. The kernel will be allowed to sleep
|
||||
* after each chunk.
|
||||
* If set to 0, the whole data will be transmitted/received
|
||||
* in one big rush without sleeping. Note that this might hurt
|
||||
* system responsiveness, if the kernel is not preemptible.
|
||||
* If CONFIG_PREEMPT is enabled, chunk_size will be forced to 0.
|
||||
*/
|
||||
struct ucmb_platform_data {
|
||||
const char *name;
|
||||
|
||||
unsigned long gpio_cs;
|
||||
unsigned int gpio_sck;
|
||||
unsigned int gpio_miso;
|
||||
unsigned int gpio_mosi;
|
||||
|
||||
unsigned int gpio_reset;
|
||||
bool reset_activelow;
|
||||
|
||||
u8 mode;
|
||||
u32 max_speed_hz;
|
||||
unsigned int chunk_size;
|
||||
|
||||
struct platform_device *pdev; /* internal */
|
||||
};
|
||||
|
||||
#define UCMB_NO_RESET ((unsigned int)-1)
|
||||
|
||||
int ucmb_device_register(struct ucmb_platform_data *pdata);
|
||||
void ucmb_device_unregister(struct ucmb_platform_data *pdata);
|
||||
|
||||
|
||||
#endif /* __KERNEL__ */
|
||||
#endif /* LINUX_UCMB_H_ */
|
@ -1,233 +0,0 @@
|
||||
/*
|
||||
* Microcontroller message bus
|
||||
* uc-side slave implementation for Atmel AVR8
|
||||
*
|
||||
* The gcc compiler always treats multi-byte variables as litte-endian.
|
||||
* So no explicit endianness conversions are done on the message header,
|
||||
* footer and status data structures.
|
||||
*
|
||||
* Copyright (C) 2009 Michael Buesch <mb@bu3sch.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include "ucmb.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <avr/io.h>
|
||||
#include <avr/interrupt.h>
|
||||
#include <util/crc16.h>
|
||||
|
||||
|
||||
struct ucmb_message_hdr {
|
||||
uint16_t magic; /* UCMB_MAGIC */
|
||||
uint16_t len; /* Payload length (excluding header and footer) */
|
||||
} __attribute__((packed));
|
||||
|
||||
struct ucmb_message_footer {
|
||||
uint16_t crc; /* CRC of the header + payload. */
|
||||
} __attribute__((packed));
|
||||
|
||||
struct ucmb_status {
|
||||
uint16_t magic; /* UCMB_MAGIC */
|
||||
uint16_t code; /* enum ucmb_status_code */
|
||||
} __attribute__((packed));
|
||||
|
||||
#define UCMB_MAGIC 0x1337
|
||||
|
||||
enum ucmb_status_code {
|
||||
UCMB_STAT_OK = 0,
|
||||
UCMB_STAT_EPROTO, /* Protocol format error */
|
||||
UCMB_STAT_ENOMEM, /* Out of memory */
|
||||
UCMB_STAT_E2BIG, /* Message too big */
|
||||
UCMB_STAT_ECRC, /* CRC error */
|
||||
};
|
||||
|
||||
|
||||
static uint8_t ucmb_buf[sizeof(struct ucmb_message_hdr) +
|
||||
UCMB_MAX_MSG_LEN +
|
||||
sizeof(struct ucmb_message_footer)];
|
||||
static uint16_t ucmb_buf_ptr;
|
||||
static struct ucmb_status status_buf;
|
||||
static uint16_t ucmb_send_message_len;
|
||||
|
||||
/* Statemachine */
|
||||
static uint8_t ucmb_state;
|
||||
enum {
|
||||
UCMB_ST_LISTEN, /* Listen for incoming messages. */
|
||||
UCMB_ST_SENDSTATUS, /* Send the status report. */
|
||||
UCMB_ST_SENDMESSAGE, /* Send the message. */
|
||||
UCMB_ST_RETRSTATUS, /* Retrieve the status report. */
|
||||
};
|
||||
|
||||
#define TRAILING 1
|
||||
|
||||
|
||||
static void ucmb_send_next_byte(void)
|
||||
{
|
||||
switch (ucmb_state) {
|
||||
case UCMB_ST_SENDSTATUS: {
|
||||
const uint8_t *st = (const uint8_t *)&status_buf;
|
||||
|
||||
if (ucmb_buf_ptr < sizeof(struct ucmb_status))
|
||||
SPDR = st[ucmb_buf_ptr];
|
||||
ucmb_buf_ptr++;
|
||||
if (ucmb_buf_ptr == sizeof(struct ucmb_status) + TRAILING) {
|
||||
ucmb_buf_ptr = 0;
|
||||
if (ucmb_send_message_len) {
|
||||
ucmb_state = UCMB_ST_SENDMESSAGE;
|
||||
goto st_sendmessage;
|
||||
} else
|
||||
ucmb_state = UCMB_ST_LISTEN;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UCMB_ST_SENDMESSAGE: {
|
||||
st_sendmessage:;
|
||||
uint16_t full_length = sizeof(struct ucmb_message_hdr) +
|
||||
ucmb_send_message_len +
|
||||
sizeof(struct ucmb_message_footer);
|
||||
if (ucmb_buf_ptr < full_length)
|
||||
SPDR = ucmb_buf[ucmb_buf_ptr];
|
||||
ucmb_buf_ptr++;
|
||||
if (ucmb_buf_ptr == full_length + TRAILING) {
|
||||
ucmb_send_message_len = 0;
|
||||
ucmb_buf_ptr = 0;
|
||||
ucmb_state = UCMB_ST_RETRSTATUS;
|
||||
}
|
||||
break;
|
||||
} }
|
||||
}
|
||||
|
||||
static uint16_t crc16_block_update(uint16_t crc, const void *_data, uint16_t size)
|
||||
{
|
||||
const uint8_t *data = _data;
|
||||
|
||||
while (size) {
|
||||
crc = _crc16_update(crc, *data);
|
||||
data++;
|
||||
size--;
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
static uint16_t ucmb_calc_msg_buffer_crc(void)
|
||||
{
|
||||
const struct ucmb_message_hdr *hdr;
|
||||
uint16_t crc = 0xFFFF;
|
||||
|
||||
hdr = (const struct ucmb_message_hdr *)ucmb_buf;
|
||||
crc = crc16_block_update(crc, ucmb_buf,
|
||||
sizeof(struct ucmb_message_hdr) + hdr->len);
|
||||
crc ^= 0xFFFF;
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
/* SPI data transfer interrupt. */
|
||||
ISR(SPI_STC_vect)
|
||||
{
|
||||
uint8_t data;
|
||||
|
||||
data = SPDR;
|
||||
SPDR = 0;
|
||||
|
||||
switch (ucmb_state) {
|
||||
case UCMB_ST_LISTEN: {
|
||||
struct ucmb_message_hdr *hdr;
|
||||
struct ucmb_message_footer *footer;
|
||||
|
||||
if (ucmb_buf_ptr < sizeof(ucmb_buf))
|
||||
ucmb_buf[ucmb_buf_ptr] = data;
|
||||
ucmb_buf_ptr++;
|
||||
if (ucmb_buf_ptr < sizeof(struct ucmb_message_hdr))
|
||||
return; /* Header RX not complete. */
|
||||
hdr = (struct ucmb_message_hdr *)ucmb_buf;
|
||||
if (ucmb_buf_ptr == sizeof(struct ucmb_message_hdr)) {
|
||||
if (hdr->magic != UCMB_MAGIC) {
|
||||
/* Invalid magic! Reset. */
|
||||
ucmb_buf_ptr = 0;
|
||||
return;
|
||||
}
|
||||
if (hdr->len > 0x8000) {
|
||||
/* Completely bogus length! Reset. */
|
||||
ucmb_buf_ptr = 0;
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (ucmb_buf_ptr == sizeof(struct ucmb_message_hdr) +
|
||||
sizeof(struct ucmb_message_footer) +
|
||||
hdr->len) {
|
||||
status_buf.magic = UCMB_MAGIC;
|
||||
status_buf.code = UCMB_STAT_OK;
|
||||
if (ucmb_buf_ptr > sizeof(ucmb_buf)) {
|
||||
/* Message is way too big and was truncated. */
|
||||
status_buf.code = UCMB_STAT_E2BIG;
|
||||
} else {
|
||||
footer = (struct ucmb_message_footer *)(
|
||||
ucmb_buf + sizeof(struct ucmb_message_hdr) +
|
||||
hdr->len);
|
||||
if (ucmb_calc_msg_buffer_crc() != footer->crc)
|
||||
status_buf.code = UCMB_STAT_ECRC;
|
||||
}
|
||||
ucmb_state = UCMB_ST_SENDSTATUS;
|
||||
ucmb_buf_ptr = 0;
|
||||
ucmb_send_next_byte();
|
||||
|
||||
if (status_buf.code != UCMB_STAT_OK)
|
||||
return; /* Corrupt message. Don't pass it to user code. */
|
||||
|
||||
ucmb_send_message_len = ucmb_rx_message(
|
||||
ucmb_buf + sizeof(struct ucmb_message_hdr),
|
||||
hdr->len);
|
||||
if (ucmb_send_message_len) {
|
||||
footer = (struct ucmb_message_footer *)(
|
||||
ucmb_buf + sizeof(struct ucmb_message_hdr) +
|
||||
ucmb_send_message_len);
|
||||
|
||||
hdr->magic = UCMB_MAGIC;
|
||||
hdr->len = ucmb_send_message_len;
|
||||
footer->crc = ucmb_calc_msg_buffer_crc();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UCMB_ST_SENDSTATUS:
|
||||
case UCMB_ST_SENDMESSAGE:
|
||||
ucmb_send_next_byte();
|
||||
break;
|
||||
case UCMB_ST_RETRSTATUS: {
|
||||
uint8_t *st = (uint8_t *)&status_buf;
|
||||
|
||||
st[ucmb_buf_ptr++] = data;
|
||||
if (ucmb_buf_ptr == sizeof(struct ucmb_status)) {
|
||||
/* We could possibly handle the status report here... */
|
||||
ucmb_buf_ptr = 0;
|
||||
ucmb_state = UCMB_ST_LISTEN;
|
||||
}
|
||||
break;
|
||||
} }
|
||||
}
|
||||
|
||||
void ucmb_init(void)
|
||||
{
|
||||
ucmb_state = UCMB_ST_LISTEN;
|
||||
|
||||
/* SPI slave mode 0 with IRQ enabled. */
|
||||
DDRB |= (1 << 4/*MISO*/);
|
||||
DDRB &= ~((1 << 5/*SCK*/) | (1 << 3/*MOSI*/) | (1 << 2/*SS*/));
|
||||
SPCR = (1 << SPE) | (1 << SPIE) /*| (1 << CPOL) | (1 << CPHA)*/;
|
||||
(void)SPSR; /* clear state */
|
||||
(void)SPDR; /* clear state */
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
#ifndef UCMB_AVR8_H_
|
||||
#define UCMB_AVR8_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
/* Max length of the message payload. */
|
||||
#define UCMB_MAX_MSG_LEN 0x200
|
||||
|
||||
/* ucmb_rx_message - Message receive callback.
|
||||
* Define this elsewhere. It's called on successful retrieval
|
||||
* of a new message.
|
||||
* If a reply message has to be transferred after this one, put the
|
||||
* message payload into the "payload" buffer and return the number
|
||||
* of bytes to transmit. If no reply message is needed, return 0.
|
||||
* Note that the "payload" buffer always has a size of UCMB_MAX_MSG_LEN.
|
||||
*/
|
||||
extern uint16_t ucmb_rx_message(uint8_t *payload,
|
||||
uint16_t payload_length);
|
||||
|
||||
/* Initialize the UCMB subsystem. */
|
||||
void ucmb_init(void);
|
||||
|
||||
#endif /* UCMB_AVR8_H_ */
|
@ -1,530 +0,0 @@
|
||||
/*
|
||||
* Microcontroller message bus
|
||||
* uc-side slave implementation for Atmel AVR8
|
||||
*
|
||||
* The gcc compiler always treats multi-byte variables as litte-endian.
|
||||
* So no explicit endianness conversions are done on the message header,
|
||||
* footer and status data structures.
|
||||
*
|
||||
* This hotpath-assembly implementation is about twice as fast
|
||||
* as the C implementation.
|
||||
*
|
||||
* Copyright (C) 2009 Michael Buesch <mb@bu3sch.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#include "ucmb.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <avr/io.h>
|
||||
#include <avr/interrupt.h>
|
||||
#include <avr/pgmspace.h>
|
||||
#include <avr/wdt.h>
|
||||
|
||||
|
||||
#ifndef __GNUC__
|
||||
# error "Need the GNU C compiler"
|
||||
#endif
|
||||
|
||||
#undef __naked
|
||||
#define __naked __attribute__((__naked__))
|
||||
#undef __used
|
||||
#define __used __attribute__((__used__))
|
||||
#undef __noret
|
||||
#define __noret __attribute__((__noreturn__))
|
||||
#undef offsetof
|
||||
#define offsetof(type, member) ((size_t)&((type *)0)->member)
|
||||
#undef unlikely
|
||||
#define unlikely(x) __builtin_expect(!!(x), 0)
|
||||
#undef mb
|
||||
#define mb() __asm__ __volatile__("" : : : "memory") /* memory barrier */
|
||||
#ifndef ucmb_errorlog
|
||||
# define ucmb_errorlog(message) do { /* nothing */ } while (0)
|
||||
#endif
|
||||
|
||||
|
||||
struct ucmb_message_hdr {
|
||||
uint16_t magic; /* UCMB_MAGIC */
|
||||
uint16_t len; /* Payload length (excluding header and footer) */
|
||||
} __attribute__((packed));
|
||||
|
||||
struct ucmb_message_footer {
|
||||
uint16_t crc; /* CRC of the header + payload. */
|
||||
} __attribute__((packed));
|
||||
|
||||
struct ucmb_status {
|
||||
uint16_t magic; /* UCMB_MAGIC */
|
||||
uint16_t code; /* enum ucmb_status_code */
|
||||
} __attribute__((packed));
|
||||
|
||||
#define UCMB_MAGIC 0x1337
|
||||
|
||||
enum ucmb_status_code {
|
||||
UCMB_STAT_OK = 0,
|
||||
UCMB_STAT_EPROTO, /* Protocol format error */
|
||||
UCMB_STAT_ENOMEM, /* Out of memory */
|
||||
UCMB_STAT_E2BIG, /* Message too big */
|
||||
UCMB_STAT_ECRC, /* CRC error */
|
||||
};
|
||||
|
||||
|
||||
static uint8_t ucmb_buf[sizeof(struct ucmb_message_hdr) +
|
||||
UCMB_MAX_MSG_LEN +
|
||||
sizeof(struct ucmb_message_footer)] __used;
|
||||
static uint16_t ucmb_buf_ptr __used;
|
||||
static struct ucmb_status status_buf __used;
|
||||
static uint16_t ucmb_send_message_len __used;
|
||||
static uint8_t ucmb_received_message;
|
||||
|
||||
/* The current IRQ handler */
|
||||
static void (*ucmb_interrupt_handler)(void) __used;
|
||||
|
||||
|
||||
/* Polynomial: x^16 + x^15 + x^2 + 1 */
|
||||
static const prog_uint16_t crc16_table[256] = {
|
||||
0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
|
||||
0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
|
||||
0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40,
|
||||
0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841,
|
||||
0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40,
|
||||
0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41,
|
||||
0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641,
|
||||
0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040,
|
||||
0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240,
|
||||
0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441,
|
||||
0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41,
|
||||
0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840,
|
||||
0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41,
|
||||
0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40,
|
||||
0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640,
|
||||
0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041,
|
||||
0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240,
|
||||
0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441,
|
||||
0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41,
|
||||
0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840,
|
||||
0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41,
|
||||
0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40,
|
||||
0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640,
|
||||
0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041,
|
||||
0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241,
|
||||
0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440,
|
||||
0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40,
|
||||
0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841,
|
||||
0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40,
|
||||
0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41,
|
||||
0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641,
|
||||
0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040
|
||||
};
|
||||
|
||||
static inline uint16_t crc16_block_update(uint16_t crc, const void *_data, uint16_t size)
|
||||
{
|
||||
const uint8_t *data = _data;
|
||||
uint8_t offset;
|
||||
|
||||
while (size--) {
|
||||
wdt_reset();
|
||||
offset = crc ^ *data;
|
||||
crc = (crc >> 8) ^ pgm_read_word(&crc16_table[offset]);
|
||||
data++;
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
static inline uint16_t ucmb_calc_msg_buffer_crc(void)
|
||||
{
|
||||
const struct ucmb_message_hdr *hdr;
|
||||
uint16_t crc = 0xFFFF;
|
||||
|
||||
hdr = (const struct ucmb_message_hdr *)ucmb_buf;
|
||||
crc = crc16_block_update(crc, ucmb_buf,
|
||||
sizeof(struct ucmb_message_hdr) + hdr->len);
|
||||
crc ^= 0xFFFF;
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
/* The generic interrupt handler.
|
||||
* This just branches to the state specific handler.
|
||||
*/
|
||||
ISR(SPI_STC_vect) __naked;
|
||||
ISR(SPI_STC_vect)
|
||||
{
|
||||
__asm__ __volatile__(
|
||||
" ; UCMB generic interrupt handler \n"
|
||||
" push __tmp_reg__ \n"
|
||||
" in __tmp_reg__, __SREG__ \n"
|
||||
" push r30 \n"
|
||||
" push r31 \n"
|
||||
" lds r30, ucmb_interrupt_handler + 0 \n"
|
||||
" lds r31, ucmb_interrupt_handler + 1 \n"
|
||||
" ijmp ; Jump to the real handler \n"
|
||||
);
|
||||
}
|
||||
#define UCMB_IRQ_EPILOGUE \
|
||||
" ; UCMB interrupt epilogue (start) \n"\
|
||||
" pop r31 \n"\
|
||||
" pop r30 \n"\
|
||||
" out __SREG__, __tmp_reg__ \n"\
|
||||
" pop __tmp_reg__ \n"\
|
||||
" reti \n"\
|
||||
" ; UCMB interrupt epilogue (end) \n"
|
||||
|
||||
static void __naked __used ucmb_handler_LISTEN(void)
|
||||
{
|
||||
__asm__ __volatile__(
|
||||
" push r16 \n"
|
||||
" push r17 \n"
|
||||
" push r18 \n"
|
||||
" lds r16, ucmb_buf_ptr + 0 \n"
|
||||
" lds r17, ucmb_buf_ptr + 1 \n"
|
||||
" ldi r18, hi8(%[sizeof_buf]) \n"
|
||||
" cpi r16, lo8(%[sizeof_buf]) \n"
|
||||
" cpc r17, r18 \n"
|
||||
" in r18, %[_SPDR] \n"
|
||||
" brsh 1f ; overflow \n"
|
||||
" ; Store SPDR in the buffer \n"
|
||||
" movw r30, r16 \n"
|
||||
" subi r30, lo8(-(ucmb_buf)) \n"
|
||||
" sbci r31, hi8(-(ucmb_buf)) \n"
|
||||
" st Z, r18 \n"
|
||||
"1: \n"
|
||||
" ; Increment the buffer pointer \n"
|
||||
" subi r16, lo8(-1) \n"
|
||||
" sbci r17, hi8(-1) \n"
|
||||
" sts ucmb_buf_ptr + 0, r16 \n"
|
||||
" sts ucmb_buf_ptr + 1, r17 \n"
|
||||
" cpi r17, 0 \n"
|
||||
" brne 1f \n"
|
||||
" cpi r16, %[sizeof_msg_hdr] \n"
|
||||
" breq hdr_sanity_check ; buf_ptr == hdrlen \n"
|
||||
" brlo st_listen_out ; buf_ptr < hdrlen \n"
|
||||
"1: \n"
|
||||
" ; Get payload length from header \n"
|
||||
" lds r30, (ucmb_buf + %[offsetof_hdr_len] + 0) \n"
|
||||
" lds r31, (ucmb_buf + %[offsetof_hdr_len] + 1) \n"
|
||||
" ; Add header and footer length to get full length \n"
|
||||
" subi r30, lo8(-(%[sizeof_msg_hdr] + %[sizeof_msg_footer])) \n"
|
||||
" sbci r31, hi8(-(%[sizeof_msg_hdr] + %[sizeof_msg_footer])) \n"
|
||||
" ; Check if we have the full packet \n"
|
||||
" cp r30, r16 \n"
|
||||
" cpc r31, r17 \n"
|
||||
" breq st_listen_have_full_packet \n"
|
||||
"st_listen_out: \n"
|
||||
" pop r18 \n"
|
||||
" pop r17 \n"
|
||||
" pop r16 \n"
|
||||
UCMB_IRQ_EPILOGUE /* reti */
|
||||
" \n"
|
||||
"hdr_sanity_check: \n"
|
||||
" lds r30, (ucmb_buf + %[offsetof_hdr_magic] + 0) \n"
|
||||
" lds r31, (ucmb_buf + %[offsetof_hdr_magic] + 1) \n"
|
||||
" ldi r18, hi8(%[_UCMB_MAGIC]) \n"
|
||||
" cpi r30, lo8(%[_UCMB_MAGIC]) \n"
|
||||
" cpc r31, r18 \n"
|
||||
" brne invalid_hdr_magic \n"
|
||||
" lds r30, (ucmb_buf + %[offsetof_hdr_len] + 0) \n"
|
||||
" lds r31, (ucmb_buf + %[offsetof_hdr_len] + 1) \n"
|
||||
" ldi r18, hi8(0x8001) \n"
|
||||
" cpi r30, lo8(0x8001) \n"
|
||||
" cpc r31, r18 \n"
|
||||
" brsh bogus_payload_len \n"
|
||||
" rjmp st_listen_out \n"
|
||||
" \n"
|
||||
"invalid_hdr_magic: \n"
|
||||
" ; Invalid magic number in the packet header. Reset. \n"
|
||||
"bogus_payload_len: \n"
|
||||
" ; Bogus payload length in packet header. Reset. \n"
|
||||
" clr r18 \n"
|
||||
" sts ucmb_buf_ptr + 0, r18 \n"
|
||||
" sts ucmb_buf_ptr + 1, r18 \n"
|
||||
" rjmp st_listen_out \n"
|
||||
" \n"
|
||||
"st_listen_have_full_packet: \n"
|
||||
" ; We have the full packet. Any SPI transfer is stopped \n"
|
||||
" ; while we are processing the packet, so this \n"
|
||||
" ; is a slowpath. \n"
|
||||
" ; Disable SPI and pass control to ucmb_work to \n"
|
||||
" ; handle the message. \n"
|
||||
" cbi %[_SPCR], %[_SPIE] \n"
|
||||
" ldi r18, 1 \n"
|
||||
" sts ucmb_received_message, r18 \n"
|
||||
" rjmp st_listen_out \n"
|
||||
: /* none */
|
||||
: [sizeof_buf] "i" (sizeof(ucmb_buf))
|
||||
, [sizeof_msg_hdr] "M" (sizeof(struct ucmb_message_hdr))
|
||||
, [sizeof_msg_footer] "M" (sizeof(struct ucmb_message_footer))
|
||||
, [offsetof_hdr_magic] "M" (offsetof(struct ucmb_message_hdr, magic))
|
||||
, [offsetof_hdr_len] "M" (offsetof(struct ucmb_message_hdr, len))
|
||||
, [_SPDR] "M" (_SFR_IO_ADDR(SPDR))
|
||||
, [_SPCR] "M" (_SFR_IO_ADDR(SPCR))
|
||||
, [_SPIE] "i" (SPIE)
|
||||
, [_UCMB_MAGIC] "i" (UCMB_MAGIC)
|
||||
: "memory"
|
||||
);
|
||||
}
|
||||
|
||||
static void __naked __used ucmb_handler_SENDSTATUS(void)
|
||||
{
|
||||
__asm__ __volatile__(
|
||||
" push r16 \n"
|
||||
/*" push r17 \n" */
|
||||
" push r18 \n"
|
||||
" lds r16, ucmb_buf_ptr + 0 \n"
|
||||
" cpi r16, %[sizeof_ucmb_status] \n"
|
||||
" brsh 1f ; This is the trailing byte \n"
|
||||
" ; Write the next byte from status_buf to SPDR \n"
|
||||
" mov r30, r16 \n"
|
||||
" clr r31 \n"
|
||||
" subi r30, lo8(-(status_buf)) \n"
|
||||
" sbci r31, hi8(-(status_buf)) \n"
|
||||
" ld r18, Z \n"
|
||||
" out %[_SPDR], r18 \n"
|
||||
"1: \n"
|
||||
" subi r16, lo8(-1) \n"
|
||||
" sts ucmb_buf_ptr + 0, r16 \n"
|
||||
" cpi r16, (%[sizeof_ucmb_status] + 1) \n"
|
||||
" brne st_sendstatus_out \n"
|
||||
" ; Finished. Sent all status_buf bytes + trailing byte. \n"
|
||||
" clr r18 \n"
|
||||
" sts ucmb_buf_ptr + 0, r18 \n"
|
||||
" ; Switch back to listening state... \n"
|
||||
" ldi r18, lo8(gs(ucmb_handler_LISTEN)) \n"
|
||||
" sts ucmb_interrupt_handler + 0, r18 \n"
|
||||
" ldi r18, hi8(gs(ucmb_handler_LISTEN)) \n"
|
||||
" sts ucmb_interrupt_handler + 1, r18 \n"
|
||||
" ; ...if we have no pending transmission \n"
|
||||
" lds r30, ucmb_send_message_len + 0 \n"
|
||||
" lds r31, ucmb_send_message_len + 1 \n"
|
||||
" clr r18 \n"
|
||||
" cpi r30, 0 \n"
|
||||
" cpc r31, r18 \n"
|
||||
" breq st_sendstatus_out \n"
|
||||
" ; Switch status to SENDMESSAGE and send the first byte. \n"
|
||||
" ldi r18, lo8(gs(ucmb_handler_SENDMESSAGE)) \n"
|
||||
" sts ucmb_interrupt_handler + 0, r18 \n"
|
||||
" ldi r18, hi8(gs(ucmb_handler_SENDMESSAGE)) \n"
|
||||
" sts ucmb_interrupt_handler + 1, r18 \n"
|
||||
" ; Send the first byte \n"
|
||||
" lds r18, ucmb_buf + 0 \n"
|
||||
" out %[_SPDR], r18 \n"
|
||||
" ldi r18, 1 \n"
|
||||
" sts ucmb_buf_ptr + 0, r18 \n"
|
||||
"st_sendstatus_out: \n"
|
||||
" pop r18 \n"
|
||||
/*" pop r17 \n"*/
|
||||
" pop r16 \n"
|
||||
UCMB_IRQ_EPILOGUE /* reti */
|
||||
: /* none */
|
||||
: [sizeof_ucmb_status] "M" (sizeof(struct ucmb_status))
|
||||
, [_SPDR] "M" (_SFR_IO_ADDR(SPDR))
|
||||
: "memory"
|
||||
);
|
||||
}
|
||||
|
||||
static void __naked __used ucmb_handler_SENDMESSAGE(void)
|
||||
{
|
||||
__asm__ __volatile__(
|
||||
" push r16 \n"
|
||||
" push r17 \n"
|
||||
" push r18 \n"
|
||||
" lds r16, ucmb_buf_ptr + 0 \n"
|
||||
" lds r17, ucmb_buf_ptr + 1 \n"
|
||||
" lds r30, ucmb_send_message_len + 0 \n"
|
||||
" lds r31, ucmb_send_message_len + 1 \n"
|
||||
" cp r16, r30 \n"
|
||||
" cpc r17, r31 \n"
|
||||
" brsh 1f ; This is the trailing byte \n"
|
||||
" movw r30, r16 \n"
|
||||
" subi r30, lo8(-(ucmb_buf)) \n"
|
||||
" sbci r31, hi8(-(ucmb_buf)) \n"
|
||||
" ld r18, Z \n"
|
||||
" out %[_SPDR], r18 \n"
|
||||
"1: \n"
|
||||
" subi r16, lo8(-1) \n"
|
||||
" sbci r17, hi8(-1) \n"
|
||||
" sts ucmb_buf_ptr + 0, r16 \n"
|
||||
" sts ucmb_buf_ptr + 1, r17 \n"
|
||||
" lds r30, ucmb_send_message_len + 0 \n"
|
||||
" lds r31, ucmb_send_message_len + 1 \n"
|
||||
" subi r30, lo8(-1) \n"
|
||||
" sbci r31, hi8(-1) \n"
|
||||
" cp r16, r30 \n"
|
||||
" cpc r17, r31 \n"
|
||||
" brne st_sendmessage_out \n"
|
||||
" ; Message + trailing byte processed. Retrieve status. \n"
|
||||
" clr r18 \n"
|
||||
" sts ucmb_buf_ptr + 0, r18 \n"
|
||||
" sts ucmb_buf_ptr + 1, r18 \n"
|
||||
" ldi r18, lo8(gs(ucmb_handler_RETRSTATUS)) \n"
|
||||
" sts ucmb_interrupt_handler + 0, r18 \n"
|
||||
" ldi r18, hi8(gs(ucmb_handler_RETRSTATUS)) \n"
|
||||
" sts ucmb_interrupt_handler + 1, r18 \n"
|
||||
"st_sendmessage_out: \n"
|
||||
" pop r18 \n"
|
||||
" pop r17 \n"
|
||||
" pop r16 \n"
|
||||
UCMB_IRQ_EPILOGUE /* reti */
|
||||
: /* none */
|
||||
: [_SPDR] "M" (_SFR_IO_ADDR(SPDR))
|
||||
: "memory"
|
||||
);
|
||||
}
|
||||
|
||||
static void __naked __used ucmb_handler_RETRSTATUS(void)
|
||||
{
|
||||
__asm__ __volatile__(
|
||||
" push r16 \n"
|
||||
/*" push r17 \n"*/
|
||||
" push r18 \n"
|
||||
" in r18, %[_SPDR] \n"
|
||||
" lds r16, ucmb_buf_ptr + 0 \n"
|
||||
" mov r30, r16 \n"
|
||||
" clr r31 \n"
|
||||
" subi r30, lo8(-(status_buf)) \n"
|
||||
" sbci r31, hi8(-(status_buf)) \n"
|
||||
" st Z, r18 \n"
|
||||
" subi r16, -1 \n"
|
||||
" sts ucmb_buf_ptr + 0, r16 \n"
|
||||
" cpi r16, %[sizeof_ucmb_status] \n"
|
||||
" brne st_retrstatus_out \n"
|
||||
" ; Completely received the status \n"
|
||||
" clr r16 \n"
|
||||
" sts ucmb_buf_ptr + 0, r16 \n"
|
||||
" ; Switch back to listening state... \n"
|
||||
" ldi r18, lo8(gs(ucmb_handler_LISTEN)) \n"
|
||||
" sts ucmb_interrupt_handler + 0, r18 \n"
|
||||
" ldi r18, hi8(gs(ucmb_handler_LISTEN)) \n"
|
||||
" sts ucmb_interrupt_handler + 1, r18 \n"
|
||||
" ; Check status-report magic value \n"
|
||||
" lds r30, (status_buf + %[offsetof_status_magic] + 0) \n"
|
||||
" lds r31, (status_buf + %[offsetof_status_magic] + 1) \n"
|
||||
" ldi r18, hi8(%[_UCMB_MAGIC]) \n"
|
||||
" cpi r30, lo8(%[_UCMB_MAGIC]) \n"
|
||||
" cpc r31, r18 \n"
|
||||
" brne invalid_status_magic \n"
|
||||
" ; Check status-report error code \n"
|
||||
" lds r30, (status_buf + %[offsetof_status_code] + 0) \n"
|
||||
" lds r31, (status_buf + %[offsetof_status_code] + 1) \n"
|
||||
" ldi r18, hi8(%[_UCMB_STAT_OK]) \n"
|
||||
" cpi r30, lo8(%[_UCMB_STAT_OK]) \n"
|
||||
" cpc r31, r18 \n"
|
||||
" brne faulty_status_code \n"
|
||||
"st_retrstatus_out: \n"
|
||||
" pop r18 \n"
|
||||
/*" pop r17 \n"*/
|
||||
" pop r16 \n"
|
||||
UCMB_IRQ_EPILOGUE /* reti */
|
||||
" \n"
|
||||
"invalid_status_magic: \n"
|
||||
"faulty_status_code: \n"
|
||||
" ; Branch to the C error handler \n"
|
||||
" ; The handler does not return, so we don't need to \n"
|
||||
" ; push/pop the registers. \n"
|
||||
" clr __zero_reg__ \n"
|
||||
" rjmp ucmb_received_faulty_status \n"
|
||||
: /* none */
|
||||
: [sizeof_ucmb_status] "M" (sizeof(struct ucmb_status))
|
||||
, [offsetof_status_magic] "M" (offsetof(struct ucmb_status, magic))
|
||||
, [offsetof_status_code] "M" (offsetof(struct ucmb_status, code))
|
||||
, [_SPDR] "M" (_SFR_IO_ADDR(SPDR))
|
||||
, [_UCMB_MAGIC] "i" (UCMB_MAGIC)
|
||||
, [_UCMB_STAT_OK] "i" (UCMB_STAT_OK)
|
||||
: "memory"
|
||||
);
|
||||
}
|
||||
|
||||
/* We received a status report with an error condition.
|
||||
* This is called from assembly code. The function does not return. */
|
||||
static void __used __noret ucmb_received_faulty_status(void)
|
||||
{
|
||||
/* The master sent us a status report with an error code.
|
||||
* Something's wrong with us. Print a status message and
|
||||
* get caught by the watchdog, yummy.
|
||||
*/
|
||||
|
||||
cli();
|
||||
wdt_disable();
|
||||
wdt_enable(WDTO_15MS);
|
||||
ucmb_errorlog("UCMB: Received faulty status report. Triggering reset.");
|
||||
while (1) {
|
||||
/* "It's Coming Right For Us!" */
|
||||
}
|
||||
}
|
||||
|
||||
void ucmb_work(void)
|
||||
{
|
||||
struct ucmb_message_hdr *hdr;
|
||||
struct ucmb_message_footer *footer;
|
||||
uint16_t payload_len;
|
||||
|
||||
if (!ucmb_received_message)
|
||||
return;
|
||||
|
||||
hdr = (struct ucmb_message_hdr *)ucmb_buf;
|
||||
payload_len = hdr->len;
|
||||
|
||||
status_buf.magic = UCMB_MAGIC;
|
||||
status_buf.code = UCMB_STAT_OK;
|
||||
if (unlikely(ucmb_buf_ptr > sizeof(ucmb_buf))) {
|
||||
/* Message is way too big and was truncated. */
|
||||
status_buf.code = UCMB_STAT_E2BIG;
|
||||
} else {
|
||||
footer = (struct ucmb_message_footer *)(
|
||||
ucmb_buf + sizeof(struct ucmb_message_hdr) +
|
||||
payload_len);
|
||||
if (ucmb_calc_msg_buffer_crc() != footer->crc)
|
||||
status_buf.code = UCMB_STAT_ECRC;
|
||||
}
|
||||
ucmb_interrupt_handler = ucmb_handler_SENDSTATUS;
|
||||
ucmb_buf_ptr = 0;
|
||||
/* Send the first byte */
|
||||
SPDR = ((uint8_t *)&status_buf)[ucmb_buf_ptr];
|
||||
ucmb_buf_ptr++;
|
||||
|
||||
if (unlikely(status_buf.code != UCMB_STAT_OK))
|
||||
goto out; /* Corrupt message. Don't pass it to user code. */
|
||||
|
||||
ucmb_send_message_len = ucmb_rx_message(
|
||||
ucmb_buf + sizeof(struct ucmb_message_hdr),
|
||||
payload_len);
|
||||
if (ucmb_send_message_len) {
|
||||
footer = (struct ucmb_message_footer *)(
|
||||
ucmb_buf + sizeof(struct ucmb_message_hdr) +
|
||||
ucmb_send_message_len);
|
||||
|
||||
hdr->magic = UCMB_MAGIC;
|
||||
hdr->len = ucmb_send_message_len;
|
||||
footer->crc = ucmb_calc_msg_buffer_crc();
|
||||
|
||||
ucmb_send_message_len += sizeof(struct ucmb_message_hdr) +
|
||||
sizeof(struct ucmb_message_footer);
|
||||
}
|
||||
|
||||
out:
|
||||
ucmb_received_message = 0;
|
||||
mb();
|
||||
/* Re-enable SPI */
|
||||
SPCR |= (1 << SPIE);
|
||||
}
|
||||
|
||||
void ucmb_init(void)
|
||||
{
|
||||
ucmb_interrupt_handler = ucmb_handler_LISTEN;
|
||||
|
||||
/* SPI slave mode 0 with IRQ enabled. */
|
||||
DDRB |= (1 << 6/*MISO*/);
|
||||
DDRB &= ~((1 << 7/*SCK*/) | (1 << 5/*MOSI*/) | (1 << 4/*SS*/));
|
||||
SPCR = (1 << SPE) | (1 << SPIE) /*| (1 << CPOL) | (1 << CPHA)*/;
|
||||
(void)SPSR; /* clear state */
|
||||
(void)SPDR; /* clear state */
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
#ifndef UCMB_AVR8_H_
|
||||
#define UCMB_AVR8_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
/* Max length of the message payload. */
|
||||
#define UCMB_MAX_MSG_LEN (128 * 64 / 8 + 16)
|
||||
|
||||
/* Error logs: If you want UCMB error log messages, define
|
||||
* ucmb_errorlog(message_string_literal)
|
||||
* somewhere. If you don't define it, UCMB will be compiled
|
||||
* without error messages. */
|
||||
|
||||
/* ucmb_rx_message - Message receive callback.
|
||||
* Define this elsewhere. It's called on successful retrieval
|
||||
* of a new message.
|
||||
* If a reply message has to be transferred after this one, put the
|
||||
* message payload into the "payload" buffer and return the number
|
||||
* of bytes to transmit. If no reply message is needed, return 0.
|
||||
* Note that the "payload" buffer always has a size of UCMB_MAX_MSG_LEN.
|
||||
* This function is called with interrupts enabled.
|
||||
*/
|
||||
extern uint16_t ucmb_rx_message(uint8_t *payload,
|
||||
uint16_t payload_length);
|
||||
|
||||
/* ucmb_work - Frequently call this from the mainloop.
|
||||
* Must not be called from interrupt context.
|
||||
* Must not be called with interrupts disabled.
|
||||
*/
|
||||
void ucmb_work(void);
|
||||
|
||||
/* Initialize the UCMB subsystem. */
|
||||
void ucmb_init(void);
|
||||
|
||||
#endif /* UCMB_AVR8_H_ */
|
Loading…
x
Reference in New Issue
Block a user