mirror of https://github.com/PentHertz/srsLTE.git
stable connection. Runs out of samples in ringbuffer if clock differences
This commit is contained in:
parent
db87a41d80
commit
c4c1c62360
|
@ -17,17 +17,19 @@ typedef struct {
|
|||
} srslte_ringbuffer_t;
|
||||
|
||||
|
||||
SRSLTE_API int srslte_ringbuffer_init(srslte_ringbuffer_t *q,
|
||||
SRSLTE_API int srslte_ringbuffer_init(srslte_ringbuffer_t *q,
|
||||
int capacity);
|
||||
|
||||
SRSLTE_API void srslte_ringbuffer_free(srslte_ringbuffer_t *q,
|
||||
int capacity);
|
||||
|
||||
SRSLTE_API int srslte_ringbuffer_write(srslte_ringbuffer_t *q,
|
||||
SRSLTE_API int srslte_ringbuffer_status(srslte_ringbuffer_t *q);
|
||||
|
||||
SRSLTE_API int srslte_ringbuffer_write(srslte_ringbuffer_t *q,
|
||||
void *ptr,
|
||||
int nof_bytes);
|
||||
|
||||
SRSLTE_API int srslte_ringbuffer_read(srslte_ringbuffer_t *q,
|
||||
SRSLTE_API int srslte_ringbuffer_read(srslte_ringbuffer_t *q,
|
||||
void *ptr,
|
||||
int nof_bytes);
|
||||
|
||||
|
|
|
@ -34,6 +34,11 @@ void srslte_ringbuffer_free(srslte_ringbuffer_t *q, int capacity)
|
|||
}
|
||||
}
|
||||
|
||||
int srslte_ringbuffer_status(srslte_ringbuffer_t *q)
|
||||
{
|
||||
return q->count;
|
||||
}
|
||||
|
||||
int srslte_ringbuffer_write(srslte_ringbuffer_t *q, void *p, int nof_bytes)
|
||||
{
|
||||
uint8_t *ptr = (uint8_t*) p;
|
||||
|
|
|
@ -67,7 +67,9 @@ public:
|
|||
|
||||
void set_time_adv_sec(float time_adv_sec);
|
||||
void get_current_cell(srslte_cell_t *cell);
|
||||
|
||||
|
||||
void scell_enable(bool enable);
|
||||
|
||||
const static int MUTEX_X_WORKER = 4;
|
||||
|
||||
int radio_recv_fnc(cf_t *data[SRSLTE_MAX_PORTS], uint32_t nsamples, srslte_timestamp_t *rx_time);
|
||||
|
@ -155,6 +157,7 @@ private:
|
|||
float rsrp();
|
||||
float rsrq();
|
||||
float snr();
|
||||
float cfo();
|
||||
private:
|
||||
srslte::log *log_h;
|
||||
srslte_ue_dl_t ue_dl;
|
||||
|
@ -162,7 +165,7 @@ private:
|
|||
cf_t *buffer[SRSLTE_MAX_PORTS];
|
||||
uint32_t cnt;
|
||||
uint32_t nof_subframes;
|
||||
float mean_rsrp, mean_rsrq, mean_snr;
|
||||
float mean_rsrp, mean_rsrq, mean_snr, mean_cfo;
|
||||
const static int RSRP_MEASURE_NOF_FRAMES = 5;
|
||||
};
|
||||
|
||||
|
@ -172,9 +175,11 @@ private:
|
|||
public:
|
||||
void init(phch_recv *parent, srslte::log *log_h, uint32_t nof_rx_antennas, uint32_t prio, int cpu_affinity = -1);
|
||||
void stop();
|
||||
void reset();
|
||||
int recv(cf_t *data[SRSLTE_MAX_PORTS], uint32_t nsamples, srslte_timestamp_t *rx_time);
|
||||
void write(cf_t *data[SRSLTE_MAX_PORTS], uint32_t nsamples, srslte_timestamp_t *rx_time);
|
||||
bool is_enabled();
|
||||
void set_cell(srslte_cell_t scell);
|
||||
private:
|
||||
void run_thread();
|
||||
|
||||
|
@ -192,6 +197,8 @@ private:
|
|||
cf_t *sf_buffer[SRSLTE_MAX_PORTS];
|
||||
srslte_ue_sync_t ue_sync;
|
||||
srslte_cell_t cell;
|
||||
uint32_t nof_rx_antennas;
|
||||
uint32_t current_sflen;
|
||||
|
||||
measure measure_p;
|
||||
sfn_sync sfn_p;
|
||||
|
|
|
@ -78,6 +78,8 @@ public:
|
|||
void set_earfcn(std::vector<uint32_t> earfcns);
|
||||
void force_freq(float dl_freq, float ul_freq);
|
||||
|
||||
void scell_enable(bool enable);
|
||||
|
||||
/********** RRC INTERFACE ********************/
|
||||
void reset();
|
||||
void sync_reset();
|
||||
|
|
|
@ -80,6 +80,7 @@ public:
|
|||
|
||||
void pregenerate_signals(bool enable);
|
||||
|
||||
void test_scell();
|
||||
|
||||
private:
|
||||
virtual ~ue();
|
||||
|
|
|
@ -387,14 +387,20 @@ int main(int argc, char *argv[])
|
|||
pthread_t input;
|
||||
pthread_create(&input, NULL, &input_loop, &args);
|
||||
|
||||
bool scell_done = false;
|
||||
bool plot_started = false;
|
||||
bool signals_pregenerated = false;
|
||||
|
||||
while (running) {
|
||||
if (ue->is_attached()) {
|
||||
if (!signals_pregenerated && args.expert.pregenerate_signals) {
|
||||
ue->pregenerate_signals(true);
|
||||
signals_pregenerated = true;
|
||||
}
|
||||
if (!scell_done) {
|
||||
((srsue::ue*) ue)->test_scell();
|
||||
scell_done = true;
|
||||
}
|
||||
if (!plot_started && args.gui.enable) {
|
||||
ue->start_plot();
|
||||
plot_started = true;
|
||||
|
|
|
@ -64,7 +64,8 @@ phch_recv::phch_recv() {
|
|||
void phch_recv::init(srslte::radio_multi *_radio_handler, mac_interface_phy *_mac, rrc_interface_phy *_rrc,
|
||||
prach *_prach_buffer, srslte::thread_pool *_workers_pool,
|
||||
phch_common *_worker_com, srslte::log *_log_h, uint32_t nof_rx_antennas_, uint32_t prio,
|
||||
int sync_cpu_affinity) {
|
||||
int sync_cpu_affinity)
|
||||
{
|
||||
radio_h = _radio_handler;
|
||||
log_h = _log_h;
|
||||
mac = _mac;
|
||||
|
@ -96,7 +97,7 @@ void phch_recv::init(srslte::radio_multi *_radio_handler, mac_interface_phy *_ma
|
|||
measure_p.init(&ue_sync, sf_buffer, log_h, nof_rx_antennas);
|
||||
|
||||
// Start scell
|
||||
scell.init(this, log_h, nof_rx_antennas_, prio, sync_cpu_affinity);
|
||||
scell.init(this, log_h, nof_rx_antennas_, prio-1, sync_cpu_affinity);
|
||||
|
||||
reset();
|
||||
|
||||
|
@ -106,7 +107,6 @@ void phch_recv::init(srslte::radio_multi *_radio_handler, mac_interface_phy *_ma
|
|||
} else {
|
||||
start_cpu(prio, sync_cpu_affinity);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
phch_recv::~phch_recv() {
|
||||
|
@ -118,13 +118,15 @@ phch_recv::~phch_recv() {
|
|||
srslte_ue_sync_free(&ue_sync);
|
||||
}
|
||||
|
||||
void phch_recv::stop() {
|
||||
void phch_recv::stop()
|
||||
{
|
||||
|
||||
running = false;
|
||||
wait_thread_finish();
|
||||
}
|
||||
|
||||
void phch_recv::reset() {
|
||||
void phch_recv::reset()
|
||||
{
|
||||
tx_mutex_cnt = 0;
|
||||
running = true;
|
||||
phy_state = IDLE;
|
||||
|
@ -141,7 +143,8 @@ void phch_recv::reset() {
|
|||
|
||||
}
|
||||
|
||||
void phch_recv::radio_error() {
|
||||
void phch_recv::radio_error()
|
||||
{
|
||||
log_h->error("SYNC: Receiving from radio.\n");
|
||||
phy_state = IDLE;
|
||||
radio_is_resetting=true;
|
||||
|
@ -157,7 +160,8 @@ void phch_recv::radio_error() {
|
|||
radio_is_resetting=false;
|
||||
}
|
||||
|
||||
bool phch_recv::wait_radio_reset() {
|
||||
bool phch_recv::wait_radio_reset()
|
||||
{
|
||||
int cnt=0;
|
||||
while(cnt < 20 && radio_is_resetting) {
|
||||
sleep(1);
|
||||
|
@ -166,11 +170,13 @@ bool phch_recv::wait_radio_reset() {
|
|||
return radio_is_resetting;
|
||||
}
|
||||
|
||||
void phch_recv::set_agc_enable(bool enable) {
|
||||
void phch_recv::set_agc_enable(bool enable)
|
||||
{
|
||||
do_agc = enable;
|
||||
}
|
||||
|
||||
void phch_recv::set_time_adv_sec(float _time_adv_sec) {
|
||||
void phch_recv::set_time_adv_sec(float _time_adv_sec)
|
||||
{
|
||||
if (TX_MODE_CONTINUOUS && !radio_h->is_first_of_burst()) {
|
||||
int nsamples = ceil(current_srate*_time_adv_sec);
|
||||
next_offset = -nsamples;
|
||||
|
@ -179,7 +185,8 @@ void phch_recv::set_time_adv_sec(float _time_adv_sec) {
|
|||
}
|
||||
}
|
||||
|
||||
void phch_recv::set_ue_sync_opts(srslte_ue_sync_t *q) {
|
||||
void phch_recv::set_ue_sync_opts(srslte_ue_sync_t *q)
|
||||
{
|
||||
if (worker_com->args->cfo_integer_enabled) {
|
||||
srslte_ue_sync_cfo_i_detec_en(q, true);
|
||||
}
|
||||
|
@ -473,9 +480,12 @@ int phch_recv::radio_recv_fnc(cf_t *data[SRSLTE_MAX_PORTS], uint32_t nsamples, s
|
|||
next_offset = nsamples;
|
||||
}
|
||||
|
||||
if (offset == 0) {
|
||||
if (offset <= 0) {
|
||||
scell.write(data, nsamples, rx_time);
|
||||
}
|
||||
|
||||
log_h->debug("SYNC: received %d samples from radio\n", nsamples);
|
||||
|
||||
return nsamples;
|
||||
} else {
|
||||
return -1;
|
||||
|
@ -487,7 +497,13 @@ int phch_recv::scell_recv_fnc(cf_t *data[SRSLTE_MAX_PORTS], uint32_t nsamples, s
|
|||
return scell.recv(data, nsamples, rx_time);
|
||||
}
|
||||
|
||||
|
||||
void phch_recv::scell_enable(bool enable)
|
||||
{
|
||||
srslte_cell_t target_cell;
|
||||
memcpy(&target_cell, &cell, sizeof(srslte_cell_t));
|
||||
target_cell.id++;
|
||||
scell.set_cell(target_cell);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -512,7 +528,6 @@ void phch_recv::run_thread()
|
|||
Debug("SYNC: state=%d\n", phy_state);
|
||||
}
|
||||
|
||||
tti = (tti+1) % 10240;
|
||||
log_h->step(tti);
|
||||
sf_idx = tti%10;
|
||||
|
||||
|
@ -635,7 +650,6 @@ void phch_recv::run_thread()
|
|||
rrc->out_of_sync();
|
||||
worker->release();
|
||||
worker_com->reset_ul();
|
||||
mac->tti_clock(tti);
|
||||
break;
|
||||
default:
|
||||
radio_error();
|
||||
|
@ -652,11 +666,12 @@ void phch_recv::run_thread()
|
|||
}
|
||||
is_in_idle = true;
|
||||
usleep(1000);
|
||||
// Keep running MAC timer from system clock
|
||||
tti = (tti+1) % 10240;
|
||||
mac->tti_clock(tti);
|
||||
break;
|
||||
}
|
||||
|
||||
// Increase TTI counter and trigger MAC clock (lower priority)
|
||||
tti = (tti+1) % 10240;
|
||||
mac->tti_clock(tti);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -860,6 +875,7 @@ bool phch_recv::sfn_sync::set_cell(srslte_cell_t cell)
|
|||
Error("SYNC: Setting cell: initiating ue_mib\n");
|
||||
return false;
|
||||
}
|
||||
reset();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -952,7 +968,8 @@ void phch_recv::measure::reset() {
|
|||
cnt = 0;
|
||||
mean_rsrp = 0;
|
||||
mean_rsrq = 0;
|
||||
mean_snr = 0;
|
||||
mean_snr = 0;
|
||||
mean_cfo = 0;
|
||||
}
|
||||
|
||||
void phch_recv::measure::set_cell(srslte_cell_t cell)
|
||||
|
@ -960,6 +977,7 @@ void phch_recv::measure::set_cell(srslte_cell_t cell)
|
|||
if (srslte_ue_dl_set_cell(&ue_dl, cell)) {
|
||||
Error("SYNC: Setting cell: initiating ue_dl_measure\n");
|
||||
}
|
||||
reset();
|
||||
}
|
||||
|
||||
float phch_recv::measure::rsrp() {
|
||||
|
@ -974,6 +992,10 @@ float phch_recv::measure::snr() {
|
|||
return 10*log10(mean_snr);
|
||||
}
|
||||
|
||||
float phch_recv::measure::cfo() {
|
||||
return mean_cfo;
|
||||
}
|
||||
|
||||
phch_recv::measure::ret_code phch_recv::measure::run_subframe(uint32_t sf_idx)
|
||||
{
|
||||
int sync_res = srslte_ue_sync_zerocopy_multi(ue_sync, buffer);
|
||||
|
@ -988,10 +1010,12 @@ phch_recv::measure::ret_code phch_recv::measure::run_subframe(uint32_t sf_idx)
|
|||
float rsrp = srslte_chest_dl_get_rsrp(&ue_dl.chest);
|
||||
float rsrq = srslte_chest_dl_get_rsrq(&ue_dl.chest);
|
||||
float snr = srslte_chest_dl_get_snr(&ue_dl.chest);
|
||||
float cfo = srslte_ue_sync_get_cfo(ue_sync);
|
||||
|
||||
mean_rsrp = SRSLTE_VEC_CMA(rsrp, mean_rsrp, cnt);
|
||||
mean_rsrq = SRSLTE_VEC_CMA(rsrq, mean_rsrq, cnt);
|
||||
mean_snr = SRSLTE_VEC_CMA(rsrq, mean_snr, cnt);
|
||||
mean_snr = SRSLTE_VEC_CMA(snr, mean_snr, cnt);
|
||||
mean_cfo = SRSLTE_VEC_CMA(cfo, mean_cfo, cnt);
|
||||
cnt++;
|
||||
|
||||
log_h->info("SYNC: Measuring RSRP %d/%d, sf_idx=%d, RSRP=%.1f dBm, SNR=%.1f dB\n",
|
||||
|
@ -1022,19 +1046,20 @@ phch_recv::measure::ret_code phch_recv::measure::run_subframe(uint32_t sf_idx)
|
|||
|
||||
void phch_recv::scell_recv::init(phch_recv *parent, srslte::log *log_h, uint32_t nof_rx_antennas, uint32_t prio, int cpu_affinity)
|
||||
{
|
||||
this->p = parent;
|
||||
this->log_h = log_h;
|
||||
this->p = parent;
|
||||
this->log_h = log_h;
|
||||
this->nof_rx_antennas = nof_rx_antennas;
|
||||
|
||||
// Create the ringbuffer for secondary cell reception
|
||||
for (int i=0;i<SRSLTE_MAX_PORTS;i++) {
|
||||
if (srslte_ringbuffer_init(&ring_buffer[i], 5*SRSLTE_SF_LEN_PRB(SRSLTE_MAX_PRB))) {
|
||||
Error("SYNC: Creating ringbuffer for SCell\n");
|
||||
if (srslte_ringbuffer_init(&ring_buffer[i], 10*SRSLTE_SF_LEN_PRB(SRSLTE_MAX_PRB))) {
|
||||
Error("SCELL: Creating ringbuffer for SCell\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
// and a separate ue_sync instance
|
||||
if (srslte_ue_sync_init_multi(&ue_sync, SRSLTE_MAX_PRB, false, scell_recv_callback, nof_rx_antennas, this)) {
|
||||
Error("SYNC: Initiating ue_sync\n");
|
||||
if (srslte_ue_sync_init_multi(&ue_sync, SRSLTE_MAX_PRB, false, scell_recv_callback, nof_rx_antennas, parent)) {
|
||||
Error("SCELL: Initiating ue_sync\n");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1043,6 +1068,9 @@ void phch_recv::scell_recv::init(phch_recv *parent, srslte::log *log_h, uint32_t
|
|||
}
|
||||
|
||||
measure_p.init(&ue_sync, sf_buffer, log_h, nof_rx_antennas);
|
||||
sfn_p.init(&ue_sync, sf_buffer, log_h);
|
||||
|
||||
reset();
|
||||
|
||||
running = true;
|
||||
if (cpu_affinity < 0) {
|
||||
|
@ -1058,6 +1086,26 @@ void phch_recv::scell_recv::stop()
|
|||
wait_thread_finish();
|
||||
}
|
||||
|
||||
void phch_recv::scell_recv::reset()
|
||||
{
|
||||
tti = 0;
|
||||
measure_p.reset();
|
||||
sfn_p.reset();
|
||||
scell_state = IDLE;
|
||||
}
|
||||
|
||||
void phch_recv::scell_recv::set_cell(srslte_cell_t scell) {
|
||||
printf("SCELL: set scell to select, id=%d, prb=%d\n", scell.id, scell.nof_prb);
|
||||
|
||||
memcpy(&cell, &scell, sizeof(srslte_cell_t));
|
||||
current_sflen = SRSLTE_SF_LEN_PRB(cell.nof_prb);
|
||||
srslte_ue_sync_set_cell(&ue_sync, scell);
|
||||
measure_p.set_cell(scell);
|
||||
sfn_p.set_cell(scell);
|
||||
|
||||
scell_state = SCELL_SELECT;
|
||||
}
|
||||
|
||||
bool phch_recv::scell_recv::is_enabled()
|
||||
{
|
||||
return scell_state != IDLE;
|
||||
|
@ -1065,13 +1113,37 @@ bool phch_recv::scell_recv::is_enabled()
|
|||
|
||||
int phch_recv::scell_recv::recv(cf_t *data[SRSLTE_MAX_PORTS], uint32_t nsamples, srslte_timestamp_t *rx_time)
|
||||
{
|
||||
if (is_enabled()) {
|
||||
for (int i=0;i<SRSLTE_MAX_PORTS;i++) {
|
||||
srslte_ringbuffer_read(&ring_buffer[i], data[i], sizeof(cf_t)*nsamples);
|
||||
if (is_enabled())
|
||||
{
|
||||
uint32_t read_samples = nsamples;
|
||||
if (read_samples > current_sflen) {
|
||||
read_samples = current_sflen;
|
||||
}
|
||||
if (nsamples < 10) {
|
||||
read_samples = 0;
|
||||
}
|
||||
int n = 0;
|
||||
for (uint32_t i=0;i<nof_rx_antennas;i++) {
|
||||
n = srslte_ringbuffer_read(&ring_buffer[i], data[i], sizeof(cf_t)*read_samples);
|
||||
if (n < 0) {
|
||||
Error("SCELL: Receiving from SCell buffer\n");
|
||||
return -1;
|
||||
}
|
||||
if ((uint32_t) n < read_samples*sizeof(cf_t)) {
|
||||
Error("SCELL: SCell received %d<%d samples from port %d\n", n/sizeof(cf_t), read_samples, i);
|
||||
return -1;
|
||||
}
|
||||
// Pad with zeros if requested more samples in order to avoid consuming the buffer
|
||||
for (int j=read_samples;j<nsamples;j++) {
|
||||
data[i][j] = 0;
|
||||
}
|
||||
}
|
||||
log_h->debug("SCELL: tti=%d, read %d/%d samples from buffer, buffer size=%d\n",
|
||||
tti, read_samples,nsamples, srslte_ringbuffer_status(&ring_buffer[0]));
|
||||
|
||||
return nsamples;
|
||||
} else {
|
||||
Error("SYNC: SCell reception not enabled\n");
|
||||
Error("SCELL: Reception not enabled\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
@ -1079,7 +1151,7 @@ int phch_recv::scell_recv::recv(cf_t *data[SRSLTE_MAX_PORTS], uint32_t nsamples,
|
|||
void phch_recv::scell_recv::write(cf_t *data[SRSLTE_MAX_PORTS], uint32_t nsamples, srslte_timestamp_t *rx_time)
|
||||
{
|
||||
if (is_enabled()) {
|
||||
for (int i = 0; i < SRSLTE_MAX_PORTS; i++) {
|
||||
for (uint32_t i = 0; i < nof_rx_antennas; i++) {
|
||||
srslte_ringbuffer_write(&ring_buffer[i], data[i], sizeof(cf_t) * nsamples);
|
||||
}
|
||||
}
|
||||
|
@ -1090,16 +1162,19 @@ void phch_recv::scell_recv::run_thread()
|
|||
while(running) {
|
||||
switch(scell_state) {
|
||||
case IDLE:
|
||||
usleep(1000);
|
||||
break;
|
||||
case SCELL_SELECT:
|
||||
|
||||
switch (sfn_p.run_subframe(&cell, &tti))
|
||||
{
|
||||
case sfn_sync::SFN_FOUND:
|
||||
log_h->info("SFN Sync OK. Camping on cell PCI=%d...\n", cell.id);
|
||||
log_h->info("SCELL: SFN Sync OK. Camping on cell PCI=%d...\n", cell.id);
|
||||
sfn_p.reset();
|
||||
scell_state = SCELL_MEASURE;
|
||||
break;
|
||||
case sfn_sync::TIMEOUT:
|
||||
log_h->info("SFN sync timeout\n");
|
||||
log_h->info("SCELL: SFN sync timeout\n");
|
||||
break;
|
||||
case sfn_sync::IDLE:
|
||||
break;
|
||||
|
@ -1107,11 +1182,15 @@ void phch_recv::scell_recv::run_thread()
|
|||
p->radio_error();
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case SCELL_MEASURE:
|
||||
switch (measure_p.run_subframe(tti%10)) {
|
||||
case measure::MEASURE_OK:
|
||||
log_h->info("SYNC: Measured OK\n");
|
||||
log_h->info("SCELL: Measured OK TTI=%5d, RSRP=%.1f dBm, RSRQ=%.1f dB, SNR=%3.1f dB, CFO=%.1f KHz, Buff=%d\n",
|
||||
tti, measure_p.rsrp(), measure_p.rsrq(), measure_p.snr(), measure_p.cfo()/1000,
|
||||
srslte_ringbuffer_status(&ring_buffer[0]));
|
||||
measure_p.reset();
|
||||
break;
|
||||
case measure::IDLE:
|
||||
break;
|
||||
|
@ -1121,6 +1200,8 @@ void phch_recv::scell_recv::run_thread()
|
|||
}
|
||||
break;
|
||||
}
|
||||
// Increase TTI counter
|
||||
tti = (tti+1) % 10240;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -243,6 +243,11 @@ void phy::cell_search_next()
|
|||
sf_recv.cell_search_next();
|
||||
}
|
||||
|
||||
void phy::scell_enable(bool enable)
|
||||
{
|
||||
sf_recv.scell_enable(enable);
|
||||
}
|
||||
|
||||
void phy::sync_reset() {
|
||||
sf_recv.reset_sync();
|
||||
}
|
||||
|
|
|
@ -213,6 +213,11 @@ void ue::pregenerate_signals(bool enable)
|
|||
phy.enable_pregen_signals(enable);
|
||||
}
|
||||
|
||||
void ue::test_scell()
|
||||
{
|
||||
phy.scell_enable(true);
|
||||
};
|
||||
|
||||
void ue::stop()
|
||||
{
|
||||
if(started)
|
||||
|
|
Loading…
Reference in New Issue