/**************************************************************************** * drivers/note/noterpmsg_driver.c * * SPDX-License-Identifier: Apache-2.0 * * 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 #include #include #include #include #include "noterpmsg.h" /**************************************************************************** * Pre-processor definitions ****************************************************************************/ #define NOTE_RPMSG_WORK_DELAY MSEC2TICK(CONFIG_DRIVERS_NOTERPMSG_WORK_DELAY) /**************************************************************************** * Private Types ****************************************************************************/ struct noterpmsg_driver_s { struct note_driver_s driver; volatile size_t head; volatile size_t tail; uint8_t buffer[CONFIG_DRIVERS_NOTERPMSG_BUFSIZE]; struct work_s work; struct rpmsg_endpoint ept; spinlock_t lock; }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ static void noterpmsg_add(FAR struct note_driver_s *driver, FAR const void *note, size_t notelen); /**************************************************************************** * Private Data ****************************************************************************/ static const struct note_driver_ops_s g_noterpmsg_ops = { noterpmsg_add }; struct noterpmsg_driver_s g_noterpmsg_driver = { { #ifdef CONFIG_SCHED_INSTRUMENTATION_FILTER "rpmsg", { { CONFIG_SCHED_INSTRUMENTATION_FILTER_DEFAULT_MODE, # ifdef CONFIG_SMP CONFIG_SCHED_INSTRUMENTATION_CPUSET # endif }, }, #endif &g_noterpmsg_ops }, }; /**************************************************************************** * Private Functions ****************************************************************************/ static inline size_t noterpmsg_next(FAR struct noterpmsg_driver_s *drv, size_t pos, size_t offset) { pos += offset; if (pos >= CONFIG_DRIVERS_NOTERPMSG_BUFSIZE) { pos -= CONFIG_DRIVERS_NOTERPMSG_BUFSIZE; } return pos; } static inline size_t noterpmsg_length(FAR struct noterpmsg_driver_s *drv) { size_t head = drv->head; size_t tail = drv->tail; if (tail > head) { head += CONFIG_DRIVERS_NOTERPMSG_BUFSIZE; } return head - tail; } static inline void noterpmsg_remove(FAR struct noterpmsg_driver_s *drv) { size_t tail = drv->tail; uint8_t notelen = drv->buffer[tail]; DEBUGASSERT(notelen <= noterpmsg_length(drv)); drv->tail = noterpmsg_next(drv, tail, notelen); } static bool noterpmsg_transfer(FAR struct noterpmsg_driver_s *drv, bool wait) { for (; ; ) { FAR uint8_t *buffer; uint32_t space; size_t len; len = noterpmsg_length(drv); if (len == 0) { return true; } buffer = rpmsg_get_tx_payload_buffer(&drv->ept, &space, wait); if (buffer == NULL) { return false; } if (space < len) { /* Find the len of large entire note data */ size_t pos = drv->tail; uint8_t notelen = drv->buffer[pos]; len = 0; while (len + notelen <= space) { pos = noterpmsg_next(drv, pos, notelen); len += notelen; notelen = drv->buffer[pos]; } } space = CONFIG_DRIVERS_NOTERPMSG_BUFSIZE - drv->tail; space = space < len ? space : len; memcpy(buffer, drv->buffer + drv->tail, space); memcpy(buffer + space, drv->buffer, len - space); if (rpmsg_send_nocopy(&drv->ept, buffer, len) < 0) { rpmsg_release_tx_buffer(&drv->ept, buffer); } drv->tail = noterpmsg_next(drv, drv->tail, len); } } static void noterpmsg_work(FAR void *priv) { FAR struct noterpmsg_driver_s *drv = priv; irqstate_t flags = spin_lock_irqsave_wo_note(&drv->lock); if (!noterpmsg_transfer(drv, false)) { work_queue(HPWORK, &drv->work, noterpmsg_work, drv, NOTE_RPMSG_WORK_DELAY); } spin_unlock_irqrestore_wo_note(&drv->lock, flags); } static void noterpmsg_add(FAR struct note_driver_s *driver, FAR const void *note, size_t notelen) { FAR struct noterpmsg_driver_s *drv = (FAR struct noterpmsg_driver_s *)driver; irqstate_t flags; size_t space; flags = spin_lock_irqsave_wo_note(&drv->lock); space = CONFIG_DRIVERS_NOTERPMSG_BUFSIZE - noterpmsg_length(drv); if (space < notelen) { if (!up_interrupt_context() && !sched_idletask()) { noterpmsg_transfer(drv, true); } else { /* Overwrite */ do { noterpmsg_remove(drv); space = CONFIG_DRIVERS_NOTERPMSG_BUFSIZE - noterpmsg_length(drv); } while (space < notelen); } } space = CONFIG_DRIVERS_NOTERPMSG_BUFSIZE - drv->head; space = space < notelen ? space : notelen; memcpy(drv->buffer + drv->head, note, space); memcpy(drv->buffer, note + space, notelen - space); drv->head = noterpmsg_next(drv, drv->head, notelen); if (work_available(&drv->work)) { work_queue(HPWORK, &drv->work, noterpmsg_work, drv, NOTE_RPMSG_WORK_DELAY); } spin_unlock_irqrestore_wo_note(&drv->lock, flags); } static int noterpmsg_ept_cb(FAR struct rpmsg_endpoint *ept, FAR void *data, size_t len, uint32_t src, FAR void *priv) { return 0; } static void noterpmsg_device_created(FAR struct rpmsg_device *rdev, FAR void *priv) { FAR struct noterpmsg_driver_s *drv = priv; int ret; if (strcmp(CONFIG_DRIVERS_NOTERPMSG_SERVER_NAME, rpmsg_get_cpuname(rdev)) == 0) { drv->ept.priv = drv; spin_lock_init(&drv->lock); ret = rpmsg_create_ept(&drv->ept, rdev, NOTERPMSG_EPT_NAME, RPMSG_ADDR_ANY, RPMSG_ADDR_ANY, noterpmsg_ept_cb, NULL); if (ret >= 0) { work_queue(HPWORK, &drv->work, noterpmsg_work, drv, 0); } } } static void noterpmsg_device_destroy(FAR struct rpmsg_device *rdev, FAR void *priv) { FAR struct noterpmsg_driver_s *drv = priv; if (strcmp(CONFIG_DRIVERS_NOTERPMSG_SERVER_NAME, rpmsg_get_cpuname(rdev)) == 0) { rpmsg_destroy_ept(&drv->ept); } } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: noterpmsg_init * * Description: * Register a rmpsg channel to note. * * Input Parameters: * None * * Returned Value: * Zero on success. A negated errno value is returned on a failure. * ****************************************************************************/ int noterpmsg_init(void) { return rpmsg_register_callback(&g_noterpmsg_driver, noterpmsg_device_created, noterpmsg_device_destroy, NULL, NULL); }