| /**************************************************************************** |
| * arch/xtensa/src/esp32/esp32_himem_chardev.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 <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <debug.h> |
| #include <sys/types.h> |
| #include <nuttx/list.h> |
| #include <nuttx/kmalloc.h> |
| #include <nuttx/himem/himem.h> |
| #include <nuttx/mutex.h> |
| #include <nuttx/fs/fs.h> |
| #include <arch/esp32/esp32_himem_chardev.h> |
| #include "esp32_himem.h" |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| #define HIMEM_UNMAPPED (-1) |
| |
| struct himem_chardev_s |
| { |
| struct list_node node; |
| size_t size; |
| esp_himem_handle_t mem_handle; |
| char name[32]; |
| }; |
| |
| struct himem_chardev_priv_s |
| { |
| off_t offset; /* himem offset[byte]. update by seek(), read(), write(). */ |
| }; |
| |
| static struct list_node g_himem_chardev_list |
| = LIST_INITIAL_VALUE(g_himem_chardev_list); |
| static esp_himem_rangehandle_t g_range_handle; |
| static mutex_t lock; |
| static size_t g_ram_offset; /* used by himem map/unmap */ |
| |
| static void *g_himem_ptr; /* mapped himem pointer */ |
| |
| static struct file *g_mapped_filep; /* for multi device */ |
| |
| /**************************************************************************** |
| * Priavte Functions |
| ****************************************************************************/ |
| |
| static int himem_chardev_open(struct file *filep) |
| { |
| struct himem_chardev_priv_s *priv = |
| kmm_malloc(sizeof(struct himem_chardev_priv_s)); |
| |
| if (priv == NULL) |
| { |
| merr("Failed to malloc.\n"); |
| return -1; |
| } |
| priv->offset = 0; |
| filep->f_priv = priv; |
| |
| return 0; |
| } |
| |
| static int himem_chardev_close(struct file *filep) |
| { |
| struct himem_chardev_priv_s *priv = filep->f_priv; |
| kmm_free(priv); |
| |
| return 0; |
| } |
| |
| static int himem_chardev_read_write(int is_write, struct file *filep, |
| char *buffer, size_t length) |
| { |
| struct himem_chardev_s *dev = filep->f_inode->i_private; |
| struct himem_chardev_priv_s *priv = filep->f_priv; |
| size_t mmap_offset = 0; |
| int ret = 0; |
| size_t i = 0; |
| size_t copy_size = 0; |
| void *himem_ptr; |
| size_t copy_range = 0; |
| |
| ret = nxmutex_lock(&lock); |
| if (ret != 0) |
| { |
| merr("Failed to lock semaphore.\n"); |
| return -1; |
| } |
| |
| while (i < length) |
| { |
| mmap_offset = priv->offset / ESP_HIMEM_BLKSZ; |
| if (mmap_offset > (dev->size / ESP_HIMEM_BLKSZ)) |
| { |
| merr("copy size(%d) over himem phy mem size(%d).\n", |
| length, dev->size); |
| goto err; |
| } |
| if ((mmap_offset != g_ram_offset) || (g_mapped_filep != filep)) |
| { |
| if (g_ram_offset != HIMEM_UNMAPPED) |
| { |
| /* already mapped another page */ |
| |
| ret = esp_himem_unmap(g_range_handle, |
| g_himem_ptr, ESP_HIMEM_BLKSZ); |
| if (ret != 0) |
| { |
| merr("Failed to unmap himem.\n"); |
| goto err; |
| } |
| g_ram_offset = HIMEM_UNMAPPED; |
| g_mapped_filep = NULL; |
| } |
| ret = esp_himem_map(dev->mem_handle, g_range_handle, |
| mmap_offset * ESP_HIMEM_BLKSZ, |
| 0, ESP_HIMEM_BLKSZ, 0, &g_himem_ptr); |
| if (ret != 0) |
| { |
| merr("Failed to map himem.\n"); |
| goto err; |
| } |
| g_ram_offset = mmap_offset; |
| g_mapped_filep = filep; |
| } |
| himem_ptr = g_himem_ptr + priv->offset % ESP_HIMEM_BLKSZ; |
| copy_range = ESP_HIMEM_BLKSZ - priv->offset % ESP_HIMEM_BLKSZ; |
| |
| /* The case of crossing a phy mem page boundary */ |
| |
| if (copy_range < (length - i)) |
| { |
| if (is_write) |
| { |
| memcpy(himem_ptr, buffer + i, copy_range); |
| } |
| else |
| { |
| memcpy(buffer + i, himem_ptr, copy_range); |
| } |
| priv->offset += copy_range; |
| copy_size += copy_range; |
| i += copy_range; |
| } |
| else |
| { |
| if (is_write) |
| { |
| memcpy(himem_ptr, buffer + i, length - i); |
| } |
| else |
| { |
| memcpy(buffer + i, himem_ptr, length - i); |
| } |
| priv->offset += length - i; |
| copy_size += length - i; |
| i += length - i; |
| } |
| } |
| nxmutex_unlock(&lock); |
| return (int)(copy_size & INT_MAX); |
| err: |
| nxmutex_unlock(&lock); |
| return copy_size > 0 ? (int)(copy_size & INT_MAX) : -1; |
| } |
| |
| static int himem_chardev_read(struct file *filep, |
| char *dst, size_t length) |
| { |
| return himem_chardev_read_write(0, filep, dst, length); |
| } |
| |
| static int himem_chardev_write(struct file *filep, |
| const char *src, size_t length) |
| { |
| return himem_chardev_read_write(1, filep, (char *)src, length); |
| } |
| |
| static off_t himem_chardev_seek(struct file *filep, |
| off_t offset, int whence) |
| { |
| struct himem_chardev_priv_s *priv = filep->f_priv; |
| struct himem_chardev_s *dev = filep->f_inode->i_private; |
| nxmutex_lock(&lock); |
| switch (whence) |
| { |
| case SEEK_SET: |
| priv->offset = offset; |
| break; |
| case SEEK_CUR: |
| priv->offset += offset; |
| break; |
| case SEEK_END: |
| priv->offset = dev->size + offset; |
| break; |
| default: |
| merr("invalid parameter: whence:%d\n", whence); |
| nxmutex_unlock(&lock); |
| return -1; |
| } |
| filep->f_pos = priv->offset; |
| nxmutex_unlock(&lock); |
| return priv->offset; |
| } |
| |
| static int himem_chardev_ioctl(struct file *filep, |
| int cmd, unsigned long arg) |
| { |
| return 0; |
| } |
| |
| static const struct file_operations g_fops = |
| { |
| .open = himem_chardev_open, |
| .close = himem_chardev_close, |
| .read = himem_chardev_read, |
| .write = himem_chardev_write, |
| .seek = himem_chardev_seek, |
| .ioctl = himem_chardev_ioctl, |
| }; |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| int himem_chardev_init(void) |
| { |
| int ret = 0; |
| ret = esp_himem_init(); |
| if (ret != 0) |
| { |
| merr("Failed to himem init.\n"); |
| return ret; |
| } |
| |
| ret = esp_himem_alloc_map_range(ESP_HIMEM_BLKSZ, &g_range_handle); /* 32KB */ |
| if (ret != 0) |
| { |
| merr("Failed to alloc himem map.\n"); |
| return ret; |
| } |
| |
| ret = nxmutex_init(&lock); |
| if (ret != 0) |
| { |
| merr("Failed to init semaphore.\n"); |
| esp_himem_free_map_range(g_range_handle); |
| return ret; |
| } |
| |
| g_ram_offset = HIMEM_UNMAPPED; |
| g_mapped_filep = NULL; |
| return ret; |
| } |
| |
| int himem_chardev_exit(void) |
| { |
| int ret = 0; |
| ret = nxmutex_destroy(&lock); |
| if (ret != 0) |
| { |
| merr("Failed to destroy semaphore.\n"); |
| esp_himem_free_map_range(g_range_handle); |
| return ret; |
| } |
| |
| ret = esp_himem_free_map_range(g_range_handle); |
| |
| return ret; |
| } |
| |
| int himem_chardev_register(char *name, size_t size) |
| { |
| int ret = 0; |
| struct himem_chardev_s *dev = |
| kmm_malloc(sizeof(struct himem_chardev_s)); |
| if (dev == NULL) |
| { |
| merr("Failed to malloc.\n"); |
| return -1; |
| } |
| |
| /* 32KB Alignment */ |
| |
| size_t mod = size % ESP_HIMEM_BLKSZ; |
| if (mod != 0) |
| { |
| size += (ESP_HIMEM_BLKSZ - mod); |
| } |
| |
| dev->size = size; |
| |
| strlcpy(dev->name, name, 32); |
| ret = esp_himem_alloc(dev->size, &dev->mem_handle); |
| if (ret != 0) |
| { |
| merr("Failed to alloc himem. size=%d\n", dev->size); |
| kmm_free(dev); |
| return ret; |
| } |
| |
| ret = register_driver(dev->name, &g_fops, 0666, dev); |
| if (ret != 0) |
| { |
| merr("Failed to register driver. dev=%s\n", dev->name); |
| esp_himem_free(dev->mem_handle); |
| kmm_free(dev); |
| return ret; |
| } |
| |
| ret = nxmutex_lock(&lock); |
| if (ret != 0) |
| { |
| merr("Failed to lock semaphore.\n"); |
| unregister_driver(dev->name); |
| esp_himem_free(dev->mem_handle); |
| kmm_free(dev); |
| return ret; |
| } |
| |
| list_add_tail(&g_himem_chardev_list, &dev->node); |
| ret = nxmutex_unlock(&lock); |
| if (ret != 0) |
| { |
| merr("Failed to unlock semaphore.\n"); |
| list_delete(&dev->node); |
| unregister_driver(dev->name); |
| esp_himem_free(dev->mem_handle); |
| kmm_free(dev); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| int himem_chardev_unregister(char *name) |
| { |
| int ret = 0; |
| int successed = 0; |
| struct himem_chardev_s *dev; |
| struct himem_chardev_s *tmp; |
| nxmutex_lock(&lock); |
| list_for_every_entry_safe(&g_himem_chardev_list, dev, |
| tmp, struct himem_chardev_s, node) |
| { |
| if (strcmp(name, dev->name) == 0) |
| { |
| if (g_ram_offset != HIMEM_UNMAPPED) |
| { |
| /* driver is mapping some page */ |
| |
| ret = esp_himem_unmap(g_range_handle, |
| g_himem_ptr, ESP_HIMEM_BLKSZ); |
| if (ret != 0) |
| { |
| merr("Failed to unmap himem.\n"); |
| successed = -1; |
| } |
| |
| g_ram_offset = HIMEM_UNMAPPED; |
| g_mapped_filep = NULL; |
| } |
| |
| ret = esp_himem_free(dev->mem_handle); |
| if (ret != 0) |
| { |
| merr("Failed to free himem.\n"); |
| successed = -1; |
| } |
| |
| ret = unregister_driver(dev->name); |
| if (ret != 0) |
| { |
| merr("Failed to unregister driver. dev=%s\n", dev->name); |
| successed = -1; |
| } |
| |
| list_delete(&dev->node); |
| kmm_free(dev); |
| nxmutex_unlock(&lock); |
| return successed; |
| } |
| } |
| |
| nxmutex_unlock(&lock); |
| merr("dev=%s is not registerd.\n", name); |
| |
| return -1; |
| } |