mirror of
https://github.com/eried/portapack-mayhem.git
synced 2024-10-01 01:26:06 -04:00
Hide baseband queue code inside baseband "API".
This commit is contained in:
parent
49a89b9dee
commit
22e44605b6
@ -127,6 +127,7 @@ CPPSRC = main.cpp \
|
|||||||
hackrf_hal.cpp \
|
hackrf_hal.cpp \
|
||||||
portapack.cpp \
|
portapack.cpp \
|
||||||
portapack_shared_memory.cpp \
|
portapack_shared_memory.cpp \
|
||||||
|
baseband_api.cpp \
|
||||||
portapack_persistent_memory.cpp \
|
portapack_persistent_memory.cpp \
|
||||||
portapack_io.cpp \
|
portapack_io.cpp \
|
||||||
i2c_pp.cpp \
|
i2c_pp.cpp \
|
||||||
|
@ -25,8 +25,7 @@
|
|||||||
|
|
||||||
#include "string_format.hpp"
|
#include "string_format.hpp"
|
||||||
|
|
||||||
#include "portapack_shared_memory.hpp"
|
#include "baseband_api.hpp"
|
||||||
using namespace portapack;
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
@ -352,12 +351,11 @@ AISAppView::AISAppView(NavigationView&) {
|
|||||||
1,
|
1,
|
||||||
});
|
});
|
||||||
|
|
||||||
BasebandConfigurationMessage message { {
|
baseband::start({
|
||||||
.mode = 3,
|
.mode = 3,
|
||||||
.sampling_rate = sampling_rate,
|
.sampling_rate = sampling_rate,
|
||||||
.decimation_factor = 1,
|
.decimation_factor = 1,
|
||||||
} };
|
});
|
||||||
shared_memory.baseband_queue.push(message);
|
|
||||||
|
|
||||||
options_channel.on_change = [this](size_t, OptionsField::value_t v) {
|
options_channel.on_change = [this](size_t, OptionsField::value_t v) {
|
||||||
this->on_frequency_changed(v);
|
this->on_frequency_changed(v);
|
||||||
@ -373,11 +371,7 @@ AISAppView::AISAppView(NavigationView&) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
AISAppView::~AISAppView() {
|
AISAppView::~AISAppView() {
|
||||||
shared_memory.baseband_queue.push_and_wait(
|
baseband::stop();
|
||||||
BasebandConfigurationMessage {
|
|
||||||
.configuration = { },
|
|
||||||
}
|
|
||||||
);
|
|
||||||
radio::disable();
|
radio::disable();
|
||||||
|
|
||||||
EventDispatcher::message_map().unregister_handler(Message::ID::AISPacket);
|
EventDispatcher::message_map().unregister_handler(Message::ID::AISPacket);
|
||||||
|
105
firmware/application/baseband_api.cpp
Normal file
105
firmware/application/baseband_api.cpp
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc.
|
||||||
|
*
|
||||||
|
* This file is part of PortaPack.
|
||||||
|
*
|
||||||
|
* 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, 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; see the file COPYING. If not, write to
|
||||||
|
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||||
|
* Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "baseband_api.hpp"
|
||||||
|
|
||||||
|
#include "audio.hpp"
|
||||||
|
#include "dsp_iir_config.hpp"
|
||||||
|
|
||||||
|
#include "portapack_shared_memory.hpp"
|
||||||
|
|
||||||
|
namespace baseband {
|
||||||
|
|
||||||
|
void AMConfig::apply() const {
|
||||||
|
const AMConfigureMessage message {
|
||||||
|
taps_6k0_decim_0,
|
||||||
|
taps_6k0_decim_1,
|
||||||
|
taps_6k0_decim_2,
|
||||||
|
channel,
|
||||||
|
modulation,
|
||||||
|
audio_12k_hpf_300hz_config
|
||||||
|
};
|
||||||
|
shared_memory.baseband_queue.push(message);
|
||||||
|
audio::set_rate(audio::Rate::Hz_12000);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NBFMConfig::apply() const {
|
||||||
|
const NBFMConfigureMessage message {
|
||||||
|
decim_0,
|
||||||
|
decim_1,
|
||||||
|
channel,
|
||||||
|
2,
|
||||||
|
deviation,
|
||||||
|
audio_24k_hpf_300hz_config,
|
||||||
|
audio_24k_deemph_300_6_config
|
||||||
|
};
|
||||||
|
shared_memory.baseband_queue.push(message);
|
||||||
|
audio::set_rate(audio::Rate::Hz_24000);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WFMConfig::apply() const {
|
||||||
|
const WFMConfigureMessage message {
|
||||||
|
taps_200k_wfm_decim_0,
|
||||||
|
taps_200k_wfm_decim_1,
|
||||||
|
taps_64_lp_156_198,
|
||||||
|
75000,
|
||||||
|
audio_48k_hpf_30hz_config,
|
||||||
|
audio_48k_deemph_2122_6_config
|
||||||
|
};
|
||||||
|
shared_memory.baseband_queue.push(message);
|
||||||
|
audio::set_rate(audio::Rate::Hz_48000);
|
||||||
|
}
|
||||||
|
|
||||||
|
void start(BasebandConfiguration configuration) {
|
||||||
|
BasebandConfigurationMessage message { configuration };
|
||||||
|
shared_memory.baseband_queue.push(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void stop() {
|
||||||
|
shared_memory.baseband_queue.push_and_wait(
|
||||||
|
BasebandConfigurationMessage {
|
||||||
|
.configuration = { },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void shutdown() {
|
||||||
|
ShutdownMessage shutdown_message;
|
||||||
|
shared_memory.baseband_queue.push(shutdown_message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void spectrum_streaming_start() {
|
||||||
|
shared_memory.baseband_queue.push_and_wait(
|
||||||
|
SpectrumStreamingConfigMessage {
|
||||||
|
SpectrumStreamingConfigMessage::Mode::Running
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void spectrum_streaming_stop() {
|
||||||
|
shared_memory.baseband_queue.push_and_wait(
|
||||||
|
SpectrumStreamingConfigMessage {
|
||||||
|
SpectrumStreamingConfigMessage::Mode::Stopped
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
} /* namespace baseband */
|
63
firmware/application/baseband_api.hpp
Normal file
63
firmware/application/baseband_api.hpp
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc.
|
||||||
|
*
|
||||||
|
* This file is part of PortaPack.
|
||||||
|
*
|
||||||
|
* 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, 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; see the file COPYING. If not, write to
|
||||||
|
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||||
|
* Boston, MA 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __BASEBAND_API_H__
|
||||||
|
#define __BASEBAND_API_H__
|
||||||
|
|
||||||
|
#include "message.hpp"
|
||||||
|
|
||||||
|
#include "dsp_fir_taps.hpp"
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
namespace baseband {
|
||||||
|
|
||||||
|
struct AMConfig {
|
||||||
|
const fir_taps_complex<64> channel;
|
||||||
|
const AMConfigureMessage::Modulation modulation;
|
||||||
|
|
||||||
|
void apply() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct NBFMConfig {
|
||||||
|
const fir_taps_real<24> decim_0;
|
||||||
|
const fir_taps_real<32> decim_1;
|
||||||
|
const fir_taps_real<32> channel;
|
||||||
|
const size_t deviation;
|
||||||
|
|
||||||
|
void apply() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct WFMConfig {
|
||||||
|
void apply() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
void start(BasebandConfiguration configuration);
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
void spectrum_streaming_start();
|
||||||
|
void spectrum_streaming_stop();
|
||||||
|
|
||||||
|
} /* namespace baseband */
|
||||||
|
|
||||||
|
#endif/*__BASEBAND_API_H__*/
|
@ -23,8 +23,7 @@
|
|||||||
|
|
||||||
#include "event_m0.hpp"
|
#include "event_m0.hpp"
|
||||||
|
|
||||||
#include "portapack_shared_memory.hpp"
|
#include "baseband_api.hpp"
|
||||||
using namespace portapack;
|
|
||||||
|
|
||||||
#include "manchester.hpp"
|
#include "manchester.hpp"
|
||||||
|
|
||||||
@ -140,20 +139,15 @@ ERTAppView::ERTAppView(NavigationView&) {
|
|||||||
1,
|
1,
|
||||||
});
|
});
|
||||||
|
|
||||||
BasebandConfigurationMessage message { {
|
baseband::start({
|
||||||
.mode = 6,
|
.mode = 6,
|
||||||
.sampling_rate = sampling_rate,
|
.sampling_rate = sampling_rate,
|
||||||
.decimation_factor = 1,
|
.decimation_factor = 1,
|
||||||
} };
|
});
|
||||||
shared_memory.baseband_queue.push(message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ERTAppView::~ERTAppView() {
|
ERTAppView::~ERTAppView() {
|
||||||
shared_memory.baseband_queue.push_and_wait(
|
baseband::stop();
|
||||||
BasebandConfigurationMessage {
|
|
||||||
.configuration = { },
|
|
||||||
}
|
|
||||||
);
|
|
||||||
radio::disable();
|
radio::disable();
|
||||||
|
|
||||||
EventDispatcher::message_map().unregister_handler(Message::ID::ERTPacket);
|
EventDispatcher::message_map().unregister_handler(Message::ID::ERTPacket);
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
#include "hal.h"
|
#include "hal.h"
|
||||||
|
|
||||||
#include "message.hpp"
|
#include "message.hpp"
|
||||||
#include "portapack_shared_memory.hpp"
|
#include "baseband_api.hpp"
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
@ -49,6 +49,5 @@ void m4_init(const portapack::spi_flash::region_t from, const portapack::memory:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void m4_request_shutdown() {
|
void m4_request_shutdown() {
|
||||||
ShutdownMessage shutdown_message;
|
baseband::shutdown();
|
||||||
shared_memory.baseband_queue.push(shutdown_message);
|
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,8 @@
|
|||||||
|
|
||||||
#include "receiver_model.hpp"
|
#include "receiver_model.hpp"
|
||||||
|
|
||||||
#include "portapack_shared_memory.hpp"
|
#include "baseband_api.hpp"
|
||||||
|
|
||||||
#include "portapack_persistent_memory.hpp"
|
#include "portapack_persistent_memory.hpp"
|
||||||
using namespace portapack;
|
using namespace portapack;
|
||||||
|
|
||||||
@ -34,79 +35,19 @@ using namespace portapack;
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
struct AMConfig {
|
static constexpr std::array<baseband::AMConfig, 3> am_configs { {
|
||||||
const fir_taps_complex<64> channel;
|
|
||||||
const AMConfigureMessage::Modulation modulation;
|
|
||||||
|
|
||||||
void apply() const;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct NBFMConfig {
|
|
||||||
const fir_taps_real<24> decim_0;
|
|
||||||
const fir_taps_real<32> decim_1;
|
|
||||||
const fir_taps_real<32> channel;
|
|
||||||
const size_t deviation;
|
|
||||||
|
|
||||||
void apply() const;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct WFMConfig {
|
|
||||||
void apply() const;
|
|
||||||
};
|
|
||||||
|
|
||||||
void AMConfig::apply() const {
|
|
||||||
const AMConfigureMessage message {
|
|
||||||
taps_6k0_decim_0,
|
|
||||||
taps_6k0_decim_1,
|
|
||||||
taps_6k0_decim_2,
|
|
||||||
channel,
|
|
||||||
modulation,
|
|
||||||
audio_12k_hpf_300hz_config
|
|
||||||
};
|
|
||||||
shared_memory.baseband_queue.push(message);
|
|
||||||
audio::set_rate(audio::Rate::Hz_12000);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NBFMConfig::apply() const {
|
|
||||||
const NBFMConfigureMessage message {
|
|
||||||
decim_0,
|
|
||||||
decim_1,
|
|
||||||
channel,
|
|
||||||
2,
|
|
||||||
deviation,
|
|
||||||
audio_24k_hpf_300hz_config,
|
|
||||||
audio_24k_deemph_300_6_config
|
|
||||||
};
|
|
||||||
shared_memory.baseband_queue.push(message);
|
|
||||||
audio::set_rate(audio::Rate::Hz_24000);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WFMConfig::apply() const {
|
|
||||||
const WFMConfigureMessage message {
|
|
||||||
taps_200k_wfm_decim_0,
|
|
||||||
taps_200k_wfm_decim_1,
|
|
||||||
taps_64_lp_156_198,
|
|
||||||
75000,
|
|
||||||
audio_48k_hpf_30hz_config,
|
|
||||||
audio_48k_deemph_2122_6_config
|
|
||||||
};
|
|
||||||
shared_memory.baseband_queue.push(message);
|
|
||||||
audio::set_rate(audio::Rate::Hz_48000);
|
|
||||||
}
|
|
||||||
|
|
||||||
static constexpr std::array<AMConfig, 3> am_configs { {
|
|
||||||
{ taps_6k0_dsb_channel, AMConfigureMessage::Modulation::DSB },
|
{ taps_6k0_dsb_channel, AMConfigureMessage::Modulation::DSB },
|
||||||
{ taps_2k8_usb_channel, AMConfigureMessage::Modulation::SSB },
|
{ taps_2k8_usb_channel, AMConfigureMessage::Modulation::SSB },
|
||||||
{ taps_2k8_lsb_channel, AMConfigureMessage::Modulation::SSB },
|
{ taps_2k8_lsb_channel, AMConfigureMessage::Modulation::SSB },
|
||||||
} };
|
} };
|
||||||
|
|
||||||
static constexpr std::array<NBFMConfig, 3> nbfm_configs { {
|
static constexpr std::array<baseband::NBFMConfig, 3> nbfm_configs { {
|
||||||
{ taps_4k25_decim_0, taps_4k25_decim_1, taps_4k25_channel, 2500 },
|
{ taps_4k25_decim_0, taps_4k25_decim_1, taps_4k25_channel, 2500 },
|
||||||
{ taps_11k0_decim_0, taps_11k0_decim_1, taps_11k0_channel, 2500 },
|
{ taps_11k0_decim_0, taps_11k0_decim_1, taps_11k0_channel, 2500 },
|
||||||
{ taps_16k0_decim_0, taps_16k0_decim_1, taps_16k0_channel, 5000 },
|
{ taps_16k0_decim_0, taps_16k0_decim_1, taps_16k0_channel, 5000 },
|
||||||
} };
|
} };
|
||||||
|
|
||||||
static constexpr std::array<WFMConfig, 1> wfm_configs { {
|
static constexpr std::array<baseband::WFMConfig, 1> wfm_configs { {
|
||||||
{ },
|
{ },
|
||||||
} };
|
} };
|
||||||
|
|
||||||
@ -210,18 +151,10 @@ void ReceiverModel::enable() {
|
|||||||
update_headphone_volume();
|
update_headphone_volume();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ReceiverModel::baseband_disable() {
|
|
||||||
shared_memory.baseband_queue.push_and_wait(
|
|
||||||
BasebandConfigurationMessage {
|
|
||||||
.configuration = { },
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ReceiverModel::disable() {
|
void ReceiverModel::disable() {
|
||||||
enabled_ = false;
|
enabled_ = false;
|
||||||
update_antenna_bias();
|
update_antenna_bias();
|
||||||
baseband_disable();
|
baseband::stop();
|
||||||
|
|
||||||
// TODO: Responsibility for enabling/disabling the radio is muddy.
|
// TODO: Responsibility for enabling/disabling the radio is muddy.
|
||||||
// Some happens in ReceiverModel, some inside radio namespace.
|
// Some happens in ReceiverModel, some inside radio namespace.
|
||||||
@ -292,14 +225,13 @@ void ReceiverModel::update_baseband_configuration() {
|
|||||||
// protocols that need quick RX/TX turn-around.
|
// protocols that need quick RX/TX turn-around.
|
||||||
|
|
||||||
// Disabling baseband while changing sampling rates seems like a good idea...
|
// Disabling baseband while changing sampling rates seems like a good idea...
|
||||||
baseband_disable();
|
baseband::stop();
|
||||||
|
|
||||||
radio::set_baseband_rate(sampling_rate() * baseband_oversampling());
|
radio::set_baseband_rate(sampling_rate() * baseband_oversampling());
|
||||||
update_tuning_frequency();
|
update_tuning_frequency();
|
||||||
radio::set_baseband_decimation_by(baseband_oversampling());
|
radio::set_baseband_decimation_by(baseband_oversampling());
|
||||||
|
|
||||||
BasebandConfigurationMessage message { baseband_configuration };
|
baseband::start(baseband_configuration);
|
||||||
shared_memory.baseband_queue.push(message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ReceiverModel::update_headphone_volume() {
|
void ReceiverModel::update_headphone_volume() {
|
||||||
|
@ -116,8 +116,6 @@ private:
|
|||||||
void update_am_configuration();
|
void update_am_configuration();
|
||||||
void update_nbfm_configuration();
|
void update_nbfm_configuration();
|
||||||
void update_wfm_configuration();
|
void update_wfm_configuration();
|
||||||
|
|
||||||
void baseband_disable();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif/*__RECEIVER_MODEL_H__*/
|
#endif/*__RECEIVER_MODEL_H__*/
|
||||||
|
@ -23,8 +23,7 @@
|
|||||||
|
|
||||||
#include "event_m0.hpp"
|
#include "event_m0.hpp"
|
||||||
|
|
||||||
#include "portapack_shared_memory.hpp"
|
#include "baseband_api.hpp"
|
||||||
using namespace portapack;
|
|
||||||
|
|
||||||
#include "string_format.hpp"
|
#include "string_format.hpp"
|
||||||
|
|
||||||
@ -247,20 +246,15 @@ TPMSAppView::TPMSAppView(NavigationView&) {
|
|||||||
1,
|
1,
|
||||||
});
|
});
|
||||||
|
|
||||||
BasebandConfigurationMessage message { {
|
baseband::start({
|
||||||
.mode = 5,
|
.mode = 5,
|
||||||
.sampling_rate = sampling_rate,
|
.sampling_rate = sampling_rate,
|
||||||
.decimation_factor = 1,
|
.decimation_factor = 1,
|
||||||
} };
|
});
|
||||||
shared_memory.baseband_queue.push(message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TPMSAppView::~TPMSAppView() {
|
TPMSAppView::~TPMSAppView() {
|
||||||
shared_memory.baseband_queue.push_and_wait(
|
baseband::stop();
|
||||||
BasebandConfigurationMessage {
|
|
||||||
.configuration = { },
|
|
||||||
}
|
|
||||||
);
|
|
||||||
radio::disable();
|
radio::disable();
|
||||||
|
|
||||||
EventDispatcher::message_map().unregister_handler(Message::ID::TPMSPacket);
|
EventDispatcher::message_map().unregister_handler(Message::ID::TPMSPacket);
|
||||||
|
@ -26,9 +26,10 @@
|
|||||||
#include "spectrum_color_lut.hpp"
|
#include "spectrum_color_lut.hpp"
|
||||||
|
|
||||||
#include "portapack.hpp"
|
#include "portapack.hpp"
|
||||||
#include "portapack_shared_memory.hpp"
|
|
||||||
using namespace portapack;
|
using namespace portapack;
|
||||||
|
|
||||||
|
#include "baseband_api.hpp"
|
||||||
|
|
||||||
#include "string_format.hpp"
|
#include "string_format.hpp"
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
@ -251,19 +252,11 @@ void WaterfallWidget::on_show() {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
shared_memory.baseband_queue.push_and_wait(
|
baseband::spectrum_streaming_start();
|
||||||
SpectrumStreamingConfigMessage {
|
|
||||||
SpectrumStreamingConfigMessage::Mode::Running
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WaterfallWidget::on_hide() {
|
void WaterfallWidget::on_hide() {
|
||||||
shared_memory.baseband_queue.push_and_wait(
|
baseband::spectrum_streaming_stop();
|
||||||
SpectrumStreamingConfigMessage {
|
|
||||||
SpectrumStreamingConfigMessage::Mode::Stopped
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
EventDispatcher::message_map().unregister_handler(Message::ID::DisplayFrameSync);
|
EventDispatcher::message_map().unregister_handler(Message::ID::DisplayFrameSync);
|
||||||
EventDispatcher::message_map().unregister_handler(Message::ID::ChannelSpectrumConfig);
|
EventDispatcher::message_map().unregister_handler(Message::ID::ChannelSpectrumConfig);
|
||||||
|
Loading…
Reference in New Issue
Block a user