/Designs/Tools/i2c_AVR_USB/SW/firmware/usbtiny/common.mk |
---|
7,21 → 7,24 |
# TARGET_ARCH - gcc -mmcu= option with AVR device type |
# OBJECTS - the objects in addition to the USBtiny objects |
# FLASH_CMD - command to upload main.hex to flash |
# FUSES_CMD - command to program the fuse bytes |
# STACK - maximum stack size (optional) |
# FLASH - flash size (optional) |
# SRAM - SRAM size (optional) |
# SCHEM - Postscript version of the schematic to be generated |
# |
# Copyright (C) 2006 Dick Streefland |
# Copyright 2006-2010 Dick Streefland |
# |
# This is free software, licensed under the terms of the GNU General |
# Public License as published by the Free Software Foundation. |
# ====================================================================== |
check = $(shell $(CC) $1 -c -xc /dev/null -o/dev/null 2>/dev/null && echo $1) |
CC = avr-gcc |
CFLAGS = -Os -g -Wall -I. -I$(USBTINY) |
ASFLAGS = -Os -g -Wall -I. |
LDFLAGS = -g |
OPTIM = -Os -ffunction-sections $(call check,-fno-split-wide-types) |
CFLAGS = -g -Wall -Wattributes -I. -I$(USBTINY) $(OPTIM) |
LDFLAGS = -g -Wl,--relax,--gc-sections |
MODULES = crc.o int.o usb.o $(OBJECTS) |
UTIL = $(USBTINY)/../util |
42,6 → 45,9 |
@python $(UTIL)/check.py main.elf $(STACK) $(FLASH) $(SRAM) |
avr-objcopy -j .text -j .data -O ihex main.elf main.hex |
check: main.elf $(UTIL)/check.py |
@python $(UTIL)/check.py main.elf $(STACK) $(FLASH) $(SRAM) |
disasm: main.elf |
avr-objdump -S main.elf |
48,6 → 54,9 |
flash: main.hex |
$(FLASH_CMD) |
fuses: |
$(FUSES_CMD) |
crc.o: $(USBTINY)/crc.S $(USBTINY)/def.h usbtiny.h |
$(COMPILE.c) $(USBTINY)/crc.S |
int.o: $(USBTINY)/int.S $(USBTINY)/def.h usbtiny.h |
/Designs/Tools/i2c_AVR_USB/SW/firmware/usbtiny/crc.S |
---|
1,19 → 1,19 |
; ====================================================================== |
; Calculate and append CRC |
; |
; The CRC is calculated 4 bits at a time, using a precomputed table of |
; 16 values. Each value is 16 bits, but only the 8 significant bits are |
; stored. The table should not cross a 256-byte page. The check.py script |
; will check for this. |
; There are two versions of the CRC16 calculation, selectable by the |
; USBTINY_FAST_CRC macro. The default implementation calculates one bit |
; at a time, and is compact but relatively slow. The "fast" version |
; processes 4 bits at a time, and is about twice as fast, but 42 bytes |
; larger. |
; |
; A bitwise algorithm would be a little smaller, but takes more time. |
; In fact, it takes too much time for the USB controller in my laptop. |
; The poll frequently is so high, that a lot of time is spent in the |
; interrupt handler, sending NAK packets, leaving little time for the |
; actual checksum calculation. An 8 bit algoritm would be even faster, |
; The fast version calculates 4 bits at a time, using a precomputed table |
; of 16 values. Each value is 16 bits, but only the 8 significant bits |
; are stored. The table should not cross a 256-byte page. The check.py |
; script will check for this. An 8 bit algoritm would be even faster, |
; but requires a lookup table of 512 bytes. |
; |
; Copyright (C) 2006 Dick Streefland |
; Copyright 2006-2010 Dick Streefland |
; |
; This is free software, licensed under the terms of the GNU General |
; Public License as published by the Free Software Foundation. |
21,6 → 21,7 |
#include "def.h" |
#if USBTINY_FAST_CRC |
; ---------------------------------------------------------------------- |
; void crc(unsigned char *data, unsigned char len); |
; ---------------------------------------------------------------------- |
41,30 → 42,42 |
movw XL, r24 |
ldi crc_h, 0xff |
ldi crc_l, 0xff |
lsl len |
breq done |
ldi zl, lo8(crc4tab) |
ldi ZH, hi8(crc4tab) |
rjmp entry |
next_nibble: |
; b = (len & 1 ? b >> 4 : *data++) |
swap b |
sbrs len, 0 |
next_byte: |
; crc ^= b |
ld b, X+ |
eor crc_l, b |
; index = (crc ^ b) & 0x0f |
; index1 = crc & 0x0f |
mov ZL, crc_l |
eor ZL, b |
andi ZL, 0x0f |
; crc >>= 4 |
; tmp = crc4tab[index1] |
add ZL, zl |
lpm tmp, Z+ |
; index2 = (crc >> 4) |
mov ZL, crc_l |
swap ZL |
; crc >>= 8 |
mov crc_l, crc_h |
; index2 = (index2 ^ tmp) & 0xf |
mov crc_h, tmp |
andi tmp, 1 |
eor ZL, tmp |
andi ZL, 0x0f |
; treat upper byte of CRC remainder |
swap crc_h |
swap crc_l |
andi crc_l, 0x0f |
mov tmp, crc_h |
andi tmp, 0xf0 |
or crc_l, tmp |
andi crc_h, 0x0f |
andi tmp, 0xe0 |
eor crc_l, tmp |
; crc ^= crc4tab[index] |
add ZL, zl |
74,9 → 87,10 |
eor crc_h, tmp |
eor crc_l, tmp |
; next nibble |
entry: |
; next byte |
dec len |
brne next_nibble |
brpl next_byte |
done: |
; crc ^= 0xffff |
93,7 → 107,7 |
; CRC table. As bits 1..8 are always zero, omit them. |
; ---------------------------------------------------------------------- |
.section .progmem.crc,"a",@progbits |
;;; .align 4 ; avoid crossing a page boundary |
;;; .align 4 ; crude way to avoid crossing a page boundary |
crc4tab: |
.byte 0x00+0x00 |
.byte 0xcc+0x01 |
121,3 → 135,49 |
crc ^= 0xA001 # X^16 + X^15 + X^2 + 1 (reversed) |
print "\t.byte\t0x%02x+0x%02x" % (crc >> 8, crc & 0xff) |
\* ---------------------------------------------------------------------- */ |
#else |
; ---------------------------------------------------------------------- |
; void crc(unsigned char *data, unsigned char len); |
; ---------------------------------------------------------------------- |
#define data r24 |
#define len r22 |
#define b r18 |
#define con_01 r19 |
#define con_a0 r20 |
#define crc_l r24 |
#define crc_h r25 |
.text |
.global crc |
.type crc, @function |
crc: |
movw XL, r24 |
ldi crc_h, 0xff |
ldi crc_l, 0xff |
tst len |
breq done1 |
ldi con_a0, 0xa0 |
ldi con_01, 0x01 |
next_byte: |
ld b, X+ |
eor crc_l, b |
ldi b, 8 |
next_bit: |
lsr crc_h |
ror crc_l |
brcc noxor |
eor crc_h, con_a0 |
eor crc_l, con_01 |
noxor: |
dec b |
brne next_bit |
dec len |
brne next_byte |
done1: |
com crc_l |
com crc_h |
st X+, crc_l |
st X+, crc_h |
ret |
#endif |
/Designs/Tools/i2c_AVR_USB/SW/firmware/usbtiny/def.h |
---|
1,7 → 1,7 |
// ====================================================================== |
// Common definitions for the USB driver |
// |
// Copyright (C) 2006 Dick Streefland |
// Copyright 2006-2010 Dick Streefland |
// |
// This is free software, licensed under the terms of the GNU General |
// Public License as published by the Free Software Foundation. |
19,12 → 19,12 |
#define CAT3(a,b,c) CAT3EXP(a, b, c) |
#define CAT3EXP(a,b,c) a ## b ## c |
// I/O Ports |
// I/O Ports for USB |
#define USB_IN CAT2(PIN, USBTINY_PORT) |
#define USB_OUT CAT2(PORT, USBTINY_PORT) |
#define USB_DDR CAT2(DDR, USBTINY_PORT) |
// I/O bit masks |
// I/O bit masks for USB |
#define USB_MASK_DMINUS (1 << (USBTINY_DMINUS)) |
#define USB_MASK_DPLUS (1 << (USBTINY_DPLUS)) |
#define USB_MASK (USB_MASK_DMINUS | USB_MASK_DPLUS) |
36,11 → 36,7 |
# define USB_INT_CONFIG MCUCR |
#endif |
#define USB_INT_CONFIG_SET ((1 << CAT3(ISC,USBTINY_INT,1)) | (1 << CAT3(ISC,USBTINY_INT,0))) |
#if defined SIG_INT0 |
# define USB_INT_VECTOR CAT2(SIG_INT, USBTINY_INT) |
#else |
# define USB_INT_VECTOR CAT2(SIG_INTERRUPT, USBTINY_INT) |
#endif |
#define USB_INT_VECTOR CAT3(INT, USBTINY_INT, _vect) |
// Interrupt enable |
#if defined GIMSK |
59,6 → 55,9 |
# define USB_INT_PENDING GIFR |
#endif |
#define USB_INT_PENDING_BIT CAT2(INTF,USBTINY_INT) |
#if defined INF0 && ! defined INTF0 |
# define INTF0 INF0 // fix for incorrect definition in iotn13.h |
#endif |
// USB PID values |
#define USB_PID_SETUP 0x2d |
72,3 → 71,26 |
// Various constants |
#define USB_BUFSIZE 11 // PID + data + CRC |
// Bit manipulation macros |
#define BIT_CLR(reg,bit) { (reg) &= ~ _BV(bit); } |
#define BIT_SET(reg,bit) { (reg) |= _BV(bit); } |
#define BIT_TST(reg,bit) (((reg) & _BV(bit)) != 0) |
// I/O port manipulation macros |
#define DDR_CLR(p,b) BIT_CLR(DDR ## p, b) |
#define DDR_SET(p,b) BIT_SET(DDR ## p, b) |
#define PORT_CLR(p,b) BIT_CLR(PORT ## p, b) |
#define PORT_SET(p,b) BIT_SET(PORT ## p, b) |
#define PORT_TST(p,b) BIT_TST(PORT ## p, b) |
#define PIN_TST(p,b) BIT_TST(PIN ## p, b) |
#define PIN_SET(p,b) BIT_SET(PIN ## p, b) |
// Macros that can be used with an argument of the form (port,bit) |
#define INPUT(bit) DDR_CLR bit |
#define OUTPUT(bit) DDR_SET bit |
#define CLR(bit) PORT_CLR bit |
#define SET(bit) PORT_SET bit |
#define ISSET(bit) PORT_TST bit |
#define TST(bit) PIN_TST bit |
#define TOGGLE(bit) PIN_SET bit |
/Designs/Tools/i2c_AVR_USB/SW/firmware/usbtiny/int.S |
---|
9,20 → 9,26 |
; When a DATA0/DATA1 packet directly follows a SETUP or OUT packet, while |
; this interrupt handler is not yet finished, there would be no time to |
; return and take another interrupt. In that case, the second packet is |
; decoded directly in the same invocation. |
; decoded directly in the same invocation. A packet immediately following |
; an ignored packet is also decoded directly. |
; |
; This code is *extremely* time critical. For instance, there is not a |
; single spare cycle in the receiver loop, and only two in the transmitter |
; loop. In addition, the various code paths are laid out in such a way that |
; the various USB timeouts are not violated, in particular the maximum time |
; between the reception of a packet and the reply, which is 6.5 bit times |
; for a detachable cable (TRSPIPD1), and 7.5 bit times for a captive cable |
; (TRSPIPD2). The worst-case delay here is 51 cycles, which is just below |
; the 52 cycles for a detachable cable. |
; between the reception of a packet and the reply, which is 7.5 bit times |
; (TRSPIPD2) for a low-speed USB captive cable. The worst-case delay here |
; is 51 cycles, which is well below the 60 cycles limit, and even below the |
; 6.5 bit times limit for a detachable cable (TRSPIPD1). |
; |
; The interrupt handler must be reached within 34 cycles after D+ goes high |
; for the first time, so the interrupts should not be disabled for longer |
; than 34-4-2=28 cycles. |
; for the first time. The interrupt response time is 4 cycles, and the RJMP |
; in the vector table takes 2 cycles. Therefore, the interrupts should not |
; be disabled for longer than: 34 - 4 - 2 = 28 cycles. When the I-bit is |
; reenabled, a single instruction is always executed before a pending |
; interrupt is served, so this instruction should be included in the |
; calculation. For RETI, the next instruction can be anything, so we |
; should assume the worst-case of 4 cycles. |
; |
; The end-of-packet (EOP) is sampled in the second bit, because the USB |
; standard allows the EOP to be delayed by up to one bit. As the EOP |
30,7 → 36,7 |
; |
; Stack usage including the return address: 11 bytes. |
; |
; Copyright (C) 2006 Dick Streefland |
; Copyright 2006-2010 Dick Streefland |
; |
; This is free software, licensed under the terms of the GNU General |
; Public License as published by the Free Software Foundation. |
45,6 → 51,7 |
tx_ack: .byte USB_PID_ACK ; ACK packet |
tx_nak: .byte USB_PID_NAK ; NAK packet |
.lcomm token_pid, 1 ; PID of most recent token packet |
.global __do_copy_data |
; ---------------------------------------------------------------------- |
; register definitions |
291,46 → 298,51 |
; clear pending interrupt (SE0+3) |
ldi byte, 1<<USB_INT_PENDING_BIT |
out USB_INT_PENDING, byte ; clear pending bit at end of packet |
; ignore packets shorter than 3 bytes |
; calculate packet length |
subi count, USB_BUFSIZE |
neg count ; count = packet length |
cpi count, 3 |
brlo ignore |
; get PID |
sub YL, count |
sbci YH, 0 |
ld pid, Y |
; check for DATA0/DATA1 first, as this is the critical path (SE0+12) |
cpi pid, USB_PID_DATA0 |
breq is_data ; handle DATA0 packet |
cpi pid, USB_PID_DATA1 |
breq is_data ; handle DATA1 packet |
; check ADDR (SE0+16) |
; separate out the non-Token packets (SE0+11) |
sbrc pid, 1 |
rjmp is_data_handshake ; jump for Data or Handshake packet |
; check ADDR of Token packet (SE0+13) |
ldd addr, Y+1 |
andi addr, 0x7f |
lds tmp, usb_address |
cp addr, tmp ; is this packet for me? |
brne ignore ; no, ignore |
; check for other PIDs (SE0+23) |
; dispatch Token packets (SE0+20) |
cpi pid, USB_PID_IN |
breq is_in ; handle IN packet |
cpi pid, USB_PID_SETUP |
breq is_setup_out ; handle SETUP packet |
cpi pid, USB_PID_OUT |
breq is_setup_out ; handle OUT packet |
brne is_setup_out ; handle SETUP and OUT packets |
; ---------------------------------------------------------------------- |
; exit point for ignored packets |
; Handle IN (SE0+22) |
; ---------------------------------------------------------------------- |
lds count, usb_tx_len |
tst count ; data ready? |
breq nak ; no, reply with NAK |
lds tmp, usb_rx_len |
tst tmp ; unprocessed input packet? |
brne nak ; yes, don't send old data for new packet |
sts usb_tx_len, tmp ; buffer is available again (after reti) |
lds tmp, usb_new_address |
sts usb_address, tmp ; assign new address at end of transfer |
ldi YL, lo8(usb_tx_buf) |
ldi YH, hi8(usb_tx_buf) |
rjmp send_packet ; SE0+40, SE0 --> SOP <= 51 |
; ---------------------------------------------------------------------- |
; exit point for ignored packets (SE0+21) |
; ---------------------------------------------------------------------- |
ignore: |
clr tmp |
sts token_pid, tmp |
pop even |
pop fixup |
pop byte |
rjmp return |
clr pid |
ignore0: |
; ---------------------------------------------------------------------- |
; Handle SETUP/OUT (SE0+30) |
; Handle SETUP/OUT (SE0+23) |
; ---------------------------------------------------------------------- |
is_setup_out: |
sts token_pid, pid ; save PID of token packet |
339,10 → 351,10 |
pop byte |
in count, USB_INT_PENDING ; next packet already started? |
sbrc count, USB_INT_PENDING_BIT |
rjmp sync ; yes, get it right away (SE0+42) |
rjmp sync ; yes, get it right away (SE0+35) |
; ---------------------------------------------------------------------- |
; restore registers and return from interrupt |
; restore registers and return from interrupt (SE0+34) |
; ---------------------------------------------------------------------- |
return: |
pop count |
355,27 → 367,26 |
reti |
; ---------------------------------------------------------------------- |
; Handle IN (SE0+26) |
; send NAK packet (SE0+31) |
; ---------------------------------------------------------------------- |
is_in: |
lds count, usb_tx_len |
tst count ; data ready? |
breq nak ; no, reply with NAK |
lds tmp, usb_rx_len |
tst tmp ; unprocessed input packet? |
brne nak ; yes, don't send old data for new packet |
sts usb_tx_len, tmp ; buffer is available again (after reti) |
ldi YL, lo8(usb_tx_buf) |
ldi YH, hi8(usb_tx_buf) |
rjmp send_packet ; SE0+40, SE0 --> SOP <= 51 |
nak: |
ldi YL, lo8(tx_nak) |
ldi YH, hi8(tx_nak) |
rjmp send_token |
; ---------------------------------------------------------------------- |
; Handle DATA0/DATA1 (SE0+17) |
; Handle Data and Handshake packets (SE0+14) |
; ---------------------------------------------------------------------- |
is_data: |
is_data_handshake: |
andi pid, 0x01 |
breq ignore0 ; ignore ACK/NAK/STALL |
; ---------------------------------------------------------------------- |
; Handle DATA0/DATA1 (SE0+16) |
; ---------------------------------------------------------------------- |
lds pid, token_pid |
tst pid ; data following our SETUP/OUT |
breq ignore ; no, ignore |
breq ignore0 ; no, ignore |
lds tmp, usb_rx_len |
tst tmp ; buffer free? |
brne nak ; no, reply with NAK |
387,21 → 398,12 |
sts usb_rx_off, tmp |
; ---------------------------------------------------------------------- |
; send ACK packet (SE0+35) |
; send ACK packet (SE0+34) |
; ---------------------------------------------------------------------- |
ack: |
ldi YL, lo8(tx_ack) |
ldi YH, hi8(tx_ack) |
rjmp send_token |
; ---------------------------------------------------------------------- |
; send NAK packet (SE0+36) |
; ---------------------------------------------------------------------- |
nak: |
ldi YL, lo8(tx_nak) |
ldi YH, hi8(tx_nak) |
send_token: |
ldi count, 1 ; SE0+40, SE0 --> SOP <= 51 |
ldi count, 1 ; SE0+37, SE0 --> SOP <= 48 |
; ---------------------------------------------------------------------- |
; acquire the bus and send a packet (11 cycles to SOP) |
/Designs/Tools/i2c_AVR_USB/SW/firmware/usbtiny/usb.c |
---|
17,10 → 17,10 |
// the callback functions usb_in() and usb_out() will be called for IN |
// and OUT transfers. |
// |
// Maximum stack usage (gcc 3.4.3 & 4.1.0) of usb_poll(): 5 bytes plus |
// Maximum stack usage (gcc 4.1.0 & 4.3.4) of usb_poll(): 5 bytes plus |
// possible additional stack usage in usb_setup(), usb_in() or usb_out(). |
// |
// Copyright (C) 2006 Dick Streefland |
// Copyright 2006-2010 Dick Streefland |
// |
// This is free software, licensed under the terms of the GNU General |
// Public License as published by the Free Software Foundation. |
58,7 → 58,8 |
byte_t usb_tx_buf[USB_BUFSIZE]; // output buffer |
byte_t usb_tx_len; // output buffer size, 0 means empty |
byte_t usb_address; // assigned USB address |
byte_t usb_address; // assigned device address |
byte_t usb_new_address; // new device address |
// ---------------------------------------------------------------------- |
// Local data |
75,7 → 76,6 |
static byte_t usb_tx_state; // TX_STATE_*, see enum above |
static byte_t usb_tx_total; // total transmit size |
static byte_t* usb_tx_data; // pointer to data to transmit |
static byte_t new_address; // new device address |
#if defined USBTINY_VENDOR_NAME |
struct |
83,7 → 83,7 |
byte_t length; |
byte_t type; |
int string[sizeof(USBTINY_VENDOR_NAME)-1]; |
} string_vendor PROGMEM = |
} const string_vendor PROGMEM = |
{ |
2 * sizeof(USBTINY_VENDOR_NAME), |
DESCRIPTOR_TYPE_STRING, |
100,7 → 100,7 |
byte_t length; |
byte_t type; |
int string[sizeof(USBTINY_DEVICE_NAME)-1]; |
} string_device PROGMEM = |
} const string_device PROGMEM = |
{ |
2 * sizeof(USBTINY_DEVICE_NAME), |
DESCRIPTOR_TYPE_STRING, |
117,7 → 117,7 |
byte_t length; |
byte_t type; |
int string[sizeof(USBTINY_SERIAL)-1]; |
} string_serial PROGMEM = |
} const string_serial PROGMEM = |
{ |
2 * sizeof(USBTINY_SERIAL), |
DESCRIPTOR_TYPE_STRING, |
129,7 → 129,7 |
#endif |
#if VENDOR_NAME_ID || DEVICE_NAME_ID || SERIAL_ID |
static byte_t string_langid [] PROGMEM = |
static byte_t const string_langid [] PROGMEM = |
{ |
4, // bLength |
DESCRIPTOR_TYPE_STRING, // bDescriptorType (string) |
138,11 → 138,11 |
#endif |
// Device Descriptor |
static byte_t descr_device [18] PROGMEM = |
static byte_t const descr_device [18] PROGMEM = |
{ |
18, // bLength |
DESCRIPTOR_TYPE_DEVICE, // bDescriptorType |
LE(0x0110), // bcdUSB |
LE(0x0101), // bcdUSB |
USBTINY_DEVICE_CLASS, // bDeviceClass |
USBTINY_DEVICE_SUBCLASS, // bDeviceSubClass |
USBTINY_DEVICE_PROTOCOL, // bDeviceProtocol |
157,7 → 157,7 |
}; |
// Configuration Descriptor |
static byte_t descr_config [] PROGMEM = |
static byte_t const descr_config [] PROGMEM = |
{ |
9, // bLength |
DESCRIPTOR_TYPE_CONFIGURATION, // bDescriptorType |
223,7 → 223,10 |
} |
else if ( data[1] == 5 ) // SET_ADDRESS |
{ |
new_address = data[2]; |
usb_new_address = data[2]; |
#ifdef USBTINY_USB_OK_LED |
SET(USBTINY_USB_OK_LED);// LED on |
#endif |
} |
else if ( data[1] == 6 ) // GET_DESCRIPTOR |
{ |
371,6 → 374,13 |
{ |
USB_INT_CONFIG |= USB_INT_CONFIG_SET; |
USB_INT_ENABLE |= (1 << USB_INT_ENABLE_BIT); |
#ifdef USBTINY_USB_OK_LED |
OUTPUT(USBTINY_USB_OK_LED); |
#endif |
#ifdef USBTINY_DMINUS_PULLUP |
SET(USBTINY_DMINUS_PULLUP); |
OUTPUT(USBTINY_DMINUS_PULLUP); // enable pullup on D- |
#endif |
sei(); |
} |
392,16 → 402,9 |
usb_rx_len = 0; // accept next packet |
} |
// refill an empty transmit buffer, when the transmitter is active |
if ( usb_tx_len == 0 ) |
if ( usb_tx_len == 0 && usb_tx_state != TX_STATE_IDLE ) |
{ |
if ( usb_tx_state != TX_STATE_IDLE ) |
{ |
usb_transmit(); |
} |
else |
{ // change the USB address at the end of a transfer |
usb_address = new_address; |
} |
usb_transmit(); |
} |
// check for USB bus reset |
for ( i = 10; i > 0 && ! (USB_IN & USB_MASK_DMINUS); i-- ) |
409,10 → 412,10 |
} |
if ( i == 0 ) |
{ // SE0 for more than 2.5uS is a reset |
cli(); |
usb_tx_len=0; |
usb_rx_len=0; |
new_address = 0; |
sei(); |
usb_new_address = 0; |
usb_address = 0; |
#ifdef USBTINY_USB_OK_LED |
CLR(USBTINY_USB_OK_LED); // LED off |
#endif |
} |
} |
/Designs/Tools/i2c_AVR_USB/SW/firmware/usbtiny/usb.h |
---|
1,7 → 1,7 |
// ====================================================================== |
// Public interface of the USB driver |
// |
// Copyright (C) 2006 Dick Streefland |
// Copyright 2006-2008 Dick Streefland |
// |
// This is free software, licensed under the terms of the GNU General |
// Public License as published by the Free Software Foundation. |