/**************************************************************************** * wireless/ieee802154/mac802154_device.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 #include #include #include #include #include #include #include #include #include #include #include #include "mac802154.h" /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ /* Device naming ************************************************************/ #define DEVNAME_FMT "/dev/ieee%d" #define DEVNAME_FMTLEN (9 + 3 + 1) /**************************************************************************** * Private Types ****************************************************************************/ /* This structure describes the state of one open mac driver instance */ struct mac802154dev_open_s { /* Supports a singly linked list */ FAR struct mac802154dev_open_s *md_flink; /* The following will be true if we are closing */ volatile bool md_closing; }; struct mac802154dev_callback_s { /* This holds the information visible to the MAC layer */ struct mac802154_maccb_s mc_cb; /* Interface understood by * the MAC layer */ FAR struct mac802154_chardevice_s *mc_priv; /* Our priv data */ }; struct mac802154_chardevice_s { MACHANDLE md_mac; /* Saved binding to the mac layer */ struct mac802154dev_callback_s md_cb; /* Callback information */ mutex_t md_lock; /* Exclusive device access */ /* Hold a list of events */ bool enableevents : 1; /* Are events enabled? */ bool geteventpending : 1; /* Is there a get event using the semaphore? */ sem_t geteventsem; /* Signaling semaphore for waiting get event */ sq_queue_t primitive_queue; /* For holding primitives to pass along */ /* The following is a singly linked list of open references to the * MAC device. */ FAR struct mac802154dev_open_s *md_open; /* Hold a list of transactions as a "readahead" buffer */ bool readpending; /* Is there a read using the semaphore? */ sem_t readsem; /* Signaling semaphore for waiting read */ sq_queue_t dataind_queue; /* MAC Service notification information */ bool md_notify_registered; pid_t md_notify_pid; struct sigevent md_notify_event; struct sigwork_s md_notify_work; }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ static int mac802154dev_notify(FAR struct mac802154_maccb_s *maccb, FAR struct ieee802154_primitive_s *primitive); static int mac802154dev_rxframe(FAR struct mac802154_chardevice_s *dev, FAR struct ieee802154_data_ind_s *ind); static int mac802154dev_open(FAR struct file *filep); static int mac802154dev_close(FAR struct file *filep); static ssize_t mac802154dev_read(FAR struct file *filep, FAR char *buffer, size_t len); static ssize_t mac802154dev_write(FAR struct file *filep, FAR const char *buffer, size_t len); static int mac802154dev_ioctl(FAR struct file *filep, int cmd, unsigned long arg); /**************************************************************************** * Private Data ****************************************************************************/ static const struct file_operations g_mac802154dev_fops = { mac802154dev_open, /* open */ mac802154dev_close, /* close */ mac802154dev_read, /* read */ mac802154dev_write, /* write */ NULL, /* seek */ mac802154dev_ioctl, /* ioctl */ }; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: mac802154dev_open * * Description: * Open the 802.15.4 MAC character device. * ****************************************************************************/ static int mac802154dev_open(FAR struct file *filep) { FAR struct inode *inode; FAR struct mac802154_chardevice_s *dev; FAR struct mac802154dev_open_s *opriv; int ret; inode = filep->f_inode; dev = inode->i_private; DEBUGASSERT(dev != NULL); /* Get exclusive access to the MAC driver data structure */ ret = nxmutex_lock(&dev->md_lock); if (ret < 0) { wlerr("ERROR: nxsem_wait failed: %d\n", ret); return ret; } /* Allocate a new open struct */ opriv = (FAR struct mac802154dev_open_s *) kmm_zalloc(sizeof(struct mac802154dev_open_s)); if (!opriv) { wlerr("ERROR: Failed to allocate new open struct\n"); ret = -ENOMEM; goto errout_with_lock; } /* Attach the open struct to the device */ opriv->md_flink = dev->md_open; dev->md_open = opriv; /* Attach the open struct to the file structure */ filep->f_priv = (FAR void *)opriv; ret = OK; errout_with_lock: nxmutex_unlock(&dev->md_lock); return ret; } /**************************************************************************** * Name: mac802154dev_close * * Description: * Close the 802.15.4 MAC character device. * ****************************************************************************/ static int mac802154dev_close(FAR struct file *filep) { FAR struct inode *inode; FAR struct mac802154_chardevice_s *dev; FAR struct mac802154dev_open_s *opriv; FAR struct mac802154dev_open_s *curr; FAR struct mac802154dev_open_s *prev; irqstate_t flags; bool closing; int ret; DEBUGASSERT(filep->f_priv); opriv = filep->f_priv; inode = filep->f_inode; DEBUGASSERT(inode->i_private); dev = inode->i_private; /* Handle an improbable race conditions with the following atomic test * and set. * * This is actually a pretty feeble attempt to handle this. The * improbable race condition occurs if two different threads try to * close the driver at the same time. The rule: don't do * that! It is feeble because we do not really enforce stale pointer * detection anyway. */ flags = enter_critical_section(); closing = opriv->md_closing; opriv->md_closing = true; leave_critical_section(flags); if (closing) { /* Another thread is doing the close */ return OK; } /* Get exclusive access to the driver structure */ ret = nxmutex_lock(&dev->md_lock); if (ret < 0) { wlerr("ERROR: nxsem_wait failed: %d\n", ret); return ret; } /* Find the open structure in the list of open structures for the device */ for (prev = NULL, curr = dev->md_open; curr && curr != opriv; prev = curr, curr = curr->md_flink); DEBUGASSERT(curr); if (!curr) { wlerr("ERROR: Failed to find open entry\n"); ret = -ENOENT; goto errout_with_lock; } /* Remove the structure from the device */ if (prev) { prev->md_flink = opriv->md_flink; } else { dev->md_open = opriv->md_flink; } /* And free the open structure */ kmm_free(opriv); /* If there are now no open instances of the driver and a signal handler is * not registered, purge the list of events. */ if (dev->md_open) { FAR struct ieee802154_primitive_s *primitive; primitive = (FAR struct ieee802154_primitive_s *) sq_remfirst(&dev->primitive_queue); while (primitive) { ieee802154_primitive_free(primitive); primitive = (FAR struct ieee802154_primitive_s *) sq_remfirst(&dev->primitive_queue); } } ret = OK; errout_with_lock: nxmutex_unlock(&dev->md_lock); return ret; } /**************************************************************************** * Name: mac802154dev_read * * Description: * Return the last received packet. * ****************************************************************************/ static ssize_t mac802154dev_read(FAR struct file *filep, FAR char *buffer, size_t len) { FAR struct inode *inode; FAR struct mac802154_chardevice_s *dev; FAR struct mac802154dev_rxframe_s *rx; FAR struct ieee802154_data_ind_s *ind; struct ieee802154_get_req_s req; int ret; inode = filep->f_inode; DEBUGASSERT(inode->i_private); dev = inode->i_private; /* Check to make sure the buffer is the right size for the struct */ if (len != sizeof(struct mac802154dev_rxframe_s)) { wlerr("ERROR: buffer isn't a mac802154dev_rxframe_s: %lu\n", (unsigned long)len); return -EINVAL; } DEBUGASSERT(buffer != NULL); rx = (FAR struct mac802154dev_rxframe_s *)buffer; while (1) { /* Get exclusive access to the driver structure */ ret = nxmutex_lock(&dev->md_lock); if (ret < 0) { wlerr("ERROR: nxsem_wait failed: %d\n", ret); return ret; } /* Try popping a data indication off the list */ ind = (FAR struct ieee802154_data_ind_s *) sq_remfirst(&dev->dataind_queue); /* If the indication is not null, we can exit the loop and copy the * data */ if (ind != NULL) { nxmutex_unlock(&dev->md_lock); break; } /* If this is a non-blocking call, or if there is another read * operation already pending, don't block. This driver returns EAGAIN * even when configured as non-blocking if another read operation is * already pending. This situation should be rare. * It will only occur when there are 2 calls to read from separate * threads and there was no data in the rx list. */ if ((filep->f_oflags & O_NONBLOCK) || dev->readpending) { nxmutex_unlock(&dev->md_lock); return -EAGAIN; } dev->readpending = true; nxmutex_unlock(&dev->md_lock); /* Wait to be signaled when a frame is added to the list */ ret = nxsem_wait(&dev->readsem); if (ret < 0) { dev->readpending = false; return ret; } /* Let the loop wrap back around, we will then pop a indication and * this time, it should have a data indication */ } /* Check if the MAC layer is in promiscuous mode. */ req.attr = IEEE802154_ATTR_MAC_PROMISCUOUS_MODE; ret = mac802154_ioctl(dev->md_mac, MAC802154IOC_MLME_GET_REQUEST, (unsigned long)&req); if (ret == 0 && req.attrval.mac.promisc_mode) { /* If it is, add the FCS back into the frame by increasing the length * by the PHY layer's FCS length. */ req.attr = IEEE802154_ATTR_PHY_FCS_LEN; ret = mac802154_ioctl(dev->md_mac, MAC802154IOC_MLME_GET_REQUEST, (unsigned long)&req); if (ret == OK) { rx->length = ind->frame->io_len + req.attrval.phy.fcslen; rx->offset = ind->frame->io_offset; } /* Copy the entire frame from the IOB to the user supplied struct */ memcpy(&rx->payload[0], &ind->frame->io_data[0], rx->length); } else { rx->length = (ind->frame->io_len - ind->frame->io_offset); rx->offset = 0; /* Copy just the payload from the IOB to the user supplied struct */ memcpy(&rx->payload[0], &ind->frame->io_data[ind->frame->io_offset], rx->length); } memcpy(&rx->meta, ind, sizeof(struct ieee802154_data_ind_s)); /* Zero out the forward link and IOB reference */ rx->meta.flink = NULL; rx->meta.frame = NULL; /* Free the IOB */ iob_free(ind->frame); /* Deallocate the data indication */ ieee802154_primitive_free((FAR struct ieee802154_primitive_s *)ind); return OK; } /**************************************************************************** * Name: mac802154dev_write * * Description: * Send a packet over the IEEE802.15.4 network. * ****************************************************************************/ static ssize_t mac802154dev_write(FAR struct file *filep, FAR const char *buffer, size_t len) { FAR struct inode *inode; FAR struct mac802154_chardevice_s *dev; FAR struct mac802154dev_txframe_s *tx; FAR struct iob_s *iob; int ret; inode = filep->f_inode; DEBUGASSERT(inode->i_private); dev = inode->i_private; /* Check if the struct is the correct size */ if (len != sizeof(struct mac802154dev_txframe_s)) { wlerr("ERROR: buffer isn't a mac802154dev_txframe_s: %lu\n", (unsigned long)len); return -EINVAL; } DEBUGASSERT(buffer != NULL); tx = (FAR struct mac802154dev_txframe_s *)buffer; /* Allocate an IOB to put the frame in */ iob = iob_alloc(false); DEBUGASSERT(iob != NULL); /* Get the MAC header length */ ret = mac802154_get_mhrlen(dev->md_mac, &tx->meta); if (ret < 0) { wlerr("ERROR: TX meta-data is invalid"); return ret; } iob->io_offset = ret; iob->io_len = iob->io_offset; memcpy(&iob->io_data[iob->io_offset], tx->payload, tx->length); iob->io_len += tx->length; /* Pass the request to the MAC layer */ ret = mac802154_req_data(dev->md_mac, &tx->meta, iob); if (ret < 0) { iob_free(iob); wlerr("ERROR: req_data failed %d\n", ret); return ret; } return OK; } /**************************************************************************** * Name: mac802154dev_ioctl * * Description: * Control the MAC layer via IOCTL commands. * ****************************************************************************/ static int mac802154dev_ioctl(FAR struct file *filep, int cmd, unsigned long arg) { FAR struct inode *inode; FAR struct mac802154_chardevice_s *dev; FAR union ieee802154_macarg_u *macarg = (FAR union ieee802154_macarg_u *)((uintptr_t)arg); int ret; DEBUGASSERT(filep->f_priv != NULL && filep->f_inode != NULL); inode = filep->f_inode; DEBUGASSERT(inode->i_private); dev = inode->i_private; /* Get exclusive access to the driver structure */ ret = nxmutex_lock(&dev->md_lock); if (ret < 0) { wlerr("ERROR: nxsem_wait failed: %d\n", ret); return ret; } switch (cmd) { /* Command: MAC802154IOC_NOTIFY_REGISTER * Description: Register to receive a signal whenever there is a * event primitive sent from the MAC layer. * Argument: The signal number to use. * Return: Zero (OK) on success. Minus one will be returned on * failure with the errno value set appropriately. */ case MAC802154IOC_NOTIFY_REGISTER: { /* Save the notification events */ dev->md_notify_event = macarg->event; dev->md_notify_pid = nxsched_getpid(); dev->md_notify_registered = true; ret = OK; } break; case MAC802154IOC_GET_EVENT: { FAR struct ieee802154_primitive_s *primitive; while (1) { /* Try popping an event off the queue */ primitive = (FAR struct ieee802154_primitive_s *) sq_remfirst(&dev->primitive_queue); /* If there was an event to pop off, copy it into the user data * and free it from the MAC layer's memory. */ if (primitive != NULL) { memcpy(&macarg->primitive, primitive, sizeof(struct ieee802154_primitive_s)); /* Free the notification */ ieee802154_primitive_free(primitive); ret = OK; break; } /* If this is a non-blocking call, or if there is another * getevent operation already pending, don't block. This driver * returns EAGAIN even when configured as non-blocking if * another getevent operation is already pending This situation * should be rare. * It will only occur when there are 2 calls from separate * threads and there was no events in the queue. */ if ((filep->f_oflags & O_NONBLOCK) || dev->geteventpending) { ret = -EAGAIN; break; } dev->geteventpending = true; nxmutex_unlock(&dev->md_lock); /* Wait to be signaled when an event is queued */ ret = nxsem_wait(&dev->geteventsem); if (ret < 0) { dev->geteventpending = false; return ret; } /* Get exclusive access again, then loop back around and try * and pop an event off the queue */ ret = nxmutex_lock(&dev->md_lock); if (ret < 0) { wlerr("ERROR: nxsem_wait failed: %d\n", ret); return ret; } } } break; case MAC802154IOC_ENABLE_EVENTS: { dev->enableevents = macarg->enable; ret = OK; } break; default: { /* Forward any unrecognized commands to the MAC layer */ ret = mac802154_ioctl(dev->md_mac, cmd, arg); } break; } nxmutex_unlock(&dev->md_lock); return ret; } static int mac802154dev_notify(FAR struct mac802154_maccb_s *maccb, FAR struct ieee802154_primitive_s *primitive) { FAR struct mac802154dev_callback_s *cb = (FAR struct mac802154dev_callback_s *)maccb; FAR struct mac802154_chardevice_s *dev; DEBUGASSERT(cb != NULL && cb->mc_priv != NULL); dev = cb->mc_priv; /* Handle the special case for data indications or "incoming frames" */ if (primitive->type == IEEE802154_PRIMITIVE_IND_DATA) { return mac802154dev_rxframe(dev, &primitive->u.dataind); } /* If there is a registered notification receiver, queue the event and * signal the receiver. Events should be popped from the queue from the * application at a reasonable rate in order for the MAC layer to be able * to allocate new notifications. */ if (dev->enableevents && (dev->md_open != NULL || dev->md_notify_registered)) { /* Get exclusive access to the driver structure. We don't care about * any signals so if we see one, just go back to trying to get access * again */ while (nxmutex_lock(&dev->md_lock) != 0); sq_addlast((FAR sq_entry_t *)primitive, &dev->primitive_queue); /* Check if there is a read waiting for data */ if (dev->geteventpending) { /* Wake the thread waiting for the data transmission */ dev->geteventpending = false; nxsem_post(&dev->geteventsem); } if (dev->md_notify_registered) { dev->md_notify_event.sigev_value.sival_int = primitive->type; nxsig_notification(dev->md_notify_pid, &dev->md_notify_event, SI_QUEUE, &dev->md_notify_work); } nxmutex_unlock(&dev->md_lock); return OK; } /* By returning a negative value, we let the MAC know that we don't want * the primitive and it will free it for us */ return -1; } /**************************************************************************** * Name: mac802154dev_rxframe * * Description: * Handle received frames forward by the IEEE 802.15.4 MAC. * * Returned Value: * any failure. On success, the ind and its contained iob will be freed. * The ind will be intact if this function returns a failure. * ****************************************************************************/ static int mac802154dev_rxframe(FAR struct mac802154_chardevice_s *dev, FAR struct ieee802154_data_ind_s *ind) { /* Get exclusive access to the driver structure. We don't care about any * signals so if we see one, just go back to trying to get access again */ while (nxmutex_lock(&dev->md_lock) != 0); /* Push the indication onto the list */ sq_addlast((FAR sq_entry_t *)ind, &dev->dataind_queue); /* Check if there is a read waiting for data */ if (dev->readpending) { /* Wake the thread waiting for the data transmission */ dev->readpending = false; nxsem_post(&dev->readsem); } /* Release the driver */ nxmutex_unlock(&dev->md_lock); return OK; } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: mac802154dev_register * * Description: * Register a character driver to access the IEEE 802.15.4 MAC layer from * user-space * * Input Parameters: * mac - Pointer to the mac layer struct to be registered. * minor - The device minor number. The IEEE802.15.4 MAC character device * will be registered as /dev/ieeeN where N is the minor number * * Returned Value: * Zero (OK) is returned on success. Otherwise a negated errno value is * returned to indicate the nature of the failure. * ****************************************************************************/ int mac802154dev_register(MACHANDLE mac, int minor) { FAR struct mac802154_chardevice_s *dev; FAR struct mac802154_maccb_s *maccb; char devname[DEVNAME_FMTLEN]; int ret; dev = kmm_zalloc(sizeof(struct mac802154_chardevice_s)); if (!dev) { wlerr("ERROR: Failed to allocate device structure\n"); return -ENOMEM; } /* Initialize the new mac driver instance */ dev->md_mac = mac; nxmutex_init(&dev->md_lock); /* Allow the device to be opened once * before blocking */ nxsem_init(&dev->readsem, 0, 0); dev->readpending = false; sq_init(&dev->dataind_queue); dev->geteventpending = false; nxsem_init(&dev->geteventsem, 0, 0); sq_init(&dev->primitive_queue); dev->enableevents = true; dev->md_notify_registered = false; /* Initialize the MAC callbacks */ dev->md_cb.mc_priv = dev; maccb = &dev->md_cb.mc_cb; maccb->flink = NULL; maccb->prio = CONFIG_IEEE802154_MACDEV_RECVRPRIO; maccb->notify = mac802154dev_notify; /* Bind the callback structure */ ret = mac802154_bind(mac, maccb); if (ret < 0) { nerr("ERROR: Failed to bind the MAC callbacks: %d\n", ret); goto errout_with_priv; } /* Create the character device name */ snprintf(devname, sizeof(devname), DEVNAME_FMT, minor); /* Register the mac character driver */ ret = register_driver(devname, &g_mac802154dev_fops, 0666, dev); if (ret < 0) { wlerr("ERROR: register_driver failed: %d\n", ret); goto errout_with_priv; } return OK; errout_with_priv: nxmutex_destroy(&dev->md_lock); kmm_free(dev); return ret; }