2015-09-21 22:01:01 +02:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2014, SICS Swedish ICT.
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
* modification, are permitted provided that the following conditions
|
|
|
|
* are met:
|
|
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer.
|
|
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
|
|
* documentation and/or other materials provided with the distribution.
|
|
|
|
* 3. Neither the name of the Institute nor the names of its contributors
|
|
|
|
* may be used to endorse or promote products derived from this software
|
|
|
|
* without specific prior written permission.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
|
|
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
|
|
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
|
|
* SUCH DAMAGE.
|
|
|
|
*
|
|
|
|
* This file is part of the Contiki operating system.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* \file
|
|
|
|
* IEEE 802.15.4 TSCH MAC schedule manager.
|
|
|
|
* \author
|
|
|
|
* Simon Duquennoy <simonduq@sics.se>
|
|
|
|
* Beshr Al Nahas <beshr@sics.se>
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "contiki.h"
|
|
|
|
#include "dev/leds.h"
|
|
|
|
#include "lib/memb.h"
|
|
|
|
#include "net/nbr-table.h"
|
|
|
|
#include "net/packetbuf.h"
|
|
|
|
#include "net/queuebuf.h"
|
|
|
|
#include "net/mac/tsch/tsch.h"
|
|
|
|
#include "net/mac/tsch/tsch-queue.h"
|
|
|
|
#include "net/mac/tsch/tsch-private.h"
|
|
|
|
#include "net/mac/tsch/tsch-packet.h"
|
|
|
|
#include "net/mac/tsch/tsch-schedule.h"
|
|
|
|
#include "net/mac/tsch/tsch-log.h"
|
|
|
|
#include "net/mac/frame802154.h"
|
|
|
|
#include "sys/process.h"
|
|
|
|
#include "sys/rtimer.h"
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#if TSCH_LOG_LEVEL >= 1
|
|
|
|
#define DEBUG DEBUG_PRINT
|
|
|
|
#else /* TSCH_LOG_LEVEL */
|
|
|
|
#define DEBUG DEBUG_NONE
|
|
|
|
#endif /* TSCH_LOG_LEVEL */
|
2016-04-20 11:45:54 +02:00
|
|
|
#include "net/net-debug.h"
|
2015-09-21 22:01:01 +02:00
|
|
|
|
|
|
|
/* Pre-allocated space for links */
|
|
|
|
MEMB(link_memb, struct tsch_link, TSCH_SCHEDULE_MAX_LINKS);
|
|
|
|
/* Pre-allocated space for slotframes */
|
|
|
|
MEMB(slotframe_memb, struct tsch_slotframe, TSCH_SCHEDULE_MAX_SLOTFRAMES);
|
|
|
|
/* List of slotframes (each slotframe holds its own list of links) */
|
|
|
|
LIST(slotframe_list);
|
|
|
|
|
|
|
|
/* Adds and returns a slotframe (NULL if failure) */
|
|
|
|
struct tsch_slotframe *
|
|
|
|
tsch_schedule_add_slotframe(uint16_t handle, uint16_t size)
|
|
|
|
{
|
|
|
|
if(size == 0) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(tsch_schedule_get_slotframe_by_handle(handle)) {
|
|
|
|
/* A slotframe with this handle already exists */
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(tsch_get_lock()) {
|
|
|
|
struct tsch_slotframe *sf = memb_alloc(&slotframe_memb);
|
|
|
|
if(sf != NULL) {
|
|
|
|
/* Initialize the slotframe */
|
|
|
|
sf->handle = handle;
|
2016-12-14 16:50:28 +01:00
|
|
|
TSCH_ASN_DIVISOR_INIT(sf->size, size);
|
2015-09-21 22:01:01 +02:00
|
|
|
LIST_STRUCT_INIT(sf, links_list);
|
|
|
|
/* Add the slotframe to the global list */
|
|
|
|
list_add(slotframe_list, sf);
|
|
|
|
}
|
|
|
|
PRINTF("TSCH-schedule: add_slotframe %u %u\n",
|
|
|
|
handle, size);
|
|
|
|
tsch_release_lock();
|
|
|
|
return sf;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
/* Removes all slotframes, resulting in an empty schedule */
|
|
|
|
int
|
|
|
|
tsch_schedule_remove_all_slotframes(void)
|
|
|
|
{
|
|
|
|
struct tsch_slotframe *sf;
|
|
|
|
while((sf = list_head(slotframe_list))) {
|
|
|
|
if(tsch_schedule_remove_slotframe(sf) == 0) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
/* Removes a slotframe Return 1 if success, 0 if failure */
|
|
|
|
int
|
|
|
|
tsch_schedule_remove_slotframe(struct tsch_slotframe *slotframe)
|
|
|
|
{
|
|
|
|
if(slotframe != NULL) {
|
|
|
|
/* Remove all links belonging to this slotframe */
|
|
|
|
struct tsch_link *l;
|
|
|
|
while((l = list_head(slotframe->links_list))) {
|
|
|
|
tsch_schedule_remove_link(slotframe, l);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Now that the slotframe has no links, remove it. */
|
|
|
|
if(tsch_get_lock()) {
|
|
|
|
PRINTF("TSCH-schedule: remove slotframe %u %u\n", slotframe->handle, slotframe->size.val);
|
|
|
|
memb_free(&slotframe_memb, slotframe);
|
|
|
|
list_remove(slotframe_list, slotframe);
|
|
|
|
tsch_release_lock();
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
/* Looks for a slotframe from a handle */
|
|
|
|
struct tsch_slotframe *
|
|
|
|
tsch_schedule_get_slotframe_by_handle(uint16_t handle)
|
|
|
|
{
|
|
|
|
if(!tsch_is_locked()) {
|
|
|
|
struct tsch_slotframe *sf = list_head(slotframe_list);
|
|
|
|
while(sf != NULL) {
|
|
|
|
if(sf->handle == handle) {
|
|
|
|
return sf;
|
|
|
|
}
|
|
|
|
sf = list_item_next(sf);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
/* Looks for a link from a handle */
|
|
|
|
struct tsch_link *
|
|
|
|
tsch_schedule_get_link_by_handle(uint16_t handle)
|
|
|
|
{
|
|
|
|
if(!tsch_is_locked()) {
|
|
|
|
struct tsch_slotframe *sf = list_head(slotframe_list);
|
|
|
|
while(sf != NULL) {
|
|
|
|
struct tsch_link *l = list_head(sf->links_list);
|
|
|
|
/* Loop over all items. Assume there is max one link per timeslot */
|
|
|
|
while(l != NULL) {
|
|
|
|
if(l->handle == handle) {
|
|
|
|
return l;
|
|
|
|
}
|
|
|
|
l = list_item_next(l);
|
|
|
|
}
|
|
|
|
sf = list_item_next(sf);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
/* Adds a link to a slotframe, return a pointer to it (NULL if failure) */
|
|
|
|
struct tsch_link *
|
|
|
|
tsch_schedule_add_link(struct tsch_slotframe *slotframe,
|
|
|
|
uint8_t link_options, enum link_type link_type, const linkaddr_t *address,
|
|
|
|
uint16_t timeslot, uint16_t channel_offset)
|
|
|
|
{
|
|
|
|
struct tsch_link *l = NULL;
|
|
|
|
if(slotframe != NULL) {
|
|
|
|
/* We currently support only one link per timeslot in a given slotframe. */
|
|
|
|
/* Start with removing the link currently installed at this timeslot (needed
|
|
|
|
* to keep neighbor state in sync with link options etc.) */
|
|
|
|
tsch_schedule_remove_link_by_timeslot(slotframe, timeslot);
|
|
|
|
if(!tsch_get_lock()) {
|
|
|
|
PRINTF("TSCH-schedule:! add_link memb_alloc couldn't take lock\n");
|
|
|
|
} else {
|
|
|
|
l = memb_alloc(&link_memb);
|
|
|
|
if(l == NULL) {
|
|
|
|
PRINTF("TSCH-schedule:! add_link memb_alloc failed\n");
|
2016-03-31 10:09:47 +02:00
|
|
|
tsch_release_lock();
|
2015-09-21 22:01:01 +02:00
|
|
|
} else {
|
|
|
|
static int current_link_handle = 0;
|
|
|
|
struct tsch_neighbor *n;
|
|
|
|
/* Add the link to the slotframe */
|
|
|
|
list_add(slotframe->links_list, l);
|
|
|
|
/* Initialize link */
|
|
|
|
l->handle = current_link_handle++;
|
|
|
|
l->link_options = link_options;
|
|
|
|
l->link_type = link_type;
|
|
|
|
l->slotframe_handle = slotframe->handle;
|
|
|
|
l->timeslot = timeslot;
|
|
|
|
l->channel_offset = channel_offset;
|
|
|
|
l->data = NULL;
|
|
|
|
if(address == NULL) {
|
|
|
|
address = &linkaddr_null;
|
|
|
|
}
|
|
|
|
linkaddr_copy(&l->addr, address);
|
|
|
|
|
|
|
|
PRINTF("TSCH-schedule: add_link %u %u %u %u %u %u\n",
|
|
|
|
slotframe->handle, link_options, link_type, timeslot, channel_offset, TSCH_LOG_ID_FROM_LINKADDR(address));
|
|
|
|
|
|
|
|
/* Release the lock before we update the neighbor (will take the lock) */
|
|
|
|
tsch_release_lock();
|
|
|
|
|
|
|
|
if(l->link_options & LINK_OPTION_TX) {
|
|
|
|
n = tsch_queue_add_nbr(&l->addr);
|
|
|
|
/* We have a tx link to this neighbor, update counters */
|
|
|
|
if(n != NULL) {
|
|
|
|
n->tx_links_count++;
|
|
|
|
if(!(l->link_options & LINK_OPTION_SHARED)) {
|
|
|
|
n->dedicated_tx_links_count++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return l;
|
|
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
/* Removes a link from slotframe. Return 1 if success, 0 if failure */
|
|
|
|
int
|
|
|
|
tsch_schedule_remove_link(struct tsch_slotframe *slotframe, struct tsch_link *l)
|
|
|
|
{
|
|
|
|
if(slotframe != NULL && l != NULL && l->slotframe_handle == slotframe->handle) {
|
|
|
|
if(tsch_get_lock()) {
|
|
|
|
uint8_t link_options;
|
|
|
|
linkaddr_t addr;
|
|
|
|
|
|
|
|
/* Save link option and addr in local variables as we need them
|
|
|
|
* after freeing the link */
|
|
|
|
link_options = l->link_options;
|
|
|
|
linkaddr_copy(&addr, &l->addr);
|
|
|
|
|
|
|
|
/* The link to be removed is scheduled as next, set it to NULL
|
|
|
|
* to abort the next link operation */
|
|
|
|
if(l == current_link) {
|
|
|
|
current_link = NULL;
|
|
|
|
}
|
|
|
|
PRINTF("TSCH-schedule: remove_link %u %u %u %u %u\n",
|
|
|
|
slotframe->handle, l->link_options, l->timeslot, l->channel_offset,
|
|
|
|
TSCH_LOG_ID_FROM_LINKADDR(&l->addr));
|
|
|
|
|
|
|
|
list_remove(slotframe->links_list, l);
|
|
|
|
memb_free(&link_memb, l);
|
|
|
|
|
|
|
|
/* Release the lock before we update the neighbor (will take the lock) */
|
|
|
|
tsch_release_lock();
|
|
|
|
|
|
|
|
/* This was a tx link to this neighbor, update counters */
|
|
|
|
if(link_options & LINK_OPTION_TX) {
|
|
|
|
struct tsch_neighbor *n = tsch_queue_add_nbr(&addr);
|
|
|
|
if(n != NULL) {
|
|
|
|
n->tx_links_count--;
|
|
|
|
if(!(link_options & LINK_OPTION_SHARED)) {
|
|
|
|
n->dedicated_tx_links_count--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
} else {
|
|
|
|
PRINTF("TSCH-schedule:! remove_link memb_alloc couldn't take lock\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
/* Removes a link from slotframe and timeslot. Return a 1 if success, 0 if failure */
|
|
|
|
int
|
|
|
|
tsch_schedule_remove_link_by_timeslot(struct tsch_slotframe *slotframe, uint16_t timeslot)
|
|
|
|
{
|
|
|
|
return slotframe != NULL &&
|
|
|
|
tsch_schedule_remove_link(slotframe, tsch_schedule_get_link_by_timeslot(slotframe, timeslot));
|
|
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
/* Looks within a slotframe for a link with a given timeslot */
|
|
|
|
struct tsch_link *
|
|
|
|
tsch_schedule_get_link_by_timeslot(struct tsch_slotframe *slotframe, uint16_t timeslot)
|
|
|
|
{
|
|
|
|
if(!tsch_is_locked()) {
|
|
|
|
if(slotframe != NULL) {
|
|
|
|
struct tsch_link *l = list_head(slotframe->links_list);
|
|
|
|
/* Loop over all items. Assume there is max one link per timeslot */
|
|
|
|
while(l != NULL) {
|
|
|
|
if(l->timeslot == timeslot) {
|
|
|
|
return l;
|
|
|
|
}
|
|
|
|
l = list_item_next(l);
|
|
|
|
}
|
|
|
|
return l;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
/* Returns the next active link after a given ASN, and a backup link (for the same ASN, with Rx flag) */
|
|
|
|
struct tsch_link *
|
2016-12-14 16:50:28 +01:00
|
|
|
tsch_schedule_get_next_active_link(struct tsch_asn_t *asn, uint16_t *time_offset,
|
2015-09-21 22:01:01 +02:00
|
|
|
struct tsch_link **backup_link)
|
|
|
|
{
|
|
|
|
uint16_t time_to_curr_best = 0;
|
|
|
|
struct tsch_link *curr_best = NULL;
|
|
|
|
struct tsch_link *curr_backup = NULL; /* Keep a back link in case the current link
|
|
|
|
turns out useless when the time comes. For instance, for a Tx-only link, if there is
|
|
|
|
no outgoing packet in queue. In that case, run the backup link instead. The backup link
|
|
|
|
must have Rx flag set. */
|
|
|
|
if(!tsch_is_locked()) {
|
|
|
|
struct tsch_slotframe *sf = list_head(slotframe_list);
|
|
|
|
/* For each slotframe, look for the earliest occurring link */
|
|
|
|
while(sf != NULL) {
|
|
|
|
/* Get timeslot from ASN, given the slotframe length */
|
2016-12-14 16:50:28 +01:00
|
|
|
uint16_t timeslot = TSCH_ASN_MOD(*asn, sf->size);
|
2015-09-21 22:01:01 +02:00
|
|
|
struct tsch_link *l = list_head(sf->links_list);
|
|
|
|
while(l != NULL) {
|
|
|
|
uint16_t time_to_timeslot =
|
|
|
|
l->timeslot > timeslot ?
|
|
|
|
l->timeslot - timeslot :
|
|
|
|
sf->size.val + l->timeslot - timeslot;
|
|
|
|
if(curr_best == NULL || time_to_timeslot < time_to_curr_best) {
|
|
|
|
time_to_curr_best = time_to_timeslot;
|
|
|
|
curr_best = l;
|
|
|
|
curr_backup = NULL;
|
|
|
|
} else if(time_to_timeslot == time_to_curr_best) {
|
|
|
|
struct tsch_link *new_best = NULL;
|
|
|
|
/* Two links are overlapping, we need to select one of them.
|
|
|
|
* By standard: prioritize Tx links first, second by lowest handle */
|
|
|
|
if((curr_best->link_options & LINK_OPTION_TX) == (l->link_options & LINK_OPTION_TX)) {
|
|
|
|
/* Both or neither links have Tx, select the one with lowest handle */
|
|
|
|
if(l->slotframe_handle < curr_best->slotframe_handle) {
|
|
|
|
new_best = l;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/* Select the link that has the Tx option */
|
|
|
|
if(l->link_options & LINK_OPTION_TX) {
|
|
|
|
new_best = l;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Maintain backup_link */
|
|
|
|
if(curr_backup == NULL) {
|
|
|
|
/* Check if 'l' best can be used as backup */
|
|
|
|
if(new_best != l && (l->link_options & LINK_OPTION_RX)) { /* Does 'l' have Rx flag? */
|
|
|
|
curr_backup = l;
|
|
|
|
}
|
|
|
|
/* Check if curr_best can be used as backup */
|
|
|
|
if(new_best != curr_best && (curr_best->link_options & LINK_OPTION_RX)) { /* Does curr_best have Rx flag? */
|
|
|
|
curr_backup = curr_best;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Maintain curr_best */
|
|
|
|
if(new_best != NULL) {
|
|
|
|
curr_best = new_best;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
l = list_item_next(l);
|
|
|
|
}
|
|
|
|
sf = list_item_next(sf);
|
|
|
|
}
|
|
|
|
if(time_offset != NULL) {
|
|
|
|
*time_offset = time_to_curr_best;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(backup_link != NULL) {
|
|
|
|
*backup_link = curr_backup;
|
|
|
|
}
|
|
|
|
return curr_best;
|
|
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
/* Module initialization, call only once at startup. Returns 1 is success, 0 if failure. */
|
|
|
|
int
|
|
|
|
tsch_schedule_init(void)
|
|
|
|
{
|
|
|
|
if(tsch_get_lock()) {
|
|
|
|
memb_init(&link_memb);
|
|
|
|
memb_init(&slotframe_memb);
|
|
|
|
list_init(slotframe_list);
|
|
|
|
tsch_release_lock();
|
|
|
|
return 1;
|
|
|
|
} else {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
/* Create a 6TiSCH minimal schedule */
|
|
|
|
void
|
|
|
|
tsch_schedule_create_minimal(void)
|
|
|
|
{
|
|
|
|
struct tsch_slotframe *sf_min;
|
|
|
|
/* First, empty current schedule */
|
|
|
|
tsch_schedule_remove_all_slotframes();
|
|
|
|
/* Build 6TiSCH minimal schedule.
|
|
|
|
* We pick a slotframe length of TSCH_SCHEDULE_DEFAULT_LENGTH */
|
|
|
|
sf_min = tsch_schedule_add_slotframe(0, TSCH_SCHEDULE_DEFAULT_LENGTH);
|
|
|
|
/* Add a single Tx|Rx|Shared slot using broadcast address (i.e. usable for unicast and broadcast).
|
|
|
|
* We set the link type to advertising, which is not compliant with 6TiSCH minimal schedule
|
|
|
|
* but is required according to 802.15.4e if also used for EB transmission.
|
|
|
|
* Timeslot: 0, channel offset: 0. */
|
|
|
|
tsch_schedule_add_link(sf_min,
|
|
|
|
LINK_OPTION_RX | LINK_OPTION_TX | LINK_OPTION_SHARED | LINK_OPTION_TIME_KEEPING,
|
|
|
|
LINK_TYPE_ADVERTISING, &tsch_broadcast_address,
|
|
|
|
0, 0);
|
|
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
/* Prints out the current schedule (all slotframes and links) */
|
|
|
|
void
|
|
|
|
tsch_schedule_print(void)
|
|
|
|
{
|
|
|
|
if(!tsch_is_locked()) {
|
|
|
|
struct tsch_slotframe *sf = list_head(slotframe_list);
|
|
|
|
|
|
|
|
printf("Schedule: slotframe list\n");
|
|
|
|
|
|
|
|
while(sf != NULL) {
|
|
|
|
struct tsch_link *l = list_head(sf->links_list);
|
|
|
|
|
|
|
|
printf("[Slotframe] Handle %u, size %u\n", sf->handle, sf->size.val);
|
|
|
|
printf("List of links:\n");
|
|
|
|
|
|
|
|
while(l != NULL) {
|
|
|
|
printf("[Link] Options %02x, type %u, timeslot %u, channel offset %u, address %u\n",
|
|
|
|
l->link_options, l->link_type, l->timeslot, l->channel_offset, l->addr.u8[7]);
|
|
|
|
l = list_item_next(l);
|
|
|
|
}
|
|
|
|
|
|
|
|
sf = list_item_next(sf);
|
|
|
|
}
|
|
|
|
|
|
|
|
printf("Schedule: end of slotframe list\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|