Merge branch 'master' of ssh://contiki.git.sourceforge.net/gitroot/contiki/contiki

This commit is contained in:
nvt 2012-03-28 22:40:38 +02:00
commit bef9b2bd36
23 changed files with 871 additions and 337 deletions

View file

@ -228,6 +228,12 @@ ifndef CUSTOM_RULE_LINK
$(LD) $(LDFLAGS) $(TARGET_STARTFILES) ${filter-out %.a,$^} ${filter %.a,$^} $(TARGET_LIBFILES) -o $@ $(LD) $(LDFLAGS) $(TARGET_STARTFILES) ${filter-out %.a,$^} ${filter %.a,$^} $(TARGET_LIBFILES) -o $@
endif endif
%.ramprof: %.$(TARGET)
nm -S -td --size-sort $< | grep -i " [abdrw] " | cut -d' ' -f2,4
%.flashprof: %.$(TARGET)
nm -S -td --size-sort $< | grep -i " [t] " | cut -d' ' -f2,4
# Don't treat %.$(TARGET) as an intermediate file because it is # Don't treat %.$(TARGET) as an intermediate file because it is
# in fact the primary target. # in fact the primary target.
.PRECIOUS: %.$(TARGET) .PRECIOUS: %.$(TARGET)

View file

@ -44,6 +44,7 @@
#include "dev/watchdog.h" #include "dev/watchdog.h"
#include "net/rime.h" #include "net/rime.h"
#include "net/netstack.h"
#include "dev/cc2420.h" #include "dev/cc2420.h"
#include "dev/leds.h" #include "dev/leds.h"
#include "dev/sht11.h" #include "dev/sht11.h"
@ -97,7 +98,7 @@ do_rssi(void)
static int sample; static int sample;
int channel; int channel;
rime_mac->off(0); NETSTACK_MAC.off(0);
cc2420_on(); cc2420_on();
for(channel = 11; channel <= 26; ++channel) { for(channel = 11; channel <= 26; ++channel) {
@ -105,7 +106,7 @@ do_rssi(void)
rssi_samples[sample].channel[channel - 11] = cc2420_rssi() + 53; rssi_samples[sample].channel[channel - 11] = cc2420_rssi() + 53;
} }
rime_mac->on(); NETSTACK_MAC.on();
sample = (sample + 1) % NUM_SAMPLES; sample = (sample + 1) % NUM_SAMPLES;

View file

@ -522,7 +522,7 @@ static int
send_packet(mac_callback_t mac_callback, void *mac_callback_ptr, struct rdc_buf_list *buf_list) send_packet(mac_callback_t mac_callback, void *mac_callback_ptr, struct rdc_buf_list *buf_list)
{ {
rtimer_clock_t t0; rtimer_clock_t t0;
rtimer_clock_t encounter_time = 0, previous_txtime = 0; rtimer_clock_t encounter_time = 0;
int strobes; int strobes;
uint8_t got_strobe_ack = 0; uint8_t got_strobe_ack = 0;
int hdrlen, len; int hdrlen, len;
@ -723,7 +723,6 @@ send_packet(mac_callback_t mac_callback, void *mac_callback_ptr, struct rdc_buf_
watchdog_periodic(); watchdog_periodic();
t0 = RTIMER_NOW(); t0 = RTIMER_NOW();
seqno = packetbuf_attr(PACKETBUF_ATTR_MAC_SEQNO); seqno = packetbuf_attr(PACKETBUF_ATTR_MAC_SEQNO);
previous_txtime = RTIMER_NOW();
for(strobes = 0, collisions = 0; for(strobes = 0, collisions = 0;
got_strobe_ack == 0 && collisions == 0 && got_strobe_ack == 0 && collisions == 0 &&
RTIMER_CLOCK_LT(RTIMER_NOW(), t0 + STROBE_TIME); strobes++) { RTIMER_CLOCK_LT(RTIMER_NOW(), t0 + STROBE_TIME); strobes++) {
@ -751,7 +750,7 @@ send_packet(mac_callback_t mac_callback, void *mac_callback_ptr, struct rdc_buf_
if(ret == RADIO_TX_OK) { if(ret == RADIO_TX_OK) {
if(!is_broadcast) { if(!is_broadcast) {
got_strobe_ack = 1; got_strobe_ack = 1;
encounter_time = previous_txtime; encounter_time = txtime;
break; break;
} }
} else if (ret == RADIO_TX_NOACK) { } else if (ret == RADIO_TX_NOACK) {
@ -776,7 +775,7 @@ send_packet(mac_callback_t mac_callback, void *mac_callback_ptr, struct rdc_buf_
len = NETSTACK_RADIO.read(ackbuf, ACK_LEN); len = NETSTACK_RADIO.read(ackbuf, ACK_LEN);
if(len == ACK_LEN && seqno == ackbuf[ACK_LEN-1]) { if(len == ACK_LEN && seqno == ackbuf[ACK_LEN-1]) {
got_strobe_ack = 1; got_strobe_ack = 1;
encounter_time = previous_txtime; encounter_time = txtime;
break; break;
} else { } else {
PRINTF("contikimac: collisions while sending\n"); PRINTF("contikimac: collisions while sending\n");
@ -784,8 +783,6 @@ send_packet(mac_callback_t mac_callback, void *mac_callback_ptr, struct rdc_buf_
} }
} }
#endif /* RDC_CONF_HARDWARE_ACK */ #endif /* RDC_CONF_HARDWARE_ACK */
previous_txtime = txtime;
} }
} }

View file

@ -89,8 +89,6 @@ void rime_input(void);
int rime_output(struct channel *c); int rime_output(struct channel *c);
extern const struct mac_driver *rime_mac;
struct rime_sniffer { struct rime_sniffer {
struct rime_sniffer *next; struct rime_sniffer *next;
void (* input_callback)(void); void (* input_callback)(void);

View file

@ -61,8 +61,6 @@
#include "lib/list.h" #include "lib/list.h"
const struct mac_driver *rime_mac;
#ifdef RIME_CONF_BROADCAST_ANNOUNCEMENT_CHANNEL #ifdef RIME_CONF_BROADCAST_ANNOUNCEMENT_CHANNEL
#define BROADCAST_ANNOUNCEMENT_CHANNEL RIME_CONF_BROADCAST_ANNOUNCEMENT_CHANNEL #define BROADCAST_ANNOUNCEMENT_CHANNEL RIME_CONF_BROADCAST_ANNOUNCEMENT_CHANNEL
#else /* RIME_CONF_BROADCAST_ANNOUNCEMENT_CHANNEL */ #else /* RIME_CONF_BROADCAST_ANNOUNCEMENT_CHANNEL */
@ -130,7 +128,6 @@ init(void)
packetbuf_clear(); packetbuf_clear();
announcement_init(); announcement_init();
rime_mac = &NETSTACK_MAC;
chameleon_init(); chameleon_init();
/* XXX This is initializes the transmission of announcements but it /* XXX This is initializes the transmission of announcements but it

View file

@ -211,6 +211,11 @@ static uint8_t rime_payload_len;
* is used this includes the UDP header in addition to the IP header). * is used this includes the UDP header in addition to the IP header).
*/ */
static uint8_t uncomp_hdr_len; static uint8_t uncomp_hdr_len;
/**
* the result of the last transmitted fragment
*/
static int last_tx_status;
/** @} */ /** @} */
#if SICSLOWPAN_CONF_FRAG #if SICSLOWPAN_CONF_FRAG
@ -234,7 +239,7 @@ static uip_buf_t sicslowpan_aligned_buf;
* length of the ip packet already sent / received. * length of the ip packet already sent / received.
* It includes IP and transport headers. * It includes IP and transport headers.
*/ */
static uint16_t processed_ip_len; static uint16_t processed_ip_in_len;
/** Datagram tag to be put in the fragments I send. */ /** Datagram tag to be put in the fragments I send. */
static uint16_t my_tag; static uint16_t my_tag;
@ -1308,6 +1313,7 @@ packet_sent(void *ptr, int status, int transmissions)
if(callback != NULL) { if(callback != NULL) {
callback->output_callback(status); callback->output_callback(status);
} }
last_tx_status = status;
} }
/*--------------------------------------------------------------------*/ /*--------------------------------------------------------------------*/
/** /**
@ -1353,6 +1359,9 @@ output(uip_lladdr_t *localdest)
/* The MAC address of the destination of the packet */ /* The MAC address of the destination of the packet */
rimeaddr_t dest; rimeaddr_t dest;
/* Number of bytes processed. */
uint16_t processed_ip_out_len;
/* init */ /* init */
uncomp_hdr_len = 0; uncomp_hdr_len = 0;
rime_hdr_len = 0; rime_hdr_len = 0;
@ -1443,6 +1452,7 @@ output(uip_lladdr_t *localdest)
((SICSLOWPAN_DISPATCH_FRAG1 << 8) | uip_len)); ((SICSLOWPAN_DISPATCH_FRAG1 << 8) | uip_len));
/* RIME_FRAG_BUF->tag = uip_htons(my_tag); */ /* RIME_FRAG_BUF->tag = uip_htons(my_tag); */
SET16(RIME_FRAG_PTR, RIME_FRAG_TAG, my_tag); SET16(RIME_FRAG_PTR, RIME_FRAG_TAG, my_tag);
my_tag++;
/* Copy payload and send */ /* Copy payload and send */
rime_hdr_len += SICSLOWPAN_FRAG1_HDR_LEN; rime_hdr_len += SICSLOWPAN_FRAG1_HDR_LEN;
@ -1461,8 +1471,16 @@ output(uip_lladdr_t *localdest)
queuebuf_free(q); queuebuf_free(q);
q = NULL; q = NULL;
/* set processed_ip_len to what we already sent from the IP payload*/ /* Check tx result. */
processed_ip_len = rime_payload_len + uncomp_hdr_len; if((last_tx_status == MAC_TX_COLLISION) ||
(last_tx_status == MAC_TX_ERR) ||
(last_tx_status == MAC_TX_ERR_FATAL)) {
PRINTFO("error in fragment tx, dropping subsequent fragments.\n");
return 0;
}
/* set processed_ip_out_len to what we already sent from the IP payload*/
processed_ip_out_len = rime_payload_len + uncomp_hdr_len;
/* /*
* Create following fragments * Create following fragments
@ -1475,19 +1493,19 @@ output(uip_lladdr_t *localdest)
SET16(RIME_FRAG_PTR, RIME_FRAG_DISPATCH_SIZE, SET16(RIME_FRAG_PTR, RIME_FRAG_DISPATCH_SIZE,
((SICSLOWPAN_DISPATCH_FRAGN << 8) | uip_len)); ((SICSLOWPAN_DISPATCH_FRAGN << 8) | uip_len));
rime_payload_len = (MAC_MAX_PAYLOAD - rime_hdr_len) & 0xf8; rime_payload_len = (MAC_MAX_PAYLOAD - rime_hdr_len) & 0xf8;
while(processed_ip_len < uip_len) { while(processed_ip_out_len < uip_len) {
PRINTFO("sicslowpan output: fragment "); PRINTFO("sicslowpan output: fragment ");
RIME_FRAG_PTR[RIME_FRAG_OFFSET] = processed_ip_len >> 3; RIME_FRAG_PTR[RIME_FRAG_OFFSET] = processed_ip_out_len >> 3;
/* Copy payload and send */ /* Copy payload and send */
if(uip_len - processed_ip_len < rime_payload_len) { if(uip_len - processed_ip_out_len < rime_payload_len) {
/* last fragment */ /* last fragment */
rime_payload_len = uip_len - processed_ip_len; rime_payload_len = uip_len - processed_ip_out_len;
} }
PRINTFO("(offset %d, len %d, tag %d)\n", PRINTFO("(offset %d, len %d, tag %d)\n",
processed_ip_len >> 3, rime_payload_len, my_tag); processed_ip_out_len >> 3, rime_payload_len, my_tag);
memcpy(rime_ptr + rime_hdr_len, memcpy(rime_ptr + rime_hdr_len,
(uint8_t *)UIP_IP_BUF + processed_ip_len, rime_payload_len); (uint8_t *)UIP_IP_BUF + processed_ip_out_len, rime_payload_len);
packetbuf_set_datalen(rime_payload_len + rime_hdr_len); packetbuf_set_datalen(rime_payload_len + rime_hdr_len);
q = queuebuf_new_from_packetbuf(); q = queuebuf_new_from_packetbuf();
if(q == NULL) { if(q == NULL) {
@ -1498,12 +1516,16 @@ output(uip_lladdr_t *localdest)
queuebuf_to_packetbuf(q); queuebuf_to_packetbuf(q);
queuebuf_free(q); queuebuf_free(q);
q = NULL; q = NULL;
processed_ip_len += rime_payload_len; processed_ip_out_len += rime_payload_len;
/* Check tx result. */
if((last_tx_status == MAC_TX_COLLISION) ||
(last_tx_status == MAC_TX_ERR) ||
(last_tx_status == MAC_TX_ERR_FATAL)) {
PRINTFO("error in fragment tx, dropping subsequent fragments.\n");
return 0;
}
} }
/* end: reset global variables */
my_tag++;
processed_ip_len = 0;
#else /* SICSLOWPAN_CONF_FRAG */ #else /* SICSLOWPAN_CONF_FRAG */
PRINTFO("sicslowpan output: Packet too large to be sent without fragmentation support; dropping packet\n"); PRINTFO("sicslowpan output: Packet too large to be sent without fragmentation support; dropping packet\n");
return 0; return 0;
@ -1558,7 +1580,7 @@ input(void)
/* if reassembly timed out, cancel it */ /* if reassembly timed out, cancel it */
if(timer_expired(&reass_timer)) { if(timer_expired(&reass_timer)) {
sicslowpan_len = 0; sicslowpan_len = 0;
processed_ip_len = 0; processed_ip_in_len = 0;
} }
/* /*
* Since we don't support the mesh and broadcast header, the first header * Since we don't support the mesh and broadcast header, the first header
@ -1593,10 +1615,10 @@ input(void)
/* If this is the last fragment, we may shave off any extrenous /* If this is the last fragment, we may shave off any extrenous
bytes at the end. We must be liberal in what we accept. */ bytes at the end. We must be liberal in what we accept. */
PRINTFI("last_fragment?: processed_ip_len %d rime_payload_len %d frag_size %d\n", PRINTFI("last_fragment?: processed_ip_in_len %d rime_payload_len %d frag_size %d\n",
processed_ip_len, packetbuf_datalen() - rime_hdr_len, frag_size); processed_ip_in_len, packetbuf_datalen() - rime_hdr_len, frag_size);
if(processed_ip_len + packetbuf_datalen() - rime_hdr_len >= frag_size) { if(processed_ip_in_len + packetbuf_datalen() - rime_hdr_len >= frag_size) {
last_fragment = 1; last_fragment = 1;
} }
break; break;
@ -1604,7 +1626,7 @@ input(void)
break; break;
} }
if(processed_ip_len > 0) { if(processed_ip_in_len > 0) {
/* reassembly is ongoing */ /* reassembly is ongoing */
/* printf("frag %d %d\n", reass_tag, frag_tag);*/ /* printf("frag %d %d\n", reass_tag, frag_tag);*/
if((frag_size > 0 && if((frag_size > 0 &&
@ -1624,7 +1646,7 @@ input(void)
* reassembly is off * reassembly is off
* start it if we received a fragment * start it if we received a fragment
*/ */
if(frag_size > 0) { if((frag_size > 0) && (frag_size <= UIP_BUFSIZE)) {
sicslowpan_len = frag_size; sicslowpan_len = frag_size;
reass_tag = frag_tag; reass_tag = frag_tag;
timer_set(&reass_timer, SICSLOWPAN_REASS_MAXAGE*CLOCK_SECOND); timer_set(&reass_timer, SICSLOWPAN_REASS_MAXAGE*CLOCK_SECOND);
@ -1690,22 +1712,22 @@ input(void)
rime_payload_len = packetbuf_datalen() - rime_hdr_len; rime_payload_len = packetbuf_datalen() - rime_hdr_len;
memcpy((uint8_t *)SICSLOWPAN_IP_BUF + uncomp_hdr_len + (uint16_t)(frag_offset << 3), rime_ptr + rime_hdr_len, rime_payload_len); memcpy((uint8_t *)SICSLOWPAN_IP_BUF + uncomp_hdr_len + (uint16_t)(frag_offset << 3), rime_ptr + rime_hdr_len, rime_payload_len);
/* update processed_ip_len if fragment, sicslowpan_len otherwise */ /* update processed_ip_in_len if fragment, sicslowpan_len otherwise */
#if SICSLOWPAN_CONF_FRAG #if SICSLOWPAN_CONF_FRAG
if(frag_size > 0) { if(frag_size > 0) {
/* Add the size of the header only for the first fragment. */ /* Add the size of the header only for the first fragment. */
if(first_fragment != 0) { if(first_fragment != 0) {
processed_ip_len += uncomp_hdr_len; processed_ip_in_len += uncomp_hdr_len;
} }
/* For the last fragment, we are OK if there is extrenous bytes at /* For the last fragment, we are OK if there is extrenous bytes at
the end of the packet. */ the end of the packet. */
if(last_fragment != 0) { if(last_fragment != 0) {
processed_ip_len = frag_size; processed_ip_in_len = frag_size;
} else { } else {
processed_ip_len += rime_payload_len; processed_ip_in_len += rime_payload_len;
} }
PRINTF("processed_ip_len %d, rime_payload_len %d\n", processed_ip_len, rime_payload_len); PRINTF("processed_ip_in_len %d, rime_payload_len %d\n", processed_ip_in_len, rime_payload_len);
} else { } else {
#endif /* SICSLOWPAN_CONF_FRAG */ #endif /* SICSLOWPAN_CONF_FRAG */
@ -1717,15 +1739,15 @@ input(void)
* If we have a full IP packet in sicslowpan_buf, deliver it to * If we have a full IP packet in sicslowpan_buf, deliver it to
* the IP stack * the IP stack
*/ */
PRINTF("sicslowpan_init processed_ip_len %d, sicslowpan_len %d\n", PRINTF("sicslowpan_init processed_ip_in_len %d, sicslowpan_len %d\n",
processed_ip_len, sicslowpan_len); processed_ip_in_len, sicslowpan_len);
if(processed_ip_len == 0 || (processed_ip_len == sicslowpan_len)) { if(processed_ip_in_len == 0 || (processed_ip_in_len == sicslowpan_len)) {
PRINTFI("sicslowpan input: IP packet ready (length %d)\n", PRINTFI("sicslowpan input: IP packet ready (length %d)\n",
sicslowpan_len); sicslowpan_len);
memcpy((uint8_t *)UIP_IP_BUF, (uint8_t *)SICSLOWPAN_IP_BUF, sicslowpan_len); memcpy((uint8_t *)UIP_IP_BUF, (uint8_t *)SICSLOWPAN_IP_BUF, sicslowpan_len);
uip_len = sicslowpan_len; uip_len = sicslowpan_len;
sicslowpan_len = 0; sicslowpan_len = 0;
processed_ip_len = 0; processed_ip_in_len = 0;
#endif /* SICSLOWPAN_CONF_FRAG */ #endif /* SICSLOWPAN_CONF_FRAG */
#if DEBUG #if DEBUG

View file

@ -570,6 +570,7 @@ tcpip_ipv6_output(void)
PRINTF("FALLBACK: removing ext hdrs & setting proto %d %d\n", PRINTF("FALLBACK: removing ext hdrs & setting proto %d %d\n",
uip_ext_len, *((uint8_t *)UIP_IP_BUF + 40)); uip_ext_len, *((uint8_t *)UIP_IP_BUF + 40));
if(uip_ext_len > 0) { if(uip_ext_len > 0) {
extern void remove_ext_hdr(void);
uint8_t proto = *((uint8_t *)UIP_IP_BUF + 40); uint8_t proto = *((uint8_t *)UIP_IP_BUF + 40);
remove_ext_hdr(); remove_ext_hdr();
/* This should be copied from the ext header... */ /* This should be copied from the ext header... */
@ -643,7 +644,6 @@ tcpip_ipv6_output(void)
PRINTF("tcpip_ipv6_output: nbr cache entry stale moving to delay\n"); PRINTF("tcpip_ipv6_output: nbr cache entry stale moving to delay\n");
} }
stimer_set(&nbr->sendns, uip_ds6_if.retrans_timer / 1000);
tcpip_output(&nbr->lladdr); tcpip_output(&nbr->lladdr);
#if UIP_CONF_IPV6_QUEUE_PKT #if UIP_CONF_IPV6_QUEUE_PKT

View file

@ -212,12 +212,11 @@ uip_ds6_periodic(void)
} }
break; break;
case NBR_DELAY: case NBR_DELAY:
if(stimer_expired(&locnbr->reachable) && (uip_len == 0)) { if(stimer_expired(&locnbr->reachable)) {
locnbr->state = NBR_PROBE; locnbr->state = NBR_PROBE;
locnbr->nscount = 1; locnbr->nscount = 0;
PRINTF("DELAY: moving to PROBE + NS %u\n", locnbr->nscount); PRINTF("DELAY: moving to PROBE\n");
uip_nd6_ns_output(NULL, &locnbr->ipaddr, &locnbr->ipaddr); stimer_set(&locnbr->sendns, 0);
stimer_set(&locnbr->sendns, uip_ds6_if.retrans_timer / 1000);
} }
break; break;
case NBR_PROBE: case NBR_PROBE:

View file

@ -166,8 +166,8 @@ loader-init.o: ${CONTIKI_TARGET}/loader/loader-init.S
$(AS) -o $(notdir $(<:.S=.o)) $< $(AS) -o $(notdir $(<:.S=.o)) $<
# cp loader-init.o build-app/ # cp loader-init.o build-app/
.PHONY: symbols.c symbols.h
ifdef CORE ifdef CORE
.PHONY: symbols.c symbols.h
symbols.c: symbols.c:
$(NM) $(CORE) | awk -f $(CONTIKI)/tools/mknmlist > symbols.c $(NM) $(CORE) | awk -f $(CONTIKI)/tools/mknmlist > symbols.c
else else

View file

@ -1,6 +1,11 @@
.SUFFIXES: .SUFFIXES:
define \n
endef
ifdef IAR ifdef IAR
${info Using IAR...} ${info Using IAR...}
#IAR_PATH = C:/Program\ Files/IAR\ Systems/Embedded\ Workbench\ 5.4\ Evaluation #IAR_PATH = C:/Program\ Files/IAR\ Systems/Embedded\ Workbench\ 5.4\ Evaluation
@ -18,7 +23,7 @@ CONTIKI_CPU_DIRS = . dev hal simplemac hal/micro/cortexm3 hal/micro/cortexm3/stm
STM32W_C = leds-arch.c leds.c clock.c watchdog.c uart1.c uart1-putchar.c slip_uart1.c slip.c\ STM32W_C = leds-arch.c leds.c clock.c watchdog.c uart1.c uart1-putchar.c slip_uart1.c slip.c\
stm32w-radio.c stm32w_systick.c uip_arch.c rtimer-arch.c adc.c micro.c sleep.c \ stm32w-radio.c stm32w_systick.c uip_arch.c rtimer-arch.c adc.c micro.c sleep.c \
micro-common.c micro-common-internal.c clocks.c mfg-token.c nvm.c flash.c rand.c system-timer.c micro-common.c micro-common-internal.c clocks.c mfg-token.c nvm.c flash.c rand.c system-timer.c mpu.c
STM32W_S = spmr.s79 context-switch.s79 STM32W_S = spmr.s79 context-switch.s79
@ -163,7 +168,7 @@ endif
FLASHER = $(CONTIKI)/tools/stm32w/stm32w_flasher/stm32w_flasher FLASHER = sudo $(CONTIKI)/tools/stm32w/stm32w_flasher/stm32w_flasher
# Check if we are running under Windows # Check if we are running under Windows
ifeq ($(HOST_OS),Windows) ifeq ($(HOST_OS),Windows)
@ -224,11 +229,11 @@ CUSTOM_RULE_C_TO_CE = 1
CUSTOM_RULE_LINK = 1 CUSTOM_RULE_LINK = 1
.PHONY: symbols.c symbols.h
ifdef CORE ifdef CORE
ifeq ($(wildcard $(CORE)),) ifeq ($(wildcard $(CORE)),)
${error $(CORE) doesn't exist} ${error $(CORE) doesn't exist}
endif endif
.PHONY: symbols.c symbols.h
symbols.c: symbols.c:
$(NM) $(CORE) | awk -f $(CONTIKI)/tools/mknmlist > symbols.c $(NM) $(CORE) | awk -f $(CONTIKI)/tools/mknmlist > symbols.c
else else
@ -250,6 +255,19 @@ endif
endif #IAR endif #IAR
MOTELIST = $(CONTIKI)/tools/stm32w/motelist-linux
MOTES = $(shell $(MOTELIST) 2>&- | grep USB | \
cut -f 4 -d \ | \
perl -ne 'print $$1 . " " if(m-(/dev/\w+)-);')
motelist: stm-motelist
stm-motelist:
$(MOTELIST)
stm-motes:
@echo $(MOTES)
$(OBJECTDIR)/%.o: %.s79 $(OBJECTDIR)/%.o: %.s79
$(AS) $(ASFLAGS) -o $@ $< $(AS) $(ASFLAGS) -o $@ $<
@ -258,7 +276,24 @@ $(OBJECTDIR)/%.o: %.s
%.bin: %.$(TARGET) %.bin: %.$(TARGET)
$(OBJCOPY) $(OBJOPTS) $< $@ $(OBJCOPY) $(OBJOPTS) $< $@
%.flash: %.bin # reset all stm32w devices sequentially, as stm32w_flasher cannot access different ports in parallel
$(FLASHER) $(FLASHEROPTS) $< stm-reset:
$(foreach PORT, $(MOTES), $(FLASHER) -r -p $(PORT);$(\n))
@echo Done
ifdef MOTE
%.upload: %.bin
$(FLASHER) $(FLASHEROPTS) $< -p $(word $(MOTE), $(MOTES))
else # MOTE
%.upload: %.bin
$(foreach PORT, $(MOTES), $(FLASHER) $(FLASHEROPTS) $< -p $(PORT);$(\n))
endif # MOTE
ifdef MOTE
login:
$(SERIALDUMP) -b115200 -d10000 $(USBDEVPREFIX)$(word $(MOTE), $(MOTES))
else
login:
$(SERIALDUMP) -b115200 -d10000 $(USBDEVPREFIX)$(firstword $(MOTES))
endif

View file

@ -21,8 +21,8 @@ endif
%.so: $(OBJECTDIR)/%.o %.so: $(OBJECTDIR)/%.o
$(LD) -shared -o $@ $^ $(LD) -shared -o $@ $^
# .PHONY: symbols.c symbols.h
ifdef CORE ifdef CORE
.PHONY: symbols.c symbols.h
symbols.c symbols.h: symbols.c symbols.h:
$(NM) $(CORE) | awk -f $(CONTIKI)/tools/mknmlist > symbols.c $(NM) $(CORE) | awk -f $(CONTIKI)/tools/mknmlist > symbols.c
else else

View file

@ -41,6 +41,7 @@
#include "contiki.h" #include "contiki.h"
#include "net/rime.h" #include "net/rime.h"
#include "net/netstack.h"
#include "dev/leds.h" #include "dev/leds.h"
#include "dev/cc2420.h" #include "dev/cc2420.h"
@ -81,7 +82,7 @@ PROCESS_THREAD(scanner_process, ev, data)
{ {
PROCESS_BEGIN(); PROCESS_BEGIN();
/* switch mac layer off, and turn radio on */ /* switch mac layer off, and turn radio on */
rime_mac->off(0); NETSTACK_MAC.off(0);
cc2420_on(); cc2420_on();
while(1) { while(1) {

View file

@ -39,6 +39,7 @@
*/ */
#include "contiki.h" #include "contiki.h"
#include "net/netstack.h"
#include "net/rime.h" #include "net/rime.h"
#include "net/rime/collect.h" #include "net/rime/collect.h"
#include "net/rime/collect-neighbor.h" #include "net/rime/collect-neighbor.h"
@ -133,7 +134,7 @@ do_rssi(void)
static int sample; static int sample;
int channel; int channel;
rime_mac->off(0); NETSTACK_MAC.off(0);
cc2420_on(); cc2420_on();
for(channel = 11; channel <= 26; ++channel) { for(channel = 11; channel <= 26; ++channel) {
@ -141,7 +142,7 @@ do_rssi(void)
rssi_samples[sample].channel[channel - 11] = cc2420_rssi() + 53; rssi_samples[sample].channel[channel - 11] = cc2420_rssi() + 53;
} }
rime_mac->on(); NETSTACK_MAC.on();
sample = (sample + 1) % NUM_SAMPLES; sample = (sample + 1) % NUM_SAMPLES;

View file

@ -41,6 +41,7 @@
#include "contiki.h" #include "contiki.h"
#include "net/rime.h" #include "net/rime.h"
#include "net/netstack.h"
#include "dev/leds.h" #include "dev/leds.h"
#include "dev/cc2420.h" #include "dev/cc2420.h"
@ -83,7 +84,7 @@ PROCESS_THREAD(scanner_process, ev, data)
{ {
PROCESS_BEGIN(); PROCESS_BEGIN();
/* switch mac layer off, and turn radio on */ /* switch mac layer off, and turn radio on */
rime_mac->off(0); NETSTACK_MAC.off(0);
cc2420_on(); cc2420_on();
while(1) { while(1) {

View file

@ -22,8 +22,3 @@ SERIALDUMP = $(CONTIKI)/tools/stm32w/serialdump-linux
ifeq ($(HOST_OS),Windows) ifeq ($(HOST_OS),Windows)
SERIALDUMP = $(CONTIKI)/tools/stm32w/serialdump-windows SERIALDUMP = $(CONTIKI)/tools/stm32w/serialdump-windows
endif endif
login:
$(SERIALDUMP) -b115200 -d10000 $(PORT)

View file

@ -172,6 +172,18 @@ main(int argc, char **argv)
contiki_argc = argc; contiki_argc = argc;
contiki_argv = argv; contiki_argv = argv;
/* native under windows is hardcoded to use the first one or two args */
/* for wpcap configuration so this needs to be "removed" from */
/* contiki_args (used by the native-border-router) */
#ifdef __CYGWIN__
contiki_argc--;
contiki_argv++;
#ifdef UIP_FALLBACK_INTERFACE
contiki_argc--;
contiki_argv++;
#endif
#endif
process_init(); process_init();
process_start(&etimer_process, NULL); process_start(&etimer_process, NULL);
ctimer_init(); ctimer_init();

View file

@ -36,7 +36,6 @@ import java.io.IOException;
import java.io.PrintStream; import java.io.PrintStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Enumeration;
import java.util.Hashtable; import java.util.Hashtable;
import java.util.Observable; import java.util.Observable;
@ -582,9 +581,7 @@ public abstract class MspMote extends AbstractEmulatedMote implements Mote, Watc
/* Match file */ /* Match file */
Hashtable<Integer, Integer> lineTable = debuggingInfo.get(file); Hashtable<Integer, Integer> lineTable = debuggingInfo.get(file);
if (lineTable == null) { if (lineTable == null) {
Enumeration<File> fileEnum = debuggingInfo.keys(); for (File f: debuggingInfo.keySet()) {
while (fileEnum.hasMoreElements()) {
File f = fileEnum.nextElement();
if (f != null && f.getName().equals(file.getName())) { if (f != null && f.getName().equals(file.getName())) {
lineTable = debuggingInfo.get(f); lineTable = debuggingInfo.get(f);
break; break;
@ -598,9 +595,7 @@ public abstract class MspMote extends AbstractEmulatedMote implements Mote, Watc
/* Match line number */ /* Match line number */
Integer address = lineTable.get(lineNr); Integer address = lineTable.get(lineNr);
if (address != null) { if (address != null) {
Enumeration<Integer> lineEnum = lineTable.keys(); for (Integer l: lineTable.keySet()) {
while (lineEnum.hasMoreElements()) {
Integer l = lineEnum.nextElement();
if (l != null && l.intValue() == lineNr) { if (l != null && l.intValue() == lineNr) {
/* Found line address */ /* Found line address */
return lineTable.get(l); return lineTable.get(l);

View file

@ -317,7 +317,7 @@ public abstract class MspMoteType implements MoteType {
logger.warn("Old simulation config detected: SkySerial was replaced by MspSerial"); logger.warn("Old simulation config detected: SkySerial was replaced by MspSerial");
intfClass = MspSerial.class.getName(); intfClass = MspSerial.class.getName();
} }
Class<? extends MoteInterface> moteInterfaceClass = Class<? extends MoteInterface> moteInterfaceClass =
simulation.getGUI().tryLoadClass(this, MoteInterface.class, intfClass); simulation.getGUI().tryLoadClass(this, MoteInterface.class, intfClass);
@ -368,7 +368,7 @@ public abstract class MspMoteType implements MoteType {
private static ELF loadELF(String filepath) throws IOException { private static ELF loadELF(String filepath) throws IOException {
return ELF.readELF(filepath); return ELF.readELF(filepath);
} }
private ELF elf; /* cached */ private ELF elf; /* cached */
public ELF getELF() throws IOException { public ELF getELF() throws IOException {
if (elf == null) { if (elf == null) {
@ -379,9 +379,9 @@ public abstract class MspMoteType implements MoteType {
} }
return elf; return elf;
} }
private Hashtable<File, Hashtable<Integer, Integer>> debuggingInfo = null; /* cached */ private Hashtable<File, Hashtable<Integer, Integer>> debuggingInfo = null; /* cached */
public Hashtable<File, Hashtable<Integer, Integer>> getFirmwareDebugInfo() public Hashtable<File, Hashtable<Integer, Integer>> getFirmwareDebugInfo()
throws IOException { throws IOException {
if (debuggingInfo == null) { if (debuggingInfo == null) {
debuggingInfo = getFirmwareDebugInfo(getELF()); debuggingInfo = getFirmwareDebugInfo(getELF());
@ -407,33 +407,35 @@ public abstract class MspMoteType implements MoteType {
for (int address: addresses) { for (int address: addresses) {
DebugInfo info = elf.getDebugInfo(address); DebugInfo info = elf.getDebugInfo(address);
if (info == null) {
if (info != null && info.getPath() != null && info.getFile() != null && info.getLine() >= 0) { continue;
/* Nasty Cygwin-Windows fix */
String path = info.getPath();
if (path.contains("/cygdrive/")) {
int index = path.indexOf("/cygdrive/");
char driveCharacter = path.charAt(index+10);
path = path.replace("/cygdrive/" + driveCharacter + "/", driveCharacter + ":/");
}
File file = new File(path, info.getFile());
try {
file = file.getCanonicalFile();
} catch (IOException e) {
} catch (java.security.AccessControlException e) {
}
Hashtable<Integer, Integer> lineToAddrHash = fileToLineHash.get(file);
if (lineToAddrHash == null) {
lineToAddrHash = new Hashtable<Integer, Integer>();
fileToLineHash.put(file, lineToAddrHash);
}
lineToAddrHash.put(info.getLine(), address);
} }
if (info.getPath() == null && info.getFile() == null) {
continue;
}
if (info.getLine() < 0) {
continue;
}
File file;
if (info.getPath() != null) {
file = new File(info.getPath(), info.getFile());
} else {
file = new File(info.getFile());
}
try {
file = file.getCanonicalFile();
} catch (IOException e) {
} catch (java.security.AccessControlException e) {
}
Hashtable<Integer, Integer> lineToAddrHash = fileToLineHash.get(file);
if (lineToAddrHash == null) {
lineToAddrHash = new Hashtable<Integer, Integer>();
fileToLineHash.put(file, lineToAddrHash);
}
lineToAddrHash.put(info.getLine(), address);
} }
return fileToLineHash; return fileToLineHash;

View file

@ -163,6 +163,8 @@ public class CodeUI extends JPanel {
} }
/* Configure breakpoint menu options */ /* Configure breakpoint menu options */
/* XXX TODO We should ask for the file specified in the firmware, not
* the actual file on disk. */
Integer address = Integer address =
CodeUI.this.mote.getExecutableAddressOf(displayedFile, line); CodeUI.this.mote.getExecutableAddressOf(displayedFile, line);
if (address == null) { if (address == null) {
@ -254,7 +256,8 @@ public class CodeUI extends JPanel {
SwingUtilities.invokeLater(new Runnable() { SwingUtilities.invokeLater(new Runnable() {
public void run() { public void run() {
displayedFile = null; displayedFile = null;
codeEditor.setText(null); codeEditor.setText("[no source displayed]");
codeEditor.setEnabled(false);
codeEditorLines.clear(); codeEditorLines.clear();
displayLine(-1, markCurrent); displayLine(-1, markCurrent);
} }
@ -275,6 +278,7 @@ public class CodeUI extends JPanel {
displayNoCode(markCurrent); displayNoCode(markCurrent);
return; return;
} }
codeEditor.setEnabled(true);
String[] lines = data.split("\n"); String[] lines = data.split("\n");
logger.info("Opening " + codeFile + " (" + lines.length + " lines)"); logger.info("Opening " + codeFile + " (" + lines.length + " lines)");

View file

@ -32,12 +32,15 @@ package se.sics.cooja.mspmote.plugins;
import java.awt.BorderLayout; import java.awt.BorderLayout;
import java.awt.Color; import java.awt.Color;
import java.awt.Component; import java.awt.Component;
import java.awt.Window;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Comparator;
import java.util.Observable; import java.util.Observable;
import java.util.Observer; import java.util.Observer;
@ -47,23 +50,23 @@ import javax.swing.Box;
import javax.swing.JButton; import javax.swing.JButton;
import javax.swing.JComboBox; import javax.swing.JComboBox;
import javax.swing.JDialog; import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JLabel; import javax.swing.JLabel;
import javax.swing.JList; import javax.swing.JList;
import javax.swing.JOptionPane; import javax.swing.JOptionPane;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane; import javax.swing.JTabbedPane;
import javax.swing.JTextField; import javax.swing.JTable;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import javax.swing.filechooser.FileFilter;
import javax.swing.plaf.basic.BasicComboBoxRenderer; import javax.swing.plaf.basic.BasicComboBoxRenderer;
import javax.swing.table.AbstractTableModel;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import org.jdom.Element; import org.jdom.Element;
import se.sics.cooja.ClassDescription; import se.sics.cooja.ClassDescription;
import se.sics.cooja.GUI; import se.sics.cooja.GUI;
import se.sics.cooja.GUI.RunnableInEDT;
import se.sics.cooja.Mote; import se.sics.cooja.Mote;
import se.sics.cooja.MotePlugin; import se.sics.cooja.MotePlugin;
import se.sics.cooja.PluginType; import se.sics.cooja.PluginType;
@ -72,6 +75,7 @@ import se.sics.cooja.VisPlugin;
import se.sics.cooja.Watchpoint; import se.sics.cooja.Watchpoint;
import se.sics.cooja.WatchpointMote; import se.sics.cooja.WatchpointMote;
import se.sics.cooja.WatchpointMote.WatchpointListener; import se.sics.cooja.WatchpointMote.WatchpointListener;
import se.sics.cooja.dialogs.MessageList;
import se.sics.cooja.mspmote.MspMote; import se.sics.cooja.mspmote.MspMote;
import se.sics.cooja.mspmote.MspMoteType; import se.sics.cooja.mspmote.MspMoteType;
import se.sics.mspsim.core.EmulationException; import se.sics.mspsim.core.EmulationException;
@ -103,11 +107,14 @@ public class MspCodeWatcher extends VisPlugin implements MotePlugin {
private WatchpointListener watchpointListener; private WatchpointListener watchpointListener;
private JComboBox fileComboBox; private JComboBox fileComboBox;
private String[] debugInfoMap = null;
private File[] sourceFiles; private File[] sourceFiles;
private JTabbedPane mainPane; private JTabbedPane mainPane;
private ArrayList<Rule> rules;
private ELFDebug debug;
private String[] debugSourceFiles;
/** /**
* Mini-debugger for MSP Motes. * Mini-debugger for MSP Motes.
* Visualizes instructions, source code and allows a user to manipulate breakpoints. * Visualizes instructions, source code and allows a user to manipulate breakpoints.
@ -121,6 +128,41 @@ public class MspCodeWatcher extends VisPlugin implements MotePlugin {
simulation = simulationToVisualize; simulation = simulationToVisualize;
this.mspMote = (MspMote) mote; this.mspMote = (MspMote) mote;
this.watchpointMote = (WatchpointMote) mote; this.watchpointMote = (WatchpointMote) mote;
try {
debug = ((MspMoteType)mspMote.getType()).getELF().getDebug();
if (debug == null) {
throw new RuntimeException("No debugging info found in firmware, aborting");
}
debugSourceFiles = debug.getSourceFiles();
if (debugSourceFiles == null) {
throw new RuntimeException("No debugging info found in firmware, aborting");
}
} catch (IOException e1) {
throw new RuntimeException("No debugging info found in firmware, aborting");
}
/* XXX Temporary workaround: source file removing duplicates awaiting Mspsim update */
{
ArrayList<String> newDebugSourceFiles = new ArrayList<String>();
for (String sf: debugSourceFiles) {
boolean found = false;
for (String nsf: newDebugSourceFiles) {
if (sf.equals(nsf)) {
found = true;
break;
}
}
if (!found) {
newDebugSourceFiles.add(sf);
}
}
debugSourceFiles = newDebugSourceFiles.toArray(new String[0]);
}
rules = new ArrayList<Rule>();
loadDefaultRules();
getContentPane().setLayout(new BorderLayout()); getContentPane().setLayout(new BorderLayout());
@ -150,7 +192,6 @@ public class MspCodeWatcher extends VisPlugin implements MotePlugin {
return this; return this;
} }
}); });
updateFileComboBox();
/* Browse code control (north) */ /* Browse code control (north) */
Box sourceCodeControl = Box.createHorizontalBox(); Box sourceCodeControl = Box.createHorizontalBox();
@ -223,8 +264,13 @@ public class MspCodeWatcher extends VisPlugin implements MotePlugin {
showCurrentPC(); showCurrentPC();
} }
public void startPlugin() {
super.startPlugin();
updateFileComboBox();
}
private void updateFileComboBox() { private void updateFileComboBox() {
sourceFiles = getSourceFiles(mspMote, debugInfoMap); sourceFiles = getSourceFiles(mspMote, rules);
fileComboBox.removeAllItems(); fileComboBox.removeAllItems();
fileComboBox.addItem("[view sourcefile]"); fileComboBox.addItem("[view sourcefile]");
for (File f: sourceFiles) { for (File f: sourceFiles) {
@ -288,39 +334,22 @@ public class MspCodeWatcher extends VisPlugin implements MotePlugin {
currentCodeFile = null; currentCodeFile = null;
try { try {
ELFDebug debug = ((MspMoteType)mspMote.getType()).getELF().getDebug();
if (debug == null) {
return;
}
int pc = mspMote.getCPU().getPC(); int pc = mspMote.getCPU().getPC();
DebugInfo debugInfo = debug.getDebugInfo(pc); DebugInfo debugInfo = debug.getDebugInfo(pc);
if (pc <= 0) {
return;
}
if (debugInfo == null) { if (debugInfo == null) {
if (pc != 0) { logger.warn("No source info at " + String.format("0x%04x", pc));
logger.warn("No sourcecode info at " + String.format("0x%04x", mspMote.getCPU().getPC())); return;
} }
File f = applySubstitutionRules(new File(debugInfo.getPath(), debugInfo.getFile()), rules);
if (f == null || !f.exists()) {
logger.warn("Unknown source at " + String.format("0x%04x", pc) + ": " + debugInfo.getPath() + " " + debugInfo.getFile());
return; return;
} }
String path = new File(debugInfo.getPath(), debugInfo.getFile()).getPath(); currentCodeFile = f;
if (path == null) {
return;
}
path = path.replace('\\', '/');
/* Debug info to source file map */
if (debugInfoMap != null && debugInfoMap.length == 2) {
if (path.startsWith(debugInfoMap[0])) {
path = debugInfoMap[1] + path.substring(debugInfoMap[0].length());
}
}
/* Nasty Cygwin-Windows fix */
if (path.length() > 10 && path.startsWith("/cygdrive/")) {
char driveCharacter = path.charAt(10);
path = driveCharacter + ":/" + path.substring(11);
}
currentCodeFile = new File(path).getCanonicalFile();
currentLineNumber = debugInfo.getLine(); currentLineNumber = debugInfo.getLine();
} catch (Exception e) { } catch (Exception e) {
logger.fatal("Exception: " + e.getMessage(), e); logger.fatal("Exception: " + e.getMessage(), e);
@ -329,147 +358,332 @@ public class MspCodeWatcher extends VisPlugin implements MotePlugin {
} }
} }
private void tryMapDebugInfo() { private int getLocatedSourcesCount() {
final String[] debugFiles; File files[] = getSourceFiles(mspMote, rules);
try { if (files == null) {
return 0;
ELFDebug debug = ((MspMoteType)mspMote.getType()).getELF().getDebug();
debugFiles = debug != null ? debug.getSourceFiles() : null;
if (debugFiles == null) {
logger.fatal("Error: No debug information is available");
return;
}
} catch (IOException e1) {
logger.fatal("Error: " + e1.getMessage(), e1);
return;
} }
String[] map = new RunnableInEDT<String[]>() { return files.length;
public String[] work() { }
/* Select which source file to use */
int counter = 0, n;
File correspondingFile = null;
while (true) {
n = JOptionPane.showOptionDialog(GUI.getTopParentContainer(),
"Choose which source file to manually locate.\n\n" +
"Some source files may not exist, as debug info is also inherited from the toolchain.\n" +
"\"Next File\" proceeds to the next source file in the debug info.\n\n" +
debugFiles[counter] + " (" + (counter+1) + '/' + debugFiles.length + ')',
"Select source file to locate", JOptionPane.YES_NO_CANCEL_OPTION,
JOptionPane.QUESTION_MESSAGE, null,
new String[] { "Next File", "Locate File", "Cancel"}, "Next File");
if (n == JOptionPane.CANCEL_OPTION || n == JOptionPane.CLOSED_OPTION) {
return null;
}
if (n == JOptionPane.NO_OPTION) {
/* Locate file */
final String filename = new File(debugFiles[counter]).getName();
JFileChooser fc = new JFileChooser();
fc.setFileFilter(new FileFilter() {
public boolean accept(File file) {
if (file.isDirectory()) { return true; }
if (file.getName().equals(filename)) {
return true;
}
return false;
}
public String getDescription() {
return "Source file " + filename;
}
});
fc.setCurrentDirectory(new File(GUI.getExternalToolsSetting("PATH_CONTIKI", ".")));
int returnVal = fc.showOpenDialog(GUI.getTopParentContainer());
if (returnVal == JFileChooser.APPROVE_OPTION) {
correspondingFile = fc.getSelectedFile();
break;
}
}
if (n == JOptionPane.YES_OPTION) { private void updateRulesUsage() {
/* Next file */ for (Rule rule: rules) {
counter = (counter+1) % debugFiles.length; rule.prefixMatches = 0;
} rule.locatesFile = 0;
} }
getSourceFiles(mspMote, rules);
/* Match files */ rulesMatched = new int[rules.size()];
try { rulesOK = new int[rules.size()];
String canonDebug = debugFiles[counter]; for (int i=0; i < rules.size(); i++) {
String canonSelected = correspondingFile.getCanonicalFile().getPath().replace('\\', '/'); rulesMatched[i] = rules.get(i).prefixMatches;
rulesOK[i] = rules.get(i).locatesFile;
int offset = 0;
while (canonDebug.regionMatches(
true,
canonDebug.length()-offset,
canonSelected, canonSelected.length()-offset,
offset)) {
offset++;
if (offset >= canonDebug.length() ||
offset >= canonSelected.length())
break;
}
offset--;
String replace = canonDebug.substring(0, canonDebug.length() - offset);
String replacement = canonSelected.substring(0, canonSelected.length() - offset);
{
JTextField replaceInput = new JTextField(replace);
replaceInput.setEditable(true);
JTextField replacementInput = new JTextField(replacement);
replacementInput.setEditable(true);
Box box = Box.createVerticalBox();
box.add(new JLabel("Debug info file:"));
box.add(new JLabel(canonDebug));
box.add(new JLabel("Selected file:"));
box.add(new JLabel(canonSelected));
box.add(Box.createVerticalStrut(20));
box.add(new JLabel("Replacing:"));
box.add(replaceInput);
box.add(new JLabel("with:"));
box.add(replacementInput);
JOptionPane optionPane = new JOptionPane();
optionPane.setMessage(box);
optionPane.setMessageType(JOptionPane.INFORMATION_MESSAGE);
optionPane.setOptions(new String[] { "OK" });
optionPane.setInitialValue("OK");
JDialog dialog = optionPane.createDialog(
GUI.getTopParentContainer(),
"Mapping debug info to real sources");
dialog.setVisible(true);
replace = replaceInput.getText();
replacement = replacementInput.getText();
}
replace = replace.replace('\\', '/');
replacement = replacement.replace('\\', '/');
return new String[] { replace, replacement };
} catch (IOException e) {
logger.fatal("Error: " + e.getMessage(), e);
return null;
}
}
}.invokeAndWait();
if (map != null) {
debugInfoMap = map;
updateFileComboBox();
} }
} }
private static File[] getSourceFiles(MspMote mote, String[] map) { private class Rule {
final String[] sourceFiles; String from = "";
try { String to = "";
ELFDebug debug = ((MspMoteType)mote.getType()).getELF().getDebug(); int prefixMatches = 0;
sourceFiles = debug != null ? debug.getSourceFiles() : null; int locatesFile = 0;
if (sourceFiles == null) { public Rule(String from, String to) {
logger.fatal("Error: No debug information is available"); this.from = from;
return new File[0]; this.to = to;
}
File apply(File f) {
if (f == null) {
return null;
} }
} catch (IOException e1) { if (from == null) {
logger.fatal("Error: " + e1.getMessage(), e1); return null;
}
if (!f.getPath().startsWith(from)) {
if (rulesWithDebuggingOutput) {
rulesDebuggingOutput.addMessage(this + " does not match: " + f.getPath(), MessageList.ERROR);
}
return null;
}
prefixMatches++;
if (rulesWithDebuggingOutput) {
rulesDebuggingOutput.addMessage(this + " testing on: " + f.getPath());
}
if (to == null) {
if (rulesWithDebuggingOutput) {
rulesDebuggingOutput.addMessage(this + " enter substition: " + f.getPath(), MessageList.ERROR);
}
return null;
}
File file = new File(to + f.getPath().substring(from.length()));
if (!file.exists()) {
if (rulesWithDebuggingOutput) {
rulesDebuggingOutput.addMessage(this + " not found: " + file.getPath(), MessageList.ERROR);
}
return null;
}
if (rulesWithDebuggingOutput) {
rulesDebuggingOutput.addMessage(this + " OK: " + f.getPath());
}
locatesFile++;
return file;
}
public String toString() {
return "[" + from + "|" + to + "]";
}
}
private static File applySubstitutionRules(String file, Collection<Rule> rules) {
return applySubstitutionRules(new File(file), rules);
}
private static File applySubstitutionRules(File file, Collection<Rule> rules) {
if (file == null) {
return null; return null;
} }
if (file.exists()) {
return file;
}
for (Rule rule: rules) {
File f = rule.apply(file);
if (f != null && f.exists()) {
return f;
}
}
return null;
}
private void loadDefaultRules() {
String rulesString = GUI.getExternalToolsSetting("MSPCODEWATCHER_RULES", "/cygdrive/c/*c:/");
String[] rulesArr = rulesString.split("\\*");
rules.clear();
for (int i=0; i < rulesArr.length/2; i++) {
Rule r = new Rule(rulesArr[i*2], rulesArr[i*2+1]);
if (r.from.equals("[empty]")) {
r.from = "";
}
if (r.to.equals("[empty]")) {
r.to = "";
}
rules.add(r);
}
}
private MessageList rulesDebuggingOutput = new MessageList();
private boolean rulesWithDebuggingOutput = false;
private int[] rulesMatched = null;
private int[] rulesOK = null;
private JTable table = null;
private void tryMapDebugInfo() {
/* called from AWT */
int r = JOptionPane.showConfirmDialog(GUI.getTopParentContainer(),
"The firmware file " + mspMote.getType().getContikiFirmwareFile().getName() + " references " + debugSourceFiles.length + " source files.\n" +
"This function tries to locate these files on disk with a set of simple substitution rules.\n" +
"\n" +
"Right now " + getLocatedSourcesCount() + "/" + debugSourceFiles.length + " source files can be found.",
"Locate source files", JOptionPane.OK_CANCEL_OPTION);
if (r != JOptionPane.OK_OPTION) {
return;
}
/* table with rules */
rulesDebuggingOutput.clearMessages();
final JDialog dialog = new JDialog((Window)GUI.getTopParentContainer(), "Locate source files");
dialog.setModal(true);
updateRulesUsage();
AbstractTableModel model = new AbstractTableModel() {
public int getRowCount() {
return 10;
}
public int getColumnCount() {
return 4;
}
public String getColumnName(int col) {
if (col == 0) {
return "Prefix";
}
if (col == 1) {
return "Substitution";
}
if (col == 2) {
return "Matched";
}
if (col == 3) {
return "OK";
}
return null;
}
public Object getValueAt(int rowIndex, int columnIndex) {
if (rowIndex < 0 || rowIndex >= rules.size()) {
return null;
}
Rule rule = rules.get(rowIndex);
if (columnIndex == 0) {
return rule.from;
}
if (columnIndex == 1) {
return rule.to;
}
if (columnIndex == 2) {
if (rulesMatched == null || rowIndex >= rulesMatched.length) {
return "[click Apply]";
}
return rulesMatched[rowIndex];
}
if (columnIndex == 3) {
if (rulesOK == null || rowIndex >= rulesOK.length) {
return "[click Apply]";
}
return rulesOK[rowIndex];
}
return null;
}
public boolean isCellEditable(int row, int column) {
if (column == 0) {
return true;
}
if (column == 1) {
return true;
}
return false;
}
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
Rule rule;
if (rowIndex < 0) {
return;
}
if (rowIndex < rules.size()) {
rule = rules.get(rowIndex);
} else {
do {
rule = new Rule("", "");
rules.add(rule);
} while (rowIndex >= rules.size());
}
if (columnIndex == 0) {
rule.from = (String) aValue;
}
if (columnIndex == 1) {
rule.to = (String) aValue;
}
rulesMatched = null;
rulesOK = null;
table.invalidate();
table.repaint();
}
};
table = new JTable(model);
/* control panel: save/load, clear/apply/close */
final JButton applyButton = new JButton("Apply");
applyButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
/* Remove trailing empty rules */
ArrayList<Rule> trimmedRules = new ArrayList<Rule>();
for (Rule rule: rules) {
if (rule.from == null || rule.from.trim().isEmpty()) {
rule.from = "";
}
if (rule.to == null || rule.to.trim().isEmpty()) {
rule.to = "";
}
if (rule.from.isEmpty() && rule.to.isEmpty()) {
/* ignore */
continue;
}
trimmedRules.add(rule);
}
rules = trimmedRules;
rulesDebuggingOutput.clearMessages();
rulesDebuggingOutput.addMessage("Applying " + rules.size() + " substitution rules");
rulesWithDebuggingOutput = true;
updateRulesUsage();
rulesWithDebuggingOutput = false;
rulesDebuggingOutput.addMessage("Done! " + "Located sources: " + getLocatedSourcesCount() + "/" + debugSourceFiles.length);
rulesDebuggingOutput.addMessage(" ");
for (String s: debugSourceFiles) {
File f = applySubstitutionRules(s, rules);
if (f == null || !f.exists()) {
rulesDebuggingOutput.addMessage("Not yet located: " + s, MessageList.ERROR);
}
}
table.invalidate();
table.repaint();
}
});
JButton clearButton = new JButton("Clear");
clearButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
rules.clear();
applyButton.doClick();
}
});
JButton loadButton = new JButton("Load default");
loadButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
loadDefaultRules();
applyButton.doClick();
}
});
JButton saveButton = new JButton("Save as default");
saveButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
StringBuilder sb = new StringBuilder();
for (Rule r: rules) {
sb.append("*");
if (r.from.isEmpty()) {
sb.append("[empty]");
} else {
sb.append(r.from);
}
sb.append("*");
if (r.to.isEmpty()) {
sb.append("[empty]");
} else {
sb.append(r.to);
}
}
if (sb.length() >= 1) {
GUI.setExternalToolsSetting("MSPCODEWATCHER_RULES", sb.substring(1));
} else {
GUI.setExternalToolsSetting("MSPCODEWATCHER_RULES", "");
}
}
});
JButton closeButton = new JButton("Close");
closeButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
updateFileComboBox();
dialog.dispose();
}
});
Box control = Box.createHorizontalBox();
control.add(loadButton);
control.add(saveButton);
control.add(Box.createHorizontalGlue());
control.add(clearButton);
control.add(applyButton);
control.add(closeButton);
final JSplitPane split = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
new JScrollPane(table),
new JScrollPane(rulesDebuggingOutput));
SwingUtilities.invokeLater(new Runnable() {
public void run() {
split.setDividerLocation(0.5);
applyButton.doClick();
}
});
dialog.getContentPane().add(BorderLayout.CENTER, split);
dialog.getContentPane().add(BorderLayout.SOUTH, control);
dialog.getRootPane().setDefaultButton(closeButton);
dialog.setSize(550, 500);
dialog.setLocationRelativeTo(GUI.getTopParentContainer());
dialog.setVisible(true);
}
private File[] getSourceFiles(MspMote mote, ArrayList<Rule> rules) {
File contikiSource = mote.getType().getContikiSourceFile(); File contikiSource = mote.getType().getContikiSourceFile();
if (contikiSource != null) { if (contikiSource != null) {
try { try {
@ -480,75 +694,22 @@ public class MspCodeWatcher extends VisPlugin implements MotePlugin {
/* Verify that files exist */ /* Verify that files exist */
ArrayList<File> existing = new ArrayList<File>(); ArrayList<File> existing = new ArrayList<File>();
for (String sourceFile: sourceFiles) { for (String sourceFile: debugSourceFiles) {
/* Debug info to source file map */ /* Debug info to source file map */
sourceFile = sourceFile.replace('\\', '/'); File f = applySubstitutionRules(sourceFile, rules);
if (map != null && map.length == 2) { if (f != null && f.exists()) {
if (sourceFile.startsWith(map[0])) { existing.add(f);
sourceFile = map[1] + sourceFile.substring(map[0].length());
}
} }
/* Nasty Cygwin-Windows fix */
if (sourceFile.length() > 10 && sourceFile.contains("/cygdrive/")) {
char driveCharacter = sourceFile.charAt(10);
sourceFile = driveCharacter + ":/" + sourceFile.substring(11);
}
File file = new File(sourceFile);
try {
file = file.getCanonicalFile();
} catch (IOException e1) {
}
if (!GUI.isVisualizedInApplet()) {
if (file.exists() && file.isFile()) {
existing.add(file);
} else {
/*logger.warn("Can't locate source file, skipping: " + file.getPath());*/
}
} else {
/* Accept all files without existence check */
existing.add(file);
}
}
/* If no files were found, suggest map function */
if (sourceFiles.length > 0 && existing.isEmpty() && GUI.isVisualized()) {
new RunnableInEDT<Boolean>() {
public Boolean work() {
JOptionPane.showMessageDialog(
GUI.getTopParentContainer(),
"The firmware debug info specifies " + sourceFiles.length + " source files.\n" +
"However, Msp Code Watcher could not find any of these files.\n" +
"Make sure the source files were not moved after the firmware compilation.\n" +
"\n" +
"If you want to manually locate the sources, click \"Map\" button.",
"No source files found",
JOptionPane.WARNING_MESSAGE);
return true;
}
}.invokeAndWait();
} }
/* Sort alphabetically */ /* Sort alphabetically */
ArrayList<File> sorted = new ArrayList<File>(); File[] sorted = existing.toArray(new File[0]);
for (File file: existing) { Arrays.sort(sorted, new Comparator<File>() {
int index = 0; public int compare(File o1, File o2) {
for (index=0; index < sorted.size(); index++) { return o1.getName().compareToIgnoreCase(o2.getName());
if (file.getName().compareToIgnoreCase(sorted.get(index).getName()) < 0) {
break;
}
} }
sorted.add(index, file); });
} return sorted;
/* Add Contiki source first */
if (contikiSource != null && contikiSource.exists()) {
sorted.add(0, contikiSource);
}
return sorted.toArray(new File[0]);
} }
public Collection<Element> getConfigXML() { public Collection<Element> getConfigXML() {
@ -559,13 +720,27 @@ public class MspCodeWatcher extends VisPlugin implements MotePlugin {
element.addContent("" + mainPane.getSelectedIndex()); element.addContent("" + mainPane.getSelectedIndex());
config.add(element); config.add(element);
for (Rule rule: rules) {
element = new Element("rule");
element.setAttribute("from", rule.from==null?"":rule.from);
element.setAttribute("to", rule.to==null?"":rule.to);
config.add(element);
}
return config; return config;
} }
public boolean setConfigXML(Collection<Element> configXML, boolean visAvailable) { public boolean setConfigXML(Collection<Element> configXML, boolean visAvailable) {
boolean clearedRules = false;
for (Element element : configXML) { for (Element element : configXML) {
if (element.getName().equals("tab")) { if (element.getName().equals("tab")) {
mainPane.setSelectedIndex(Integer.parseInt(element.getText())); mainPane.setSelectedIndex(Integer.parseInt(element.getText()));
} else if (element.getName().equals("rule")) {
if (!clearedRules) {
rules.clear();
clearedRules = true;
}
rules.add(new Rule(element.getAttributeValue("from"), element.getAttributeValue("to")));
} }
} }
return true; return true;
@ -582,7 +757,7 @@ public class MspCodeWatcher extends VisPlugin implements MotePlugin {
} }
}; };
private AbstractAction mapAction = new AbstractAction("Locate sources") { private AbstractAction mapAction = new AbstractAction("Locate sources...") {
private static final long serialVersionUID = -3929432830596292495L; private static final long serialVersionUID = -3929432830596292495L;
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {

View file

@ -2943,10 +2943,24 @@ public class GUI extends Observable {
* null * null
*/ */
public static void main(String[] args) { public static void main(String[] args) {
String logConfigFile = null;
for (String element : args) {
if (element.startsWith("-log4j=")) {
String arg = element.substring("-log4j=".length());
logConfigFile = arg;
}
}
try { try {
// Configure logger // Configure logger
if ((new File(LOG_CONFIG_FILE)).exists()) { if (logConfigFile != null) {
if (new File(logConfigFile).exists()) {
DOMConfigurator.configure(logConfigFile);
} else {
System.err.println("Failed to open " + logConfigFile);
System.exit(1);
}
} else if (new File(LOG_CONFIG_FILE).exists()) {
DOMConfigurator.configure(LOG_CONFIG_FILE); DOMConfigurator.configure(LOG_CONFIG_FILE);
} else { } else {
// Used when starting from jar // Used when starting from jar

View file

@ -696,7 +696,6 @@ public class ScriptRunner extends VisPlugin {
} }
}); });
if (fileChooser.showOpenDialog(scriptRunner) != JFileChooser.APPROVE_OPTION) { if (fileChooser.showOpenDialog(scriptRunner) != JFileChooser.APPROVE_OPTION) {
logger.debug("cancel");
return; return;
} }
scriptRunner.setLinkFile(fileChooser.getSelectedFile()); scriptRunner.setLinkFile(fileChooser.getSelectedFile());

280
tools/stm32w/motelist-linux Normal file
View file

@ -0,0 +1,280 @@
#!/usr/bin/perl -w
use strict;
# $Id: motelist-linux,v 1.1 2007/05/29 19:34:30 adam Exp $
# @author Cory Sharp <cory@moteiv.com>
# @author Joe Polastre
my $help = <<'EOF';
usage: motelist [options]
$Revision: 1.1 $
options:
-h display this help
-c compact format, not pretty but easier for parsing
-f specify the usb-serial file (for smote.cs)
-k kernel version: 2.4, 2.6, auto (default)
-m method to scan usb: procfs, sysfs, auto (default)
-dev_prefix force the device prefix for the serial device
-usb display extra usb information
EOF
my %Opt = (
compact => 0,
usb => 0,
method => "auto",
kernel => "auto",
dev_prefix => [ "/dev/usb/tts/", "/dev/ttyUSB", "/dev/tts/USB" ],
usbserial => "sudo cat /proc/tty/driver/usbserial |",
);
while (@ARGV) {
last unless $ARGV[0] =~ /^-/;
my $opt = shift @ARGV;
if( $opt eq "-h" ) { print "$help\n"; exit 0; }
elsif( $opt eq "-c" ) { $Opt{compact} = 1; }
elsif( $opt eq "-f" ) { $Opt{usbserial} = shift @ARGV; }
elsif( $opt eq "-k" ) { $Opt{kernel} = shift @ARGV; }
elsif( $opt eq "-m" ) { $Opt{method} = shift @ARGV; }
elsif( $opt eq "-dev_prefix" ) { $Opt{dev_prefix} = shift @ARGV; }
elsif( $opt eq "-usb" ) { $Opt{usb} = 1; }
else { print STDERR "$help\nerror, unknown command line option $opt\n"; exit 1; }
}
if( $Opt{kernel} eq "auto" ) {
$Opt{kernel} = "unknown";
$Opt{kernel} = $1 if snarf("/proc/version") =~ /\bLinux version (\d+\.\d+)/;
}
if( $Opt{method} eq "auto" ) {
$Opt{method} = ($Opt{kernel} eq "2.4") ? "procfs" : "sysfs";
}
my @devs = $Opt{method} eq "procfs" ? scan_procfs() : scan_sysfs();
print_motelist( sort { cmp_usbdev($a,$b) } @devs );
#
# SysFS
#
sub scan_sysfs {
# Scan /sys/bus/usb/drivers/usb for FTDI devices
my @ftdidevs =
grep { ($_->{UsbVendor}||"") eq "0403" && ($_->{UsbProduct}||"") eq "6001" && ($_->{UsbBcdDevice}||"") eq "0600"}
map { {
SysPath => $_,
UsbVendor => snarf("$_/idVendor",1),
UsbProduct => snarf("$_/idProduct",1),
UsbBcdDevice => snarf("$_/bcdDevice",1),
} }
glob("/sys/bus/usb/drivers/usb/*");
# Gather information about each FTDI device
for my $f (@ftdidevs) {
my $syspath = $f->{SysPath};
$f->{InfoManufacturer} = snarf("$syspath/manufacturer",1);
$f->{InfoProduct} = snarf("$syspath/product",1);
$f->{InfoSerial} = snarf("$syspath/serial",1);
$f->{UsbDevNum} = snarf("$syspath/devnum",1);
print $syspath;
print "\n";
my $devstr = readlink($syspath);
if( $devstr =~ m{([^/]+)/usb(\d+)/.*-([^/]+)$} ) {
$f->{UsbPath} = "usb-$1-$3";
$f->{UsbBusNum} = $2;
}
($f->{SysDev} = $syspath) =~ s{^.*/}{};
my $port = "$syspath/$f->{SysDev}:1.0";
($f->{DriverName} = readlink("$port/driver")) =~ s{^.*/}{} if -l "$port/driver";
($f->{SerialDevName} = (glob("$port/tty*"),undef)[0]) =~ s{^.*/}{};
$f->{SerialDevNum} = $1 if $f->{SerialDevName} =~ /(\d+)/;
$f->{SerialDevName} = getSerialDevName( $f->{SerialDevNum} ) || " (none)";
}
return @ftdidevs;
}
#
# Scan Procfs
#
sub scan_procfs {
my $text_devs = snarf("< /proc/bus/usb/devices");
my $text_serial = snarf($Opt{usbserial});
my @usbdevs = map { {parse_usb_devices_text($_)} }
grep { !/^\s*$/ } split /\n+(?=T:)/, $text_devs;
my %usbtree = build_usb_tree( @usbdevs );
my %usbserialtree = build_usbserial_tree( $text_serial );
for my $tts ( values %usbserialtree ) {
$usbtree{usbkey($tts->{path})}{usbserial} = $tts if defined $tts->{path};
}
my @ftdidevs = map { {
UsbVendor => $_->{Vendor},
UsbProduct => $_->{ProdID},
UsbBcdDevice => $_->{BcdDevice},
InfoManufacturer => $_->{Manufacturer},
InfoProduct => $_->{Product},
InfoSerial => $_->{SerialNumber},
UsbBusNum => $_->{nbus},
UsbDevNum => $_->{ndev},
UsbPath => (($Opt{kernel} eq "2.4") ? $_->{usbserial}{path} : $_->{usbpath}),
DriverName => $_->{driver},
SerialDevNum => $_->{usbserial}{tts},
SerialDevName => getSerialDevName($_->{usbserial}{tts}) || " (none)",
} }
grep { ($_->{Vendor}||"") eq "0403" && ($_->{ProdID}||"") eq "6001" && ($_->{BcdDevice}||"") eq "0600"}
values %usbtree;
return @ftdidevs;
}
sub build_usb_tree {
my @devs = @_;
my %tree = ();
for my $dev (sort { $a->{Lev} <=> $b->{Lev} } @devs) {
my ($bus,$lev,$prnt) = ( $dev->{Bus}+0, $dev->{Lev}+0, $dev->{Prnt}+0 );
my $devnum = $dev->{"Dev#"}+0;
$dev->{nbus} = $bus;
$dev->{ndev} = $devnum;
$tree{"bus$bus"} = {} unless exists $tree{"bus$bus"};
$tree{"bus$bus"}{"dev$devnum"} = $dev;
if( $lev == 0 ) {
$dev->{usbpath} = "usb-$dev->{SerialNumber}";
} else {
my $sep = ($lev==1) ? "-" : ".";
$dev->{parent} = $tree{"bus$bus"}{"dev$prnt"};
$dev->{usbpath} = $dev->{parent}{usbpath} . $sep . ($dev->{Port}+1);
}
$tree{usbkey($dev->{usbpath})} = $dev;
}
return %tree;
}
sub parse_usb_devices_text {
my $text = shift;
$text =~ s/^\S+\s*//gm;
$text =~ s/\s*(\S+=)/\001$1/g;
return map { m/(.*?)=(.*)/ } split /\001/, $text;
}
sub build_usbserial_tree {
my $text = shift;
my %tree = ();
while( $text =~ /^([^:]+):(.*)/mg ) {
my ($tts,$params) = ($1,$2);
$tree{$tts} = { tts => $tts };
while ($params =~ m/\s+([^:]+):(?:"([^"]*)"|(\S+))/g) {
$tree{$tts}{$1} = $2||$3;
}
}
return %tree;
}
sub usbkey {
if( $Opt{kernel} eq "2.4" ) {
(my $key = $_[0]) =~ s/^.*-//;
return $key;
}
return $_[0];
}
#
# getSerialDevName
#
# For each device, force to use dev_prefix if it's not an array. Otherwise,
# assume it's a list of candidate prefixes. Check them and commit to the
# first one that actually exists.
#
sub getSerialDevName {
my $devnum = shift;
my $devname = undef;
if( defined $devnum ) {
if( ref($Opt{dev_prefix}) eq "ARRAY" ) {
$devname = $devnum;
for my $prefix (@{$Opt{dev_prefix}}) {
my $file = $prefix . $devnum;
if( -e $file ) { $devname = $file; last; }
}
} else {
$devname = $Opt{dev_prefix} . $devnum;
}
}
return $devname;
}
#
# Print motelist
#
sub print_motelist {
my @devs = @_;
# If none were found, quit
if( @devs == 0 ) {
print "No devices found.\n";
return;
}
# Print a header
if( !$Opt{compact} ) {
if( $Opt{usb} ) {
print << "EOF" unless $Opt{compact};
Bus Dev USB Path Reference Device Description
--- --- ------------------------ ---------- ---------------- -------------------------------------
EOF
} else {
print << "EOF" unless $Opt{compact};
Reference Device Description
---------- ---------------- ---------------------------------------------
EOF
}
}
# Print the usb information
for my $dev (sort { cmp_usbdev($a,$b) } @devs) {
my $desc = join( " ", $dev->{InfoManufacturer}||"", $dev->{InfoProduct}||"" ) || " (none)";
my @output = ( $dev->{InfoSerial}||" (none)", $dev->{SerialDevName}, $desc );
@output = ( $dev->{UsbBusNum}, $dev->{UsbDevNum}, $dev->{UsbPath}, @output ) if $Opt{usb};
if( $Opt{compact} ) {
print join(",",@output) . "\n";
} else {
printf( ($Opt{usb}?"%3d %3d %-24s ":"")."%-10s %-16s %s\n", @output );
}
}
}
#
# Cmp Usbdev's
#
sub cmp_usbdev {
my ($a,$b) = @_;
if( defined $a->{SerialDevNum} ) {
if( defined $b->{SerialDevNum} ) {
return $a->{SerialDevNum} <=> $b->{SerialDevNum};
}
return -1;
}
return 1 if defined $b->{SerialDevNum};
return ($a->{InfoSerial}||"") cmp ($b->{InfoSerial}||"");
}
#
# Read a file in
#
sub snarf {
open my $fh, $_[0] or return undef;
my $text = do{local $/;<$fh>};
close $fh;
$text =~ s/\s+$// if $_[1];
return $text;
}