summaryrefslogtreecommitdiff
path: root/openpicc/application/ssc.c
diff options
context:
space:
mode:
Diffstat (limited to 'openpicc/application/ssc.c')
-rw-r--r--openpicc/application/ssc.c599
1 files changed, 599 insertions, 0 deletions
diff --git a/openpicc/application/ssc.c b/openpicc/application/ssc.c
new file mode 100644
index 0000000..f15b25d
--- /dev/null
+++ b/openpicc/application/ssc.c
@@ -0,0 +1,599 @@
+/* AT91SAM7 SSC controller routines for OpenPICC
+ * (C) 2006 by Harald Welte <hwelte@hmw-consulting.de>
+ * (C) 2007-2008 Henryk Plötz <henryk@ploetzli.ch>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * We use SSC for both TX and RX side.
+ *
+ * RX side is interconnected with demodulated carrier
+ *
+ * TX side is interconnected with load modulation circuitry
+ */
+
+#include <FreeRTOS.h>
+#include <queue.h>
+#include <task.h>
+#include <openpicc.h>
+
+#include <string.h>
+#include <errno.h>
+
+#include "ssc.h"
+#include "iso14443.h"
+
+#include "tc_cdiv_sync.h"
+#include "tc_fdt.h"
+#include "led.h"
+
+#include "usb_print.h"
+#include "cmd.h"
+
+#define PRINT_DEBUG 0
+
+// BROKEN Old FIQ code
+int ssc_tx_pending=0, ssc_tx_fiq_fdt_cdiv=0, ssc_tx_fiq_fdt_ssc=0;
+
+struct _ssc_handle {
+ enum ssc_mode mode;
+ const struct openpicc_hardware *openpicc;
+ ssc_dma_rx_buffer_t* rx_buffer[2];
+ ssc_callback_t callback;
+ xQueueHandle rx_queue;
+ AT91PS_PDC pdc;
+ AT91PS_SSC ssc;
+ u_int8_t rx_enabled, tx_enabled;
+ u_int8_t rx_running, tx_running;
+};
+
+static ssc_handle_t _ssc;
+static const ssc_mode_def ssc_modes[] = {
+ /* Undefined, no size set */
+ [SSC_MODE_NONE] = {SSC_MODE_NONE, 0, 0, 0},
+ /* 14443A Frame */
+ [SSC_MODE_14443A] = {SSC_MODE_14443A,
+ ISO14443_BITS_PER_SSC_TRANSFER * ISO14443A_SAMPLE_LEN, // transfersize_ssc
+ ISO14443_BITS_PER_SSC_TRANSFER * ISO14443A_SAMPLE_LEN <= 8 ? 8 : 16, // transfersize_pdc
+ DIV_ROUND_UP(ISO14443A_MAX_RX_FRAME_SIZE_IN_BITS, ISO14443_BITS_PER_SSC_TRANSFER) },
+};
+static struct {
+ ssc_metric metric;
+ char *name;
+ int value;
+} ssc_metrics[] = {
+ [METRIC_RX_OVERFLOWS] = {METRIC_RX_OVERFLOWS, "Rx overflows", 0},
+ [METRIC_FREE_RX_BUFFERS] = {METRIC_FREE_RX_BUFFERS, "Free Rx buffers", 0},
+ [METRIC_MANAGEMENT_ERRORS] = {METRIC_MANAGEMENT_ERRORS, "Internal buffer management error", 0},
+ [METRIC_MANAGEMENT_ERRORS_1] = {METRIC_MANAGEMENT_ERRORS_1, "Internal buffer management error type 1", 0},
+ [METRIC_MANAGEMENT_ERRORS_2] = {METRIC_MANAGEMENT_ERRORS_2, "Internal buffer management error type 2", 0},
+ [METRIC_MANAGEMENT_ERRORS_3] = {METRIC_MANAGEMENT_ERRORS_3, "Internal buffer management error type 3", 0},
+};
+
+static ssc_dma_rx_buffer_t _rx_buffers[SSC_DMA_BUFFER_COUNT];
+ssc_dma_tx_buffer_t _tx_buffer;
+
+/******* PRIVATE "meat" code *************************************************/
+
+#define SSC_RX_IRQ_MASK (AT91C_SSC_RXRDY | \
+ AT91C_SSC_OVRUN | \
+ AT91C_SSC_ENDRX | \
+ AT91C_SSC_RXBUFF | \
+ AT91C_SSC_RXSYN | \
+ AT91C_SSC_CP0 | \
+ AT91C_SSC_CP1)
+
+#define SSC_TX_IRQ_MASK (AT91C_SSC_TXRDY | \
+ AT91C_SSC_TXEMPTY | \
+ AT91C_SSC_ENDTX | \
+ AT91C_SSC_TXBUFE | \
+ AT91C_SSC_TXSYN)
+
+static __ramfunc ssc_dma_rx_buffer_t* _unload_rx(ssc_handle_t *sh);
+static __ramfunc int _reload_rx(ssc_handle_t *sh);
+
+
+static int __ramfunc _ssc_rx_irq(u_int32_t orig_sr, int start_asserted, portBASE_TYPE task_woken)
+{
+ int end_asserted = 0;
+ ssc_handle_t *sh = &_ssc;
+ u_int32_t orig_rcmr = sh->ssc->SSC_RCMR;
+
+#if PRINT_DEBUG
+ int old = usb_print_set_default_flush(0); // DEBUG OUTPUT
+ DumpStringToUSB("orig:"); // DEBUG OUTPUT
+ DumpUIntToUSB(orig_sr); // DEBUG OUTPUT
+ DumpStringToUSB("\n\r"); // DEBUG OUTPUT
+#endif
+ u_int32_t sr = orig_sr | _ssc.ssc->SSC_SR;
+
+ if( (sr & AT91C_SSC_RXSYN) || start_asserted) {
+ sh->ssc->SSC_RCMR = (orig_rcmr & (~AT91C_SSC_START)) | (AT91C_SSC_START_CONTINOUS);
+ /* Receiving has started */
+ if(sh->callback != NULL) {
+ sh->callback(CALLBACK_RX_FRAME_BEGIN, &end_asserted);
+ if(end_asserted)
+ sr = orig_sr | _ssc.ssc->SSC_SR;
+ }
+ }
+
+#if PRINT_DEBUG
+ DumpStringToUSB("sr:"); // DEBUG OUTPUT
+ DumpUIntToUSB(sr); // DEBUG OUTPUT
+ DumpStringToUSB("\n\r"); // DEBUG OUTPUT
+ usb_print_set_default_flush(old); // DEBUG OUTPUT
+#endif
+
+ if(((sr & (AT91C_SSC_CP1 | AT91C_SSC_ENDRX)) || end_asserted) && (sh->rx_buffer[0] != NULL)) {
+ /* Receiving has ended */
+ AT91F_PDC_DisableRx(sh->pdc);
+ AT91F_SSC_DisableRx(sh->ssc);
+ sh->ssc->SSC_RCMR = ((sh->ssc->SSC_RCMR) & (~AT91C_SSC_START)) | (AT91C_SSC_START_0);
+
+ ssc_dma_rx_buffer_t *buffer = _unload_rx(sh);
+ if(buffer != NULL) {
+ if(sh->callback != NULL)
+ sh->callback(CALLBACK_RX_FRAME_ENDED, buffer);
+
+ if(buffer->state != FREE) {
+ task_woken = xQueueSendFromISR(sh->rx_queue, &buffer, task_woken);
+ }
+ }
+
+ if(_reload_rx(sh)) {
+ int dummy = sh->ssc->SSC_RHR; (void)dummy;
+ AT91F_PDC_EnableRx(sh->pdc);
+ AT91F_SSC_EnableRx(sh->ssc);
+ } else {
+ sh->ssc->SSC_IDR = SSC_RX_IRQ_MASK;
+ sh->rx_running = 0;
+ sh->callback(CALLBACK_RX_STOPPED, sh);
+ }
+
+ }
+
+ sh->ssc->SSC_RCMR = orig_rcmr;
+ return task_woken;
+}
+
+/* Exported callback for the case when the frame start has been detected externally */
+void ssc_frame_started(void)
+{
+ _ssc_rx_irq(_ssc.ssc->SSC_SR, 1, pdFALSE);
+}
+
+static void __ramfunc ssc_irq(void) __attribute__ ((naked));
+static void __ramfunc ssc_irq(void)
+{
+ portENTER_SWITCHING_ISR();
+ vLedSetRed(1);
+ portBASE_TYPE task_woken = pdFALSE;
+
+ u_int32_t sr = _ssc.ssc->SSC_SR;
+ if(sr & AT91C_SSC_RXSYN) {
+ task_woken = _ssc_rx_irq(sr, 1, task_woken);
+ } else if(sr & SSC_RX_IRQ_MASK) {
+ task_woken = _ssc_rx_irq(sr, 0, task_woken);
+ }
+
+ AT91F_AIC_ClearIt(AT91C_ID_SSC);
+ AT91F_AIC_AcknowledgeIt();
+
+ vLedSetRed(0);
+ portEXIT_SWITCHING_ISR(task_woken);
+}
+
+static __ramfunc ssc_dma_rx_buffer_t *_get_buffer(ssc_dma_buffer_state_t old, ssc_dma_buffer_state_t new)
+{
+ ssc_dma_rx_buffer_t *buffer = NULL;
+ int i;
+ for(i=0; i < SSC_DMA_BUFFER_COUNT; i++) {
+ if(_rx_buffers[i].state == old) {
+ buffer = &_rx_buffers[i];
+ buffer->state = new;
+ break;
+ }
+ }
+ return buffer;
+}
+
+/* Doesn't check sh, must be called with interrupts disabled */
+static __ramfunc int _reload_rx(ssc_handle_t *sh)
+{
+ int result = 0;
+ if(sh->rx_buffer[0] != NULL) {
+ ssc_metrics[METRIC_MANAGEMENT_ERRORS_1].value++;
+ result = 1;
+ goto out;
+ }
+
+ ssc_dma_rx_buffer_t *buffer = _get_buffer(FREE, PENDING);
+
+ if(buffer == NULL) {
+ ssc_metrics[METRIC_RX_OVERFLOWS].value++;
+ goto out;
+ }
+
+ buffer->reception_mode = &ssc_modes[sh->mode];
+ buffer->len_transfers = ssc_modes[sh->mode].transfers;
+
+ AT91F_PDC_SetRx(sh->pdc, buffer->data, buffer->len_transfers);
+ sh->rx_buffer[0] = buffer;
+
+ result = 1;
+out:
+ return result;
+}
+
+// Doesn't check sh, call with interrupts disabled, SSC/PDC stopped
+static __ramfunc ssc_dma_rx_buffer_t* _unload_rx(ssc_handle_t *sh)
+{
+ if(sh->rx_buffer[0] == NULL) {
+ ssc_metrics[METRIC_MANAGEMENT_ERRORS_2].value++;
+ return NULL;
+ }
+
+ ssc_dma_rx_buffer_t *buffer = sh->rx_buffer[0];
+ u_int32_t rpr = sh->pdc->PDC_RPR,
+ rcr = sh->pdc->PDC_RCR;
+ AT91F_PDC_SetRx(sh->pdc, 0, 0);
+ sh->rx_buffer[0] = NULL;
+ buffer->state = FULL;
+
+ if(rcr == 0) {
+ buffer->flags.overflow = 1;
+ } else {
+ buffer->flags.overflow = 0;
+ }
+
+ if(rcr > 0) {
+ /* Append a zero to make sure the buffer decoder finds the stop bit */
+ switch(buffer->reception_mode->transfersize_pdc) {
+ case 8:
+ //*((u_int8_t*)rpr++) = sh->ssc->SSC_RSHR;
+ *((u_int8_t*)rpr++) = 0;
+ --rcr;
+ break;
+ case 16:
+ *((u_int16_t*)rpr++) = 0;
+ --rcr;
+ break;
+ case 32:
+ *((u_int32_t*)rpr++) = 0;
+ --rcr;
+ break;
+ }
+ }
+
+ if((buffer->len_transfers - rcr) != (rpr - (unsigned int)buffer->data)*(buffer->reception_mode->transfersize_pdc/8)) {
+ ssc_metrics[METRIC_MANAGEMENT_ERRORS_3].value++;
+ buffer->state = FREE;
+ return NULL;
+ }
+
+ buffer->len_transfers = buffer->len_transfers - rcr;
+
+ return buffer;
+}
+
+void ssc_select_clock(enum ssc_clock_source clock)
+{
+ if(!OPENPICC->features.clock_switching) return;
+ switch(clock) {
+ case CLOCK_SELECT_PLL:
+ AT91F_PIO_ClearOutput(AT91C_BASE_PIOA, OPENPICC->CLOCK_SWITCH);
+ break;
+ case CLOCK_SELECT_CARRIER:
+ AT91F_PIO_SetOutput(AT91C_BASE_PIOA, OPENPICC->CLOCK_SWITCH);
+ break;
+ default: break;
+ }
+}
+
+static void _ssc_start_rx(ssc_handle_t *sh)
+{
+ taskENTER_CRITICAL();
+ if(sh != &_ssc) goto out;
+ if(sh->rx_running) goto out;
+ if(!sh->rx_enabled) goto out;
+
+ // Load buffer
+ if(!_reload_rx(sh))
+ goto out;
+
+ sh->ssc->SSC_IER = AT91C_SSC_RXSYN | \
+ AT91C_SSC_CP1 | AT91C_SSC_ENDRX;
+ sh->rx_running = 1;
+ if(sh->callback != NULL)
+ sh->callback(CALLBACK_RX_STARTED, sh);
+
+ // Actually enable reception
+ int dummy = sh->ssc->SSC_RHR; (void)dummy;
+ AT91F_PDC_EnableRx(sh->pdc);
+ AT91F_SSC_EnableRx(sh->ssc);
+
+out:
+ taskEXIT_CRITICAL();
+ usb_print_string_f(sh->rx_running ? "SSC now running\n\r":"SSC not running\n\r", 0);
+}
+
+static void _ssc_stop_rx(ssc_handle_t *sh)
+{
+ taskENTER_CRITICAL();
+ sh->ssc->SSC_IDR = SSC_RX_IRQ_MASK;
+ sh->rx_running = 0;
+ if(sh->callback != NULL)
+ sh->callback(CALLBACK_RX_STOPPED, sh);
+ taskEXIT_CRITICAL();
+}
+
+/******* PRIVATE Initialization Code *****************************************/
+static void _ssc_rx_mode_set(ssc_handle_t *sh, enum ssc_mode ssc_mode)
+{
+ taskENTER_CRITICAL();
+ int was_running = sh->rx_running;
+ if(was_running) _ssc_stop_rx(sh);
+
+ u_int8_t data_len=0, num_data=0, sync_len=0;
+ u_int32_t start_cond=0;
+ u_int32_t clock_gating=0;
+ u_int8_t stop = 0, invert=0;
+
+ switch(ssc_mode) {
+ case SSC_MODE_14443A:
+ /* Start on Compare 0. The funky calculations down there are designed to allow a different
+ * (longer) compare length for Compare 1 than for Compare 0. Both lengths are set by the
+ * same register. */
+ start_cond = AT91C_SSC_START_0;
+ sync_len = ISO14443A_EOF_LEN;
+ sh->ssc->SSC_RC0R = ISO14443A_SOF_SAMPLE << (ISO14443A_EOF_LEN-ISO14443A_SOF_LEN);
+ sh->ssc->SSC_RC1R = ISO14443A_EOF_SAMPLE;
+
+ data_len = ssc_modes[SSC_MODE_14443A].transfersize_ssc;
+
+ /* We are using stop on Compare 1. The docs are ambiguous but my interpretation is that
+ * this means that num_data is basically ignored and reception is continuous until stop
+ * event. Set num_data to the maximum anyways. */
+ num_data = 16;
+ stop = 1;
+
+ stop = 0;
+ start_cond = AT91C_SSC_START_CONTINOUS;
+ sync_len = 0;
+
+ /* We can't use receive clock gating with RF because RF will only go up with the first
+ * edge, this doesn't cooperate with the longer sync len above.
+ * FIXME: What's the interaction between clock BURST on v0.4p1, RF and Compare 0?
+ * In theory this shouldn't work even without SSC clock_gating because BURST gates the
+ * clock before the SSC and so it shouldn't get sync_len samples before Compare 0.
+ * I believe there's a bug waiting to happen somewhere here. */
+ clock_gating = (0x0 << 6);
+ //invert = AT91C_SSC_CKI;
+ break;
+ case SSC_MODE_NONE:
+ goto out;
+ break;
+ }
+
+ /* Receive frame mode register */
+ sh->ssc->SSC_RFMR = ((data_len-1) & 0x1f) |
+ (((num_data-1) & 0x0f) << 8) |
+ (((sync_len-1) & 0x0f) << 16);
+
+ /* Receive clock mode register, Clock selection: RK, Clock output: None */
+ sh->ssc->SSC_RCMR = AT91C_SSC_CKS_RK | AT91C_SSC_CKO_NONE |
+ clock_gating | invert | start_cond | (stop << 12);
+
+out:
+ sh->mode = ssc_mode;
+ if(was_running) _ssc_start_rx(sh);
+ taskEXIT_CRITICAL();
+}
+
+static inline int _init_ssc_rx(ssc_handle_t *sh)
+{
+ tc_cdiv_sync_init();
+ tc_cdiv_sync_enable();
+
+ if(sh->rx_queue == NULL) {
+ sh->rx_queue = xQueueCreate(10, sizeof(sh->rx_buffer[0]));
+ if(sh->rx_queue == NULL)
+ goto out_fail_queue;
+ }
+
+ sh->ssc = AT91C_BASE_SSC;
+ sh->pdc = (AT91PS_PDC) &(sh->ssc->SSC_RPR);
+
+ AT91F_SSC_CfgPMC();
+
+ AT91F_PIO_CfgPeriph(AT91C_BASE_PIOA,
+ OPENPICC_SSC_DATA | OPENPICC_SSC_CLOCK |
+ OPENPICC_PIO_FRAME,
+ 0);
+
+ AT91F_PIO_CfgOutput(AT91C_BASE_PIOA, OPENPICC_PIO_SSC_DATA_CONTROL);
+ AT91F_PIO_ClearOutput(AT91C_BASE_PIOA, OPENPICC_PIO_SSC_DATA_CONTROL);
+
+ if(OPENPICC->features.data_gating) {
+ AT91F_PIO_CfgOutput(AT91C_BASE_PIOA, OPENPICC->DATA_GATE);
+ AT91F_PIO_SetOutput(AT91C_BASE_PIOA, OPENPICC->DATA_GATE);
+ }
+
+ if(OPENPICC->features.clock_switching) {
+ AT91F_PIO_CfgOutput(AT91C_BASE_PIOA, OPENPICC->CLOCK_SWITCH);
+ ssc_select_clock(CLOCK_SELECT_PLL);
+ }
+
+ /* Disable all interrupts */
+ sh->ssc->SSC_IDR = SSC_RX_IRQ_MASK;
+
+ /* don't divide clock inside SSC, we do that in tc_cdiv */
+ sh->ssc->SSC_CMR = 0;
+
+ sh->rx_buffer[0] = sh->rx_buffer[1] = NULL;
+
+ /* Will be set to a real value some time later
+ * FIXME Layering? */
+ tc_fdt_set(0xff00);
+
+ return 1;
+
+out_fail_queue:
+ return 0;
+}
+
+static int _ssc_register_callback(ssc_handle_t *sh, ssc_callback_t _callback)
+{
+ if(!sh) return -EINVAL;
+ if(sh->callback != NULL) return -EBUSY;
+ sh->callback = _callback;
+ if(sh->callback != NULL)
+ sh->callback(CALLBACK_SETUP, sh);
+ return 0;
+}
+
+static int _ssc_unregister_callback(ssc_handle_t *sh, ssc_callback_t _callback)
+{
+ if(!sh) return -EINVAL;
+ if(_callback == NULL || sh->callback == _callback) {
+ if(sh->callback != NULL)
+ sh->callback(CALLBACK_TEARDOWN, sh);
+ sh->callback = NULL;
+ }
+ return 0;
+}
+
+/******* PUBLIC API **********************************************************/
+ssc_handle_t* ssc_open(u_int8_t init_rx, u_int8_t init_tx, enum ssc_mode mode, ssc_callback_t callback)
+{
+ ssc_handle_t *sh = &_ssc;
+ unsigned int i;
+
+ if(sh->rx_enabled || sh->tx_enabled || sh->rx_running) {
+ if( ssc_close(sh) != 0) {
+ return NULL;
+ }
+ }
+
+ for(i=0; i<sizeof(_rx_buffers)/sizeof(_rx_buffers[0]); i++)
+ memset(&_rx_buffers[i], 0, sizeof(_rx_buffers[i]));
+
+ if(init_tx) {
+ // FIXME Implement
+ sh->tx_enabled = 0;
+ return NULL;
+ }
+
+ if(init_rx) {
+ sh->rx_enabled = _init_ssc_rx(sh);
+ if(!sh->rx_enabled) {
+ ssc_close(sh);
+ return NULL;
+ } else {
+ _ssc_rx_mode_set(sh, mode);
+ }
+ }
+
+ if(sh->rx_enabled || sh->tx_enabled) {
+ AT91F_AIC_ConfigureIt(AT91C_ID_SSC,
+ OPENPICC_IRQ_PRIO_SSC,
+ AT91C_AIC_SRCTYPE_INT_HIGH_LEVEL, (THandler)&ssc_irq);
+ //AT91C_AIC_SRCTYPE_INT_POSITIVE_EDGE, (THandler)&ssc_irq);
+ AT91F_AIC_EnableIt(AT91C_ID_SSC);
+ }
+
+ if(callback != NULL)
+ _ssc_register_callback(sh, callback);
+
+ if(init_rx)
+ _ssc_start_rx(sh);
+
+ return sh;
+}
+
+int ssc_recv(ssc_handle_t* sh, ssc_dma_rx_buffer_t* *buffer,unsigned int timeout)
+{
+ if(sh == NULL) return -EINVAL;
+
+ taskENTER_CRITICAL();
+ if(sh->rx_running) {
+ if(PRINT_DEBUG) usb_print_string_f("Continuing SSC Rx\n\r",0); // DEBUG OUTPUT
+ } else {
+ if(PRINT_DEBUG) usb_print_string_f("ERR: SSC halted\n\r",0); // DEBUG OUTPUT
+ /* Try starting the Reception */
+ _ssc_start_rx(sh);
+ }
+ taskEXIT_CRITICAL();
+
+ if(xQueueReceive(sh->rx_queue, buffer, timeout)){
+ if(*buffer != NULL) return 0;
+ else return -EINTR;
+ }
+
+ return -ETIMEDOUT;
+}
+
+int ssc_close(ssc_handle_t* sh)
+{
+ if(sh->rx_running)
+ _ssc_stop_rx(sh);
+
+ if(sh->rx_enabled) {
+ // FIXME Implement
+ sh->rx_enabled = 0;
+ }
+ if(sh->tx_enabled) {
+ // FIXME Implement
+ sh->tx_enabled = 0;
+ }
+
+ _ssc_unregister_callback(sh, NULL);
+ return 0;
+}
+
+int ssc_get_metric(ssc_metric metric, char **description, int *value)
+{
+ char *_name="Undefined";
+ int _value=0;
+ int valid=0;
+
+ if(metric < sizeof(ssc_metrics)/sizeof(ssc_metrics[0])) {
+ _name = ssc_metrics[metric].name;
+ _value = ssc_metrics[metric].value;
+ valid = 1;
+ }
+
+ switch(metric) {
+ case METRIC_FREE_RX_BUFFERS:
+ _value = 0;
+ int i;
+ for(i=0; i < SSC_DMA_BUFFER_COUNT; i++)
+ if(_rx_buffers[i].state == FREE) _value++;
+ break;
+ case METRIC_MANAGEMENT_ERRORS:
+ _value = ssc_metrics[METRIC_MANAGEMENT_ERRORS_1].value +
+ ssc_metrics[METRIC_MANAGEMENT_ERRORS_2].value +
+ ssc_metrics[METRIC_MANAGEMENT_ERRORS_3].value;
+ break;
+ default:
+ break;
+ }
+
+ if(!valid) return 0;
+
+ if(description != NULL) *description = _name;
+ if(value != NULL) *value = _value;
+ return 1;
+}
+
personal git repositories of Harald Welte. Your mileage may vary