/* Returns the first packet from a neighbor queue */ struct tsch_packet * tsch_queue_get_packet_for_nbr(const struct tsch_neighbor *n, struct tsch_link *link) { if(!tsch_is_locked()) { int is_shared_link = link != NULL && link->link_options & LINK_OPTION_SHARED; if(n != NULL) { int16_t get_index = ringbufindex_peek_get(&n->tx_ringbuf); if(get_index != -1 && !(is_shared_link && !tsch_queue_backoff_expired(n))) { /* If this is a shared link, make sure the backoff has expired */ #if TSCH_WITH_LINK_SELECTOR int packet_attr_slotframe = queuebuf_attr(n->tx_array[get_index]->qb, PACKETBUF_ATTR_TSCH_SLOTFRAME); int packet_attr_timeslot = queuebuf_attr(n->tx_array[get_index]->qb, PACKETBUF_ATTR_TSCH_TIMESLOT); if(packet_attr_slotframe != 0xffff && packet_attr_slotframe != link->slotframe_handle) { return NULL; } if(packet_attr_timeslot != 0xffff && packet_attr_timeslot != link->timeslot) { return NULL; } #endif return n->tx_array[get_index]; } } } return NULL; }
/* 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"); } }
/* Returns the head packet from a neighbor queue (from neighbor address) */ struct tsch_packet * tsch_queue_get_packet_for_dest_addr(const linkaddr_t *addr, struct tsch_link *link) { if(!tsch_is_locked()) { return tsch_queue_get_packet_for_nbr(tsch_queue_get_nbr(addr), link); } return NULL; }
/* Returns the number of packets currently in the queue */ int tsch_queue_packet_count(const linkaddr_t *addr) { struct tsch_neighbor *n = NULL; if(!tsch_is_locked()) { n = tsch_queue_add_nbr(addr); if(n != NULL) { return ringbufindex_elements(&n->tx_ringbuf); } } return -1; }
/* Flush all neighbor queues */ void tsch_queue_flush_all(void) { /* Deallocate unneeded neighbors */ if(!tsch_is_locked()) { struct tsch_neighbor *n = list_head(neighbor_list); while(n != NULL) { struct tsch_neighbor *next_n = list_item_next(n); tsch_queue_flush_nbr_queue(n); n = next_n; } } }
/* Get a TSCH time source (we currently assume there is only one) */ struct tsch_neighbor * tsch_queue_get_time_source(void) { if(!tsch_is_locked()) { struct tsch_neighbor *curr_nbr = list_head(neighbor_list); while(curr_nbr != NULL) { if(curr_nbr->is_time_source) { return curr_nbr; } curr_nbr = list_item_next(curr_nbr); } } return NULL; }
/* Get a TSCH neighbor */ struct tsch_neighbor * tsch_queue_get_nbr(const linkaddr_t *addr) { if(!tsch_is_locked()) { struct tsch_neighbor *n = list_head(neighbor_list); while(n != NULL) { if(linkaddr_cmp(&n->addr, addr)) { return n; } n = list_item_next(n); } } return NULL; }
/* 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; }
/* Add packet to neighbor queue. Use same lockfree implementation as ringbuf.c (put is atomic) */ struct tsch_packet * tsch_queue_add_packet(const linkaddr_t *addr, mac_callback_t sent, void *ptr) { struct tsch_neighbor *n = NULL; int16_t put_index = -1; struct tsch_packet *p = NULL; if(!tsch_is_locked()) { n = tsch_queue_add_nbr(addr); if(n != NULL) { put_index = ringbufindex_peek_put(&n->tx_ringbuf); if(put_index != -1) { p = memb_alloc(&packet_memb); if(p != NULL) { /* Enqueue packet */ #ifdef TSCH_CALLBACK_PACKET_READY TSCH_CALLBACK_PACKET_READY(); #endif p->qb = queuebuf_new_from_packetbuf(); if(p->qb != NULL) { p->sent = sent; p->ptr = ptr; p->ret = MAC_TX_DEFERRED; p->transmissions = 0; /* Add to ringbuf (actual add committed through atomic operation) */ n->tx_array[put_index] = p; ringbufindex_put(&n->tx_ringbuf); return p; } else { memb_free(&packet_memb, p); } } } } } PRINTF("TSCH-queue:! add packet failed: %u %p %d %p %p\n", tsch_is_locked(), n, put_index, p, p ? p->qb : NULL); return 0; }
/* Decrement backoff window for all queues directed at dest_addr */ void tsch_queue_update_all_backoff_windows(const linkaddr_t *dest_addr) { if(!tsch_is_locked()) { int is_broadcast = linkaddr_cmp(dest_addr, &tsch_broadcast_address); struct tsch_neighbor *n = list_head(neighbor_list); while(n != NULL) { if(n->backoff_window != 0 /* Is the queue in backoff state? */ && ((n->tx_links_count == 0 && is_broadcast) || (n->tx_links_count > 0 && linkaddr_cmp(dest_addr, &n->addr)))) { n->backoff_window--; } n = list_item_next(n); } } }
/* Flush all neighbor queues */ void tsch_queue_reset(void) { /* Deallocate unneeded neighbors */ if(!tsch_is_locked()) { struct tsch_neighbor *n = list_head(neighbor_list); while(n != NULL) { struct tsch_neighbor *next_n = list_item_next(n); /* Flush queue */ tsch_queue_flush_nbr_queue(n); /* Reset backoff exponent */ tsch_queue_backoff_reset(n); n = next_n; } } }
/* Remove first packet from a neighbor queue */ struct tsch_packet * tsch_queue_remove_packet_from_queue(struct tsch_neighbor *n) { if(!tsch_is_locked()) { if(n != NULL) { /* Get and remove packet from ringbuf (remove committed through an atomic operation */ int16_t get_index = ringbufindex_get(&n->tx_ringbuf); if(get_index != -1) { return n->tx_array[get_index]; } else { return NULL; } } } return NULL; }
/* Update TSCH time source */ int tsch_queue_update_time_source(const linkaddr_t *new_addr) { if(!tsch_is_locked()) { if(!tsch_is_coordinator) { struct tsch_neighbor *old_time_src = tsch_queue_get_time_source(); struct tsch_neighbor *new_time_src = NULL; if(new_addr != NULL) { /* Get/add neighbor, return 0 in case of failure */ new_time_src = tsch_queue_add_nbr(new_addr); if(new_time_src == NULL) { return 0; } } if(new_time_src != old_time_src) { PRINTF("TSCH: update time source: %u -> %u\n", TSCH_LOG_ID_FROM_LINKADDR(old_time_src ? &old_time_src->addr : NULL), TSCH_LOG_ID_FROM_LINKADDR(new_time_src ? &new_time_src->addr : NULL)); /* Update time source */ if(new_time_src != NULL) { new_time_src->is_time_source = 1; /* (Re)set keep-alive timeout */ tsch_set_ka_timeout(TSCH_KEEPALIVE_TIMEOUT); /* Start sending keepalives */ tsch_schedule_keepalive(); } else { /* Stop sending keepalives */ tsch_set_ka_timeout(0); } if(old_time_src != NULL) { old_time_src->is_time_source = 0; } #ifdef TSCH_CALLBACK_NEW_TIME_SOURCE TSCH_CALLBACK_NEW_TIME_SOURCE(old_time_src, new_time_src); #endif } return 1; } } return 0; }
/* 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; }
/* Deallocate neighbors with empty queue */ void tsch_queue_free_unused_neighbors(void) { /* Deallocate unneeded neighbors */ if(!tsch_is_locked()) { struct tsch_neighbor *n = list_head(neighbor_list); while(n != NULL) { struct tsch_neighbor *next_n = list_item_next(n); /* Queue is empty, no tx link to this neighbor: deallocate. * Always keep time source and virtual broadcast neighbors. */ if(!n->is_broadcast && !n->is_time_source && !n->tx_links_count && tsch_queue_is_empty(n)) { tsch_queue_remove_nbr(n); } n = next_n; } } }
/* 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; }
/* Update TSCH time source */ int tsch_queue_update_time_source(const linkaddr_t *new_addr) { if(!tsch_is_locked()) { if(!tsch_is_coordinator) { struct tsch_neighbor *old_time_src = tsch_queue_get_time_source(); struct tsch_neighbor *new_time_src = NULL; if(new_addr != NULL) { /* Get/add neighbor, return 0 in case of failure */ new_time_src = tsch_queue_add_nbr(new_addr); if(new_time_src == NULL) { return 0; } } if(new_time_src != old_time_src) { PRINTF("TSCH: update time source: %u -> %u\n", TSCH_LOG_ID_FROM_LINKADDR(old_time_src ? &old_time_src->addr : NULL), TSCH_LOG_ID_FROM_LINKADDR(new_time_src ? &new_time_src->addr : NULL)); /* Update time source */ if(new_time_src != NULL) { new_time_src->is_time_source = 1; } if(old_time_src != NULL) { old_time_src->is_time_source = 0; } #ifdef TSCH_CALLBACK_NEW_TIME_SOURCE TSCH_CALLBACK_NEW_TIME_SOURCE(old_time_src, new_time_src); #endif } return 1; } } return 0; }
/* Returns the head packet of any neighbor queue with zero backoff counter. * Writes pointer to the neighbor in *n */ struct tsch_packet * tsch_queue_get_unicast_packet_for_any(struct tsch_neighbor **n, struct tsch_link *link) { if(!tsch_is_locked()) { struct tsch_neighbor *curr_nbr = list_head(neighbor_list); struct tsch_packet *p = NULL; while(curr_nbr != NULL) { if(!curr_nbr->is_broadcast && curr_nbr->tx_links_count == 0) { /* Only look up for non-broadcast neighbors we do not have a tx link to */ p = tsch_queue_get_packet_for_nbr(curr_nbr, link); if(p != NULL) { if(n != NULL) { *n = curr_nbr; } return p; } } curr_nbr = list_item_next(curr_nbr); } } return NULL; }
/* Is the neighbor queue empty? */ int tsch_queue_is_empty(const struct tsch_neighbor *n) { return !tsch_is_locked() && n != NULL && ringbufindex_empty(&n->tx_ringbuf); }
/* Returns the next active link after a given ASN, and a backup link (for the same ASN, with Rx flag) */ struct tsch_link * tsch_schedule_get_next_active_link(struct asn_t *asn, uint16_t *time_offset, 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 */ uint16_t timeslot = ASN_MOD(*asn, sf->size); 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; }