RBS2000: Move SABM re-transmit logic into LAPD code
This means we now can support multiple LAPD links
This commit is contained in:
parent
334c5ab2e6
commit
dcf42e6cd2
|
@ -31,8 +31,6 @@
|
||||||
|
|
||||||
#include "input/lapd.h"
|
#include "input/lapd.h"
|
||||||
|
|
||||||
#define SABM_INTERVAL 0, 300000
|
|
||||||
|
|
||||||
static struct gsm_bts_model model_rbs2k = {
|
static struct gsm_bts_model model_rbs2k = {
|
||||||
.type = GSM_BTS_TYPE_RBS2000,
|
.type = GSM_BTS_TYPE_RBS2000,
|
||||||
.name = "rbs2000",
|
.name = "rbs2000",
|
||||||
|
@ -52,6 +50,26 @@ static int shutdown_om(struct gsm_bts *bts)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Tell LAPD to start start the SAP (send SABM requests) for all signalling
|
||||||
|
* timeslots in this line */
|
||||||
|
static void start_sabm_in_line(struct e1inp_line *line)
|
||||||
|
{
|
||||||
|
struct e1inp_sign_link *link;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < ARRAY_SIZE(line->ts); i++) {
|
||||||
|
struct e1inp_ts *ts = &line->ts[i];
|
||||||
|
|
||||||
|
if (ts->type != E1INP_TS_TYPE_SIGN)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
llist_for_each_entry(link, &ts->sign.sign_links, list) {
|
||||||
|
lapd_sap_start(ts->driver.dahdi.lapd, link->tei, link->sapi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Callback function to be called every time we receive a signal from INPUT */
|
/* Callback function to be called every time we receive a signal from INPUT */
|
||||||
static int gbl_sig_cb(unsigned int subsys, unsigned int signal,
|
static int gbl_sig_cb(unsigned int subsys, unsigned int signal,
|
||||||
void *handler_data, void *signal_data)
|
void *handler_data, void *signal_data)
|
||||||
|
@ -72,23 +90,6 @@ static int gbl_sig_cb(unsigned int subsys, unsigned int signal,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void sabm_timer_cb(void *_line);
|
|
||||||
|
|
||||||
/* FIXME: we need one per bts (or rather: per signalling TS, not one global! */
|
|
||||||
struct timer_list sabm_timer = {
|
|
||||||
.cb = &sabm_timer_cb,
|
|
||||||
};
|
|
||||||
|
|
||||||
static void sabm_timer_cb(void *_line)
|
|
||||||
{
|
|
||||||
struct e1inp_ts *e1i_ts = _line;
|
|
||||||
|
|
||||||
/* FIXME: use the TEI that was configured via vty */
|
|
||||||
lapd_send_sabm(e1i_ts->driver.dahdi.lapd, 62, 62);
|
|
||||||
|
|
||||||
bsc_schedule_timer(&sabm_timer, SABM_INTERVAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Callback function to be called every time we receive a signal from INPUT */
|
/* Callback function to be called every time we receive a signal from INPUT */
|
||||||
static int inp_sig_cb(unsigned int subsys, unsigned int signal,
|
static int inp_sig_cb(unsigned int subsys, unsigned int signal,
|
||||||
void *handler_data, void *signal_data)
|
void *handler_data, void *signal_data)
|
||||||
|
@ -103,8 +104,6 @@ static int inp_sig_cb(unsigned int subsys, unsigned int signal,
|
||||||
switch (isd->link_type) {
|
switch (isd->link_type) {
|
||||||
case E1INP_SIGN_OML:
|
case E1INP_SIGN_OML:
|
||||||
if (isd->trx->bts->type == GSM_BTS_TYPE_RBS2000) {
|
if (isd->trx->bts->type == GSM_BTS_TYPE_RBS2000) {
|
||||||
/* FIXME: only disable the timer that belong sto this timeslot */
|
|
||||||
bsc_del_timer(&sabm_timer);
|
|
||||||
bootstrap_om_rbs2k(isd->trx->bts);
|
bootstrap_om_rbs2k(isd->trx->bts);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -114,10 +113,7 @@ static int inp_sig_cb(unsigned int subsys, unsigned int signal,
|
||||||
/* Right now Ericsson RBS are only supported on DAHDI */
|
/* Right now Ericsson RBS are only supported on DAHDI */
|
||||||
if (strcasecmp(isd->line->driver->name, "DAHDI"))
|
if (strcasecmp(isd->line->driver->name, "DAHDI"))
|
||||||
break;
|
break;
|
||||||
/* FIXME: properly determine the OML signalling timeslot,
|
start_sabm_in_line(isd->line);
|
||||||
* or rather: all signalling timeslots and start one timer each */
|
|
||||||
sabm_timer.data = &isd->line->ts[1-1];
|
|
||||||
bsc_schedule_timer(&sabm_timer, SABM_INTERVAL);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
/* OpenBSC minimal LAPD implementation */
|
/* OpenBSC minimal LAPD implementation */
|
||||||
|
|
||||||
/* (C) 2009 by oystein@homelien.no
|
/* (C) 2009 by oystein@homelien.no
|
||||||
* (C) 2011 by Harald Welte <laforge@gnumonks.org>
|
|
||||||
* (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
|
* (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
|
||||||
* (C) 2010 by Digium and Matthew Fredrickson <creslin@digium.com>
|
* (C) 2010 by Digium and Matthew Fredrickson <creslin@digium.com>
|
||||||
|
* (C) 2011 by Harald Welte <laforge@gnumonks.org>
|
||||||
*
|
*
|
||||||
* All Rights Reserved
|
* All Rights Reserved
|
||||||
*
|
*
|
||||||
|
@ -23,6 +23,12 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* TODO:
|
||||||
|
* detect RR timeout and set SAP state back to SABM_RETRANSMIT
|
||||||
|
* use of value_string
|
||||||
|
* further code cleanup (spaghetti)
|
||||||
|
*/
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
@ -33,13 +39,14 @@
|
||||||
#include <osmocore/linuxlist.h>
|
#include <osmocore/linuxlist.h>
|
||||||
#include <osmocore/talloc.h>
|
#include <osmocore/talloc.h>
|
||||||
#include <osmocore/msgb.h>
|
#include <osmocore/msgb.h>
|
||||||
|
#include <osmocore/timer.h>
|
||||||
#include <openbsc/debug.h>
|
#include <openbsc/debug.h>
|
||||||
|
|
||||||
|
#define SABM_INTERVAL 0, 300000
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
LAPD_TEI_NONE = 0,
|
LAPD_TEI_NONE = 0,
|
||||||
|
|
||||||
LAPD_TEI_ASSIGNED,
|
LAPD_TEI_ASSIGNED,
|
||||||
|
|
||||||
LAPD_TEI_ACTIVE,
|
LAPD_TEI_ACTIVE,
|
||||||
} lapd_tei_state;
|
} lapd_tei_state;
|
||||||
|
|
||||||
|
@ -93,10 +100,24 @@ const char *lapd_cmd_types[] = {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum lapd_sap_state {
|
||||||
|
SAP_STATE_INACTIVE,
|
||||||
|
SAP_STATE_SABM_RETRANS,
|
||||||
|
SAP_STATE_ACTIVE,
|
||||||
|
};
|
||||||
|
|
||||||
|
const char *lapd_sap_states[] = {
|
||||||
|
"INACTIVE",
|
||||||
|
"SABM_RETRANS",
|
||||||
|
"ACTIVE",
|
||||||
|
};
|
||||||
|
|
||||||
const char *lapd_msg_types = "?ISU";
|
const char *lapd_msg_types = "?ISU";
|
||||||
|
|
||||||
|
/* structure representing an allocated TEI within a LAPD instance */
|
||||||
struct lapd_tei {
|
struct lapd_tei {
|
||||||
struct llist_head list;
|
struct llist_head list;
|
||||||
|
struct lapd_instance *li;
|
||||||
|
|
||||||
uint8_t tei;
|
uint8_t tei;
|
||||||
/* A valid N(R) value is one that is in the range V(A) ≤ N(R) ≤ V(S). */
|
/* A valid N(R) value is one that is in the range V(A) ≤ N(R) ≤ V(S). */
|
||||||
|
@ -104,6 +125,19 @@ struct lapd_tei {
|
||||||
int va; /* last acked by peer */
|
int va; /* last acked by peer */
|
||||||
int vr; /* next expected to be received */
|
int vr; /* next expected to be received */
|
||||||
lapd_tei_state state;
|
lapd_tei_state state;
|
||||||
|
|
||||||
|
struct llist_head sap_list;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Structure representing a SAP within a TEI. We use this for TE-mode to
|
||||||
|
* re-transmit SABM */
|
||||||
|
struct lapd_sap {
|
||||||
|
struct llist_head list;
|
||||||
|
struct lapd_tei *tei;
|
||||||
|
uint8_t sapi;
|
||||||
|
enum lapd_sap_state state;
|
||||||
|
|
||||||
|
struct timer_list sabme_timer; /* timer to re-transmit SABM message */
|
||||||
};
|
};
|
||||||
|
|
||||||
/* 3.5.2.2 Send state variable V(S)
|
/* 3.5.2.2 Send state variable V(S)
|
||||||
|
@ -142,6 +176,7 @@ struct lapd_tei {
|
||||||
* correctly received all I frames numbered up to and including N(R) − 1.
|
* correctly received all I frames numbered up to and including N(R) − 1.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* Resolve TEI structure from given numeric TEI */
|
||||||
static struct lapd_tei *teip_from_tei(struct lapd_instance *li, uint8_t tei)
|
static struct lapd_tei *teip_from_tei(struct lapd_instance *li, uint8_t tei)
|
||||||
{
|
{
|
||||||
struct lapd_tei *lt;
|
struct lapd_tei *lt;
|
||||||
|
@ -155,28 +190,87 @@ static struct lapd_tei *teip_from_tei(struct lapd_instance *li, uint8_t tei)
|
||||||
|
|
||||||
static void lapd_tei_set_state(struct lapd_tei *teip, int newstate)
|
static void lapd_tei_set_state(struct lapd_tei *teip, int newstate)
|
||||||
{
|
{
|
||||||
DEBUGP(DMI, "state change on tei %d: %s -> %s\n", teip->tei,
|
DEBUGP(DMI, "state change on TEI %d: %s -> %s\n", teip->tei,
|
||||||
lapd_tei_states[teip->state], lapd_tei_states[newstate]);
|
lapd_tei_states[teip->state], lapd_tei_states[newstate]);
|
||||||
teip->state = newstate;
|
teip->state = newstate;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Allocate a new TEI */
|
||||||
int lapd_tei_alloc(struct lapd_instance *li, uint8_t tei)
|
struct lapd_tei *lapd_tei_alloc(struct lapd_instance *li, uint8_t tei)
|
||||||
{
|
{
|
||||||
struct lapd_tei *teip;
|
struct lapd_tei *teip;
|
||||||
|
|
||||||
teip = talloc_zero(li, struct lapd_tei);
|
teip = talloc_zero(li, struct lapd_tei);
|
||||||
if (!teip)
|
if (!teip)
|
||||||
return -ENOMEM;
|
return NULL;
|
||||||
|
|
||||||
|
teip->li = li;
|
||||||
teip->tei = tei;
|
teip->tei = tei;
|
||||||
llist_add(&teip->list, &li->tei_list);
|
llist_add(&teip->list, &li->tei_list);
|
||||||
|
INIT_LLIST_HEAD(&teip->sap_list);
|
||||||
|
|
||||||
lapd_tei_set_state(teip, LAPD_TEI_ASSIGNED);
|
lapd_tei_set_state(teip, LAPD_TEI_ASSIGNED);
|
||||||
|
|
||||||
return 0;
|
return teip;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Find a SAP within a given TEI */
|
||||||
|
static struct lapd_sap *lapd_sap_find(struct lapd_tei *teip, uint8_t sapi)
|
||||||
|
{
|
||||||
|
struct lapd_sap *sap;
|
||||||
|
|
||||||
|
llist_for_each_entry(sap, &teip->sap_list, list) {
|
||||||
|
if (sap->sapi == sapi)
|
||||||
|
return sap;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sabme_timer_cb(void *_sap);
|
||||||
|
|
||||||
|
/* Allocate a new SAP within a given TEI */
|
||||||
|
static struct lapd_sap *lapd_sap_alloc(struct lapd_tei *teip, uint8_t sapi)
|
||||||
|
{
|
||||||
|
struct lapd_sap *sap = talloc_zero(teip, struct lapd_sap);
|
||||||
|
|
||||||
|
LOGP(DMI, LOGL_INFO, "Allocating SAP for SAPI=%u / TEI=%u\n",
|
||||||
|
sapi, teip->tei);
|
||||||
|
|
||||||
|
sap->sapi = sapi;
|
||||||
|
sap->tei = teip;
|
||||||
|
sap->sabme_timer.cb = &sabme_timer_cb;
|
||||||
|
sap->sabme_timer.data = sap;
|
||||||
|
|
||||||
|
llist_add(&sap->list, &teip->sap_list);
|
||||||
|
|
||||||
|
return sap;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void lapd_sap_set_state(struct lapd_tei *teip, uint8_t sapi,
|
||||||
|
enum lapd_sap_state newstate)
|
||||||
|
{
|
||||||
|
struct lapd_sap *sap = lapd_sap_find(teip, sapi);
|
||||||
|
if (!sap)
|
||||||
|
return;
|
||||||
|
|
||||||
|
DEBUGP(DMI, "state change on TEI %u / SAPI %u: %s -> %s\n", teip->tei,
|
||||||
|
sapi, lapd_sap_states[sap->state], lapd_sap_states[newstate]);
|
||||||
|
switch (sap->state) {
|
||||||
|
case SAP_STATE_SABM_RETRANS:
|
||||||
|
if (newstate != SAP_STATE_SABM_RETRANS)
|
||||||
|
bsc_del_timer(&sap->sabme_timer);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (newstate == SAP_STATE_SABM_RETRANS)
|
||||||
|
bsc_schedule_timer(&sap->sabme_timer, SABM_INTERVAL);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
sap->state = newstate;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Input function into TEI manager */
|
||||||
static void lapd_tei_receive(struct lapd_instance *li, uint8_t *data, int len)
|
static void lapd_tei_receive(struct lapd_instance *li, uint8_t *data, int len)
|
||||||
{
|
{
|
||||||
uint8_t entity = data[0];
|
uint8_t entity = data[0];
|
||||||
|
@ -214,6 +308,7 @@ static void lapd_tei_receive(struct lapd_instance *li, uint8_t *data, int len)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* General input function for any data received for this LAPD instance */
|
||||||
uint8_t *lapd_receive(struct lapd_instance *li, uint8_t * data, unsigned int len,
|
uint8_t *lapd_receive(struct lapd_instance *li, uint8_t * data, unsigned int len,
|
||||||
int *ilen, lapd_mph_type *prim)
|
int *ilen, lapd_mph_type *prim)
|
||||||
{
|
{
|
||||||
|
@ -391,6 +486,7 @@ uint8_t *lapd_receive(struct lapd_instance *li, uint8_t * data, unsigned int len
|
||||||
teip->vr = 0;
|
teip->vr = 0;
|
||||||
teip->va = 0;
|
teip->va = 0;
|
||||||
lapd_tei_set_state(teip, LAPD_TEI_ACTIVE);
|
lapd_tei_set_state(teip, LAPD_TEI_ACTIVE);
|
||||||
|
lapd_sap_set_state(teip, sapi, SAP_STATE_ACTIVE);
|
||||||
*prim = LAPD_MPH_ACTIVATE_IND;
|
*prim = LAPD_MPH_ACTIVATE_IND;
|
||||||
break;
|
break;
|
||||||
case LAPD_CMD_RR:
|
case LAPD_CMD_RR:
|
||||||
|
@ -478,7 +574,8 @@ uint8_t *lapd_receive(struct lapd_instance *li, uint8_t * data, unsigned int len
|
||||||
return NULL;
|
return NULL;
|
||||||
};
|
};
|
||||||
|
|
||||||
int lapd_send_sabm(struct lapd_instance *li, uint8_t tei, uint8_t sapi)
|
/* low-level function to send a single SABM message */
|
||||||
|
static int lapd_send_sabm(struct lapd_instance *li, uint8_t tei, uint8_t sapi)
|
||||||
{
|
{
|
||||||
struct msgb *msg = msgb_alloc_headroom(1024, 128, "LAPD SABM");
|
struct msgb *msg = msgb_alloc_headroom(1024, 128, "LAPD SABM");
|
||||||
if (!msg)
|
if (!msg)
|
||||||
|
@ -486,10 +583,6 @@ int lapd_send_sabm(struct lapd_instance *li, uint8_t tei, uint8_t sapi)
|
||||||
|
|
||||||
DEBUGP(DMI, "Sending SABM for TEI=%u, SAPI=%u\n", tei, sapi);
|
DEBUGP(DMI, "Sending SABM for TEI=%u, SAPI=%u\n", tei, sapi);
|
||||||
|
|
||||||
/* make sure we know the TEI at the time the response comes in */
|
|
||||||
if (!teip_from_tei(li, tei))
|
|
||||||
lapd_tei_alloc(li, tei);
|
|
||||||
|
|
||||||
msgb_put_u8(msg, (sapi << 2) | (li->network_side ? 2 : 0));
|
msgb_put_u8(msg, (sapi << 2) | (li->network_side ? 2 : 0));
|
||||||
msgb_put_u8(msg, (tei << 1) | 1);
|
msgb_put_u8(msg, (tei << 1) | 1);
|
||||||
msgb_put_u8(msg, 0x7F);
|
msgb_put_u8(msg, 0x7F);
|
||||||
|
@ -501,6 +594,60 @@ int lapd_send_sabm(struct lapd_instance *li, uint8_t tei, uint8_t sapi)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* timer call-back function for SABM re-transmission */
|
||||||
|
static void sabme_timer_cb(void *_sap)
|
||||||
|
{
|
||||||
|
struct lapd_sap *sap = _sap;
|
||||||
|
|
||||||
|
lapd_send_sabm(sap->tei->li, sap->tei->tei, sap->sapi);
|
||||||
|
|
||||||
|
bsc_schedule_timer(&sap->sabme_timer, SABM_INTERVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Start a (user-side) SAP for the specified TEI/SAPI on the LAPD instance */
|
||||||
|
int lapd_sap_start(struct lapd_instance *li, uint8_t tei, uint8_t sapi)
|
||||||
|
{
|
||||||
|
struct lapd_sap *sap;
|
||||||
|
struct lapd_tei *teip;
|
||||||
|
|
||||||
|
teip = teip_from_tei(li, tei);
|
||||||
|
if (!teip)
|
||||||
|
teip = lapd_tei_alloc(li, tei);
|
||||||
|
|
||||||
|
sap = lapd_sap_find(teip, sapi);
|
||||||
|
if (sap)
|
||||||
|
return -EEXIST;
|
||||||
|
|
||||||
|
sap = lapd_sap_alloc(teip, sapi);
|
||||||
|
|
||||||
|
lapd_sap_set_state(teip, sapi, SAP_STATE_SABM_RETRANS);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stop a (user-side) SAP for the specified TEI/SAPI on the LAPD instance */
|
||||||
|
int lapd_sap_stop(struct lapd_instance *li, uint8_t tei, uint8_t sapi)
|
||||||
|
{
|
||||||
|
struct lapd_tei *teip;
|
||||||
|
struct lapd_sap *sap;
|
||||||
|
|
||||||
|
teip = teip_from_tei(li, tei);
|
||||||
|
if (!teip)
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
sap = lapd_sap_find(teip, sapi);
|
||||||
|
if (!sap)
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
bsc_del_timer(&sap->sabme_timer);
|
||||||
|
|
||||||
|
llist_del(&sap->list);
|
||||||
|
talloc_free(sap);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Transmit Data (I-Frame) on the given LAPD Instance / TEI / SAPI */
|
||||||
void lapd_transmit(struct lapd_instance *li, uint8_t tei, uint8_t sapi,
|
void lapd_transmit(struct lapd_instance *li, uint8_t tei, uint8_t sapi,
|
||||||
uint8_t *data, unsigned int len)
|
uint8_t *data, unsigned int len)
|
||||||
{
|
{
|
||||||
|
@ -530,6 +677,7 @@ void lapd_transmit(struct lapd_instance *li, uint8_t tei, uint8_t sapi,
|
||||||
li->transmit_cb(buf, len, li->cbdata);
|
li->transmit_cb(buf, len, li->cbdata);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Allocate a new LAPD instance */
|
||||||
struct lapd_instance *lapd_instance_alloc(int network_side,
|
struct lapd_instance *lapd_instance_alloc(int network_side,
|
||||||
void (*tx_cb)(uint8_t *data, int len,
|
void (*tx_cb)(uint8_t *data, int len,
|
||||||
void *cbdata), void *cbdata)
|
void *cbdata), void *cbdata)
|
||||||
|
|
|
@ -36,6 +36,11 @@ struct lapd_instance *lapd_instance_alloc(int network_side,
|
||||||
void (*tx_cb)(uint8_t *data, int len,
|
void (*tx_cb)(uint8_t *data, int len,
|
||||||
void *cbdata), void *cbdata);
|
void *cbdata), void *cbdata);
|
||||||
|
|
||||||
int lapd_send_sabm(struct lapd_instance *li, uint8_t tei, uint8_t sapi);
|
|
||||||
|
/* Start a (user-side) SAP for the specified TEI/SAPI on the LAPD instance */
|
||||||
|
int lapd_sap_start(struct lapd_instance *li, uint8_t tei, uint8_t sapi);
|
||||||
|
|
||||||
|
/* Stop a (user-side) SAP for the specified TEI/SAPI on the LAPD instance */
|
||||||
|
int lapd_sap_stop(struct lapd_instance *li, uint8_t tei, uint8_t sapi);
|
||||||
|
|
||||||
#endif /* OPENBSC_LAPD_H */
|
#endif /* OPENBSC_LAPD_H */
|
||||||
|
|
Loading…
Reference in New Issue