| /**************************************************************************** |
| * drivers/syslog/syslog_rpmsg.c |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. The |
| * ASF licenses this file to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance with the |
| * License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations |
| * under the License. |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include <nuttx/config.h> |
| |
| #include <assert.h> |
| #include <ctype.h> |
| #include <errno.h> |
| #include <string.h> |
| #include <sys/boardctl.h> |
| #include <syslog.h> |
| |
| #ifdef CONFIG_ARCH_LOWPUTC |
| #include <nuttx/arch.h> |
| #endif |
| |
| #include <nuttx/irq.h> |
| #include <nuttx/rptun/openamp.h> |
| #include <nuttx/syslog/syslog_rpmsg.h> |
| #include <nuttx/wqueue.h> |
| |
| #include "syslog_rpmsg.h" |
| |
| /**************************************************************************** |
| * Pre-processor definitions |
| ****************************************************************************/ |
| |
| #if CONFIG_SYSLOG_RPMSG_WORK_DELAY |
| # define SYSLOG_RPMSG_WORK_DELAY MSEC2TICK(CONFIG_SYSLOG_RPMSG_WORK_DELAY) |
| #else |
| # define SYSLOG_RPMSG_WORK_DELAY MSEC2TICK(100) |
| #endif |
| |
| #define SYSLOG_RPMSG_COUNT(p) ((p)->head - (p)->tail) |
| #define SYSLOG_RPMSG_SPACE(p) ((p)->size - 1 - SYSLOG_RPMSG_COUNT(p)) |
| #define SYSLOG_RPMSG_HEADOFF(p) ((p)->head & ((p)->size -1)) |
| #define SYSLOG_RPMSG_TAILOFF(p) ((p)->tail & ((p)->size -1)) |
| #define SYSLOG_RPMSG_FLUSHOFF(p) ((p)->flush & ((p)->size -1)) |
| |
| /**************************************************************************** |
| * Private Types |
| ****************************************************************************/ |
| |
| struct syslog_rpmsg_s |
| { |
| volatile size_t head; /* The head index (where data is added) */ |
| volatile size_t tail; /* The tail index (where data is removed) */ |
| volatile size_t flush; /* The tail index of flush (where data is removed) */ |
| size_t size; /* Size of the RAM buffer */ |
| FAR char *buffer; /* Circular RAM buffer */ |
| struct work_s work; /* Used for deferred callback work */ |
| |
| struct rpmsg_endpoint ept; |
| bool suspend; |
| }; |
| |
| /**************************************************************************** |
| * Private Function Prototypes |
| ****************************************************************************/ |
| |
| static void syslog_rpmsg_work(FAR void *priv_); |
| static void syslog_rpmsg_putchar(FAR struct syslog_rpmsg_s *priv, int ch, |
| bool last); |
| static void syslog_rpmsg_device_created(FAR struct rpmsg_device *rdev, |
| FAR void *priv_); |
| static void syslog_rpmsg_device_destroy(FAR struct rpmsg_device *rdev, |
| FAR void *priv_); |
| static int syslog_rpmsg_ept_cb(FAR struct rpmsg_endpoint *ept, |
| FAR void *data, size_t len, uint32_t src, |
| FAR void *priv_); |
| #ifdef CONFIG_SYSLOG_RPMSG_CHARDEV |
| static ssize_t syslog_rpmsg_file_read(FAR struct file *filep, |
| FAR char *buffer, size_t len); |
| static ssize_t syslog_rpmsg_file_write(FAR struct file *filep, |
| FAR const char *buffer, size_t len); |
| #endif |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| static struct syslog_rpmsg_s g_syslog_rpmsg; |
| |
| #ifdef CONFIG_SYSLOG_RPMSG_CHARDEV |
| static const struct file_operations g_syslog_rpmsgfops = |
| { |
| NULL, /* open */ |
| NULL, /* close */ |
| syslog_rpmsg_file_read, /* read */ |
| syslog_rpmsg_file_write, /* write */ |
| }; |
| #endif |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| static bool syslog_rpmsg_transfer(FAR struct syslog_rpmsg_s *priv, bool wait) |
| { |
| FAR struct syslog_rpmsg_transfer_s *msg = NULL; |
| irqstate_t flags; |
| uint32_t space; |
| size_t len; |
| size_t off; |
| size_t len_end; |
| |
| do |
| { |
| msg = rpmsg_get_tx_payload_buffer(&priv->ept, &space, wait); |
| if (!msg) |
| { |
| return false; |
| } |
| |
| memset(msg, 0, sizeof(*msg)); |
| |
| flags = enter_critical_section(); |
| |
| space -= sizeof(*msg); |
| len = SYSLOG_RPMSG_COUNT(priv); |
| off = SYSLOG_RPMSG_TAILOFF(priv); |
| len_end = priv->size - off; |
| |
| if (len > space) |
| { |
| len = space; |
| } |
| |
| if (len > len_end) |
| { |
| memcpy(msg->data, &priv->buffer[off], len_end); |
| memcpy(msg->data + len_end, priv->buffer, len - len_end); |
| memset(&priv->buffer[off], 0, len_end); |
| memset(priv->buffer, 0, len - len_end); |
| } |
| else |
| { |
| memcpy(msg->data, &priv->buffer[off], len); |
| memset(&priv->buffer[off], 0, len); |
| } |
| |
| msg->count = len; |
| priv->tail += len; |
| msg->header.command = SYSLOG_RPMSG_TRANSFER; |
| rpmsg_send_nocopy(&priv->ept, msg, sizeof(*msg) + len); |
| len = SYSLOG_RPMSG_COUNT(priv); |
| |
| leave_critical_section(flags); |
| } |
| while (len > 0); |
| |
| return true; |
| } |
| |
| static void syslog_rpmsg_work(FAR void *priv_) |
| { |
| FAR struct syslog_rpmsg_s *priv = priv_; |
| |
| if (!syslog_rpmsg_transfer(priv, false)) |
| { |
| work_queue(HPWORK, &priv->work, syslog_rpmsg_work, priv, |
| SYSLOG_RPMSG_WORK_DELAY); |
| } |
| } |
| |
| static void syslog_rpmsg_putchar(FAR struct syslog_rpmsg_s *priv, int ch, |
| bool last) |
| { |
| if (priv->head + 1 - priv->tail >= priv->size) |
| { |
| bool ret = false; |
| |
| if (!priv->flush && !up_interrupt_context() && !sched_idletask()) |
| { |
| ret = syslog_rpmsg_transfer(priv, true); |
| } |
| |
| if (!ret) |
| { |
| /* Overwrite */ |
| |
| priv->buffer[SYSLOG_RPMSG_TAILOFF(priv)] = 0; |
| priv->tail++; |
| } |
| } |
| |
| priv->buffer[SYSLOG_RPMSG_HEADOFF(priv)] = ch & 0xff; |
| priv->head++; |
| |
| if (priv->flush) |
| { |
| #if defined(CONFIG_ARCH_LOWPUTC) |
| up_putc(ch); |
| #endif |
| priv->flush++; |
| return; |
| } |
| |
| if (last && !priv->suspend && is_rpmsg_ept_ready(&priv->ept)) |
| { |
| clock_t delay = SYSLOG_RPMSG_WORK_DELAY; |
| size_t space = SYSLOG_RPMSG_SPACE(priv); |
| |
| /* Start work immediately when data more then 75% and meet last */ |
| |
| if (space < priv->size / 4) |
| { |
| delay = 0; |
| } |
| #if CONFIG_SYSLOG_RPMSG_WORK_DELAY == 0 |
| else |
| { |
| return; |
| } |
| #endif |
| |
| work_queue(HPWORK, &priv->work, syslog_rpmsg_work, priv, delay); |
| } |
| } |
| |
| static void syslog_rpmsg_device_created(FAR struct rpmsg_device *rdev, |
| FAR void *priv_) |
| { |
| FAR struct syslog_rpmsg_s *priv = priv_; |
| int ret; |
| |
| if (priv->buffer && strcmp(CONFIG_SYSLOG_RPMSG_SERVER_NAME, |
| rpmsg_get_cpuname(rdev)) == 0) |
| { |
| priv->ept.priv = priv; |
| |
| ret = rpmsg_create_ept(&priv->ept, rdev, SYSLOG_RPMSG_EPT_NAME, |
| RPMSG_ADDR_ANY, RPMSG_ADDR_ANY, |
| syslog_rpmsg_ept_cb, NULL); |
| if (ret == 0) |
| { |
| work_queue(HPWORK, &priv->work, syslog_rpmsg_work, priv, 0); |
| } |
| } |
| } |
| |
| static void syslog_rpmsg_device_destroy(FAR struct rpmsg_device *rdev, |
| FAR void *priv_) |
| { |
| FAR struct syslog_rpmsg_s *priv = priv_; |
| |
| if (priv->buffer && strcmp(CONFIG_SYSLOG_RPMSG_SERVER_NAME, |
| rpmsg_get_cpuname(rdev)) == 0) |
| { |
| rpmsg_destroy_ept(&priv->ept); |
| } |
| } |
| |
| static int syslog_rpmsg_ept_cb(FAR struct rpmsg_endpoint *ept, |
| FAR void *data, size_t len, uint32_t src, |
| FAR void *priv_) |
| { |
| FAR struct syslog_rpmsg_s *priv = priv_; |
| FAR struct syslog_rpmsg_header_s *header = data; |
| |
| if (header->command == SYSLOG_RPMSG_SUSPEND) |
| { |
| work_cancel(HPWORK, &priv->work); |
| priv->suspend = true; |
| } |
| else if (header->command == SYSLOG_RPMSG_RESUME) |
| { |
| priv->suspend = false; |
| work_queue(HPWORK, &priv->work, syslog_rpmsg_work, priv, 0); |
| } |
| else if (header->command == SYSLOG_RPMSG_SYNC) |
| { |
| syslog_rpmsg_transfer(priv, true); |
| rpmsg_send(ept, data, len); |
| } |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_SYSLOG_RPMSG_CHARDEV |
| static ssize_t syslog_rpmsg_file_read(FAR struct file *filep, |
| FAR char *buffer, size_t len) |
| { |
| FAR struct inode *inode = filep->f_inode; |
| FAR struct syslog_rpmsg_s *priv; |
| irqstate_t flags; |
| |
| /* Some sanity checking */ |
| |
| DEBUGASSERT(inode->i_private); |
| priv = inode->i_private; |
| |
| flags = enter_critical_section(); |
| if (!priv->suspend && is_rpmsg_ept_ready(&priv->ept)) |
| { |
| work_queue(HPWORK, &priv->work, syslog_rpmsg_work, priv, 0); |
| } |
| |
| leave_critical_section(flags); |
| return 0; |
| } |
| |
| static ssize_t syslog_rpmsg_file_write(FAR struct file *filep, |
| FAR const char *buffer, size_t len) |
| { |
| syslog(LOG_INFO, "%.*s", (int)len, buffer); |
| return len; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| int syslog_rpmsg_putc(FAR struct syslog_channel_s *channel, int ch) |
| { |
| FAR struct syslog_rpmsg_s *priv = &g_syslog_rpmsg; |
| irqstate_t flags; |
| |
| flags = enter_critical_section(); |
| syslog_rpmsg_putchar(priv, ch, true); |
| leave_critical_section(flags); |
| |
| return ch; |
| } |
| |
| int syslog_rpmsg_flush(FAR struct syslog_channel_s *channel) |
| { |
| FAR struct syslog_rpmsg_s *priv = &g_syslog_rpmsg; |
| irqstate_t flags; |
| |
| flags = enter_critical_section(); |
| |
| if (priv->head > priv->flush && |
| priv->head - priv->flush > priv->size) |
| { |
| priv->flush = priv->tail; |
| } |
| |
| while (priv->flush < priv->head) |
| { |
| #if defined(CONFIG_ARCH_LOWPUTC) |
| up_putc(priv->buffer[SYSLOG_RPMSG_FLUSHOFF(priv)]); |
| #endif |
| priv->flush++; |
| } |
| |
| leave_critical_section(flags); |
| |
| return OK; |
| } |
| |
| ssize_t syslog_rpmsg_write(FAR struct syslog_channel_s *channel, |
| FAR const char *buffer, size_t buflen) |
| { |
| FAR struct syslog_rpmsg_s *priv = &g_syslog_rpmsg; |
| irqstate_t flags; |
| size_t nwritten; |
| |
| flags = enter_critical_section(); |
| for (nwritten = 1; nwritten <= buflen; nwritten++) |
| { |
| syslog_rpmsg_putchar(priv, *buffer++, nwritten == buflen); |
| } |
| |
| leave_critical_section(flags); |
| |
| return buflen; |
| } |
| |
| void syslog_rpmsg_init_early(FAR void *buffer, size_t size) |
| { |
| FAR struct syslog_rpmsg_s *priv = &g_syslog_rpmsg; |
| #ifdef CONFIG_BOARDCTL_RESET_CAUSE |
| struct boardioc_reset_cause_s cause; |
| int ret; |
| #endif |
| bool is_empty = true; |
| char prev; |
| char cur; |
| size_t i; |
| |
| DEBUGASSERT((size & (size - 1)) == 0); |
| |
| priv->buffer = buffer; |
| priv->size = size; |
| |
| #ifdef CONFIG_BOARDCTL_RESET_CAUSE |
| memset(&cause, 0, sizeof(cause)); |
| ret = boardctl(BOARDIOC_RESET_CAUSE, (uintptr_t)&cause); |
| if (ret >= 0 && cause.cause == BOARDIOC_RESETCAUSE_SYS_CHIPPOR) |
| { |
| memset(buffer, 0, size); |
| return; |
| } |
| #endif |
| |
| prev = priv->buffer[size - 1]; |
| |
| for (i = 0; i < size; i++) |
| { |
| cur = priv->buffer[i]; |
| |
| if (!isprint(cur) && !isspace(cur) && cur != '\0') |
| { |
| memset(buffer, 0, size); |
| is_empty = true; |
| break; |
| } |
| else if (prev && !cur) |
| { |
| priv->head = i; |
| is_empty = false; |
| } |
| else if (!prev && cur) |
| { |
| priv->tail = i; |
| } |
| |
| prev = cur; |
| } |
| |
| if (is_empty) |
| { |
| priv->head = priv->tail = 0; |
| } |
| else if (priv->head < priv->tail) |
| { |
| priv->head += priv->size; |
| } |
| } |
| |
| int syslog_rpmsg_init(void) |
| { |
| #ifdef CONFIG_SYSLOG_RPMSG_CHARDEV |
| int ret; |
| |
| ret = register_driver(CONFIG_SYSLOG_DEVPATH, &g_syslog_rpmsgfops, |
| 0666, &g_syslog_rpmsg); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| #endif |
| |
| return rpmsg_register_callback(&g_syslog_rpmsg, |
| syslog_rpmsg_device_created, |
| syslog_rpmsg_device_destroy, |
| NULL, |
| NULL); |
| } |