Run rustfmt
This commit is contained in:
parent
30ca73458b
commit
bd75921035
18
src/gdt.rs
18
src/gdt.rs
|
@ -1,14 +1,14 @@
|
||||||
use x86_64::VirtAddr;
|
|
||||||
use x86_64::instructions::segmentation::Segment;
|
|
||||||
use x86_64::structures::tss::TaskStateSegment;
|
|
||||||
use x86_64::structures::gdt::{GlobalDescriptorTable, Descriptor, SegmentSelector};
|
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
use x86_64::instructions::segmentation::Segment;
|
||||||
|
use x86_64::structures::gdt::{Descriptor, GlobalDescriptorTable, SegmentSelector};
|
||||||
|
use x86_64::structures::tss::TaskStateSegment;
|
||||||
|
use x86_64::VirtAddr;
|
||||||
|
|
||||||
pub const DOUBLE_FAULT_IST_INDEX: u16 = 0;
|
pub const DOUBLE_FAULT_IST_INDEX: u16 = 0;
|
||||||
|
|
||||||
struct Selectors {
|
struct Selectors {
|
||||||
code_selector: SegmentSelector,
|
code_selector: SegmentSelector,
|
||||||
tss_selector: SegmentSelector
|
tss_selector: SegmentSelector,
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
|
@ -31,7 +31,13 @@ lazy_static! {
|
||||||
let mut gdt = GlobalDescriptorTable::new();
|
let mut gdt = GlobalDescriptorTable::new();
|
||||||
let code_selector = gdt.add_entry(Descriptor::kernel_code_segment());
|
let code_selector = gdt.add_entry(Descriptor::kernel_code_segment());
|
||||||
let tss_selector = gdt.add_entry(Descriptor::tss_segment(&TSS));
|
let tss_selector = gdt.add_entry(Descriptor::tss_segment(&TSS));
|
||||||
(gdt, Selectors { code_selector, tss_selector })
|
(
|
||||||
|
gdt,
|
||||||
|
Selectors {
|
||||||
|
code_selector,
|
||||||
|
tss_selector,
|
||||||
|
},
|
||||||
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,24 +1,22 @@
|
||||||
use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame};
|
|
||||||
use crate::println;
|
|
||||||
use crate::print;
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use crate::gdt;
|
use crate::gdt;
|
||||||
|
use crate::print;
|
||||||
|
use crate::println;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
use pic8259::ChainedPics;
|
use pic8259::ChainedPics;
|
||||||
use spin::Mutex;
|
use spin::Mutex;
|
||||||
|
use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame};
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref IDT: InterruptDescriptorTable = {
|
static ref IDT: InterruptDescriptorTable = {
|
||||||
let mut idt = InterruptDescriptorTable::new();
|
let mut idt = InterruptDescriptorTable::new();
|
||||||
idt.breakpoint.set_handler_fn(breakpoint_handler);
|
idt.breakpoint.set_handler_fn(breakpoint_handler);
|
||||||
unsafe {
|
unsafe {
|
||||||
idt.double_fault.set_handler_fn(double_fault_handler)
|
idt.double_fault
|
||||||
|
.set_handler_fn(double_fault_handler)
|
||||||
.set_stack_index(gdt::DOUBLE_FAULT_IST_INDEX);
|
.set_stack_index(gdt::DOUBLE_FAULT_IST_INDEX);
|
||||||
}
|
}
|
||||||
idt[InterruptIndex::Timer.as_usize()]
|
idt[InterruptIndex::Timer.as_usize()].set_handler_fn(timer_interrupt_handler);
|
||||||
.set_handler_fn(timer_interrupt_handler);
|
idt[InterruptIndex::Keyboard.as_usize()].set_handler_fn(keyboard_interrupt_handler);
|
||||||
idt[InterruptIndex::Keyboard.as_usize()]
|
|
||||||
.set_handler_fn(keyboard_interrupt_handler);
|
|
||||||
idt
|
idt
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -33,23 +31,31 @@ extern "x86-interrupt" fn breakpoint_handler(stack_frame: InterruptStackFrame) {
|
||||||
println!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame);
|
println!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "x86-interrupt" fn double_fault_handler(stack_frame: InterruptStackFrame, _error_code: u64) -> ! {
|
extern "x86-interrupt" fn double_fault_handler(
|
||||||
|
stack_frame: InterruptStackFrame,
|
||||||
|
_error_code: u64,
|
||||||
|
) -> ! {
|
||||||
panic!("EXCEPTION : DOUBLE FAULT\n{:#?}", stack_frame);
|
panic!("EXCEPTION : DOUBLE FAULT\n{:#?}", stack_frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "x86-interrupt" fn timer_interrupt_handler(_stack_frame: InterruptStackFrame) {
|
extern "x86-interrupt" fn timer_interrupt_handler(_stack_frame: InterruptStackFrame) {
|
||||||
unsafe {
|
unsafe {
|
||||||
PICS.lock().notify_end_of_interrupt(InterruptIndex::Timer.as_u8());
|
PICS.lock()
|
||||||
|
.notify_end_of_interrupt(InterruptIndex::Timer.as_u8());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: InterruptStackFrame) {
|
extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: InterruptStackFrame) {
|
||||||
|
use pc_keyboard::{layouts, DecodedKey, HandleControl, KeyCode, Keyboard, ScancodeSet1};
|
||||||
use x86_64::instructions::port::Port;
|
use x86_64::instructions::port::Port;
|
||||||
use pc_keyboard::{layouts, DecodedKey, KeyCode, HandleControl, Keyboard, ScancodeSet1};
|
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref KEYBOARD: Mutex<Keyboard<layouts::Us104Key, ScancodeSet1>> = {
|
static ref KEYBOARD: Mutex<Keyboard<layouts::Us104Key, ScancodeSet1>> = {
|
||||||
Mutex::new(Keyboard::new(layouts::Us104Key, ScancodeSet1, HandleControl::Ignore))
|
Mutex::new(Keyboard::new(
|
||||||
|
layouts::Us104Key,
|
||||||
|
ScancodeSet1,
|
||||||
|
HandleControl::Ignore,
|
||||||
|
))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,13 +74,14 @@ extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: InterruptStac
|
||||||
} else if character == '\n' {
|
} else if character == '\n' {
|
||||||
use arrayvec::ArrayString;
|
use arrayvec::ArrayString;
|
||||||
let writer = crate::vga_buffer::WRITER.lock();
|
let writer = crate::vga_buffer::WRITER.lock();
|
||||||
|
|
||||||
// Gather all chars in the current row into one ArrayString
|
// Gather all chars in the current row into one ArrayString
|
||||||
let mut builder = ArrayString::<80>::new();
|
let mut builder = ArrayString::<80>::new();
|
||||||
for character in &writer.buffer.chars[crate::vga_buffer::BUFFER_HEIGHT - 1] {
|
for character in &writer.buffer.chars[crate::vga_buffer::BUFFER_HEIGHT - 1]
|
||||||
|
{
|
||||||
builder.push(character.read().ascii_character as char);
|
builder.push(character.read().ascii_character as char);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We can be sure that we have the writer lock so we can safely call this method
|
// We can be sure that we have the writer lock so we can safely call this method
|
||||||
unsafe {
|
unsafe {
|
||||||
crate::vga_buffer::WRITER.force_unlock();
|
crate::vga_buffer::WRITER.force_unlock();
|
||||||
|
@ -89,20 +96,20 @@ extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: InterruptStac
|
||||||
let row = crate::vga_buffer::BUFFER_HEIGHT - 1;
|
let row = crate::vga_buffer::BUFFER_HEIGHT - 1;
|
||||||
|
|
||||||
crate::vga_buffer::move_cursor(col as u16, row as u16);
|
crate::vga_buffer::move_cursor(col as u16, row as u16);
|
||||||
},
|
}
|
||||||
DecodedKey::RawKey(key) => {
|
DecodedKey::RawKey(key) => {
|
||||||
match key {
|
match key {
|
||||||
KeyCode::ArrowLeft => {
|
KeyCode::ArrowLeft => {
|
||||||
let mut writer = crate::vga_buffer::WRITER.lock();
|
let mut writer = crate::vga_buffer::WRITER.lock();
|
||||||
let col = writer.column_position;
|
let col = writer.column_position;
|
||||||
let row = crate::vga_buffer::BUFFER_HEIGHT - 1;
|
let row = crate::vga_buffer::BUFFER_HEIGHT - 1;
|
||||||
|
|
||||||
// Barrier for the prompt
|
// Barrier for the prompt
|
||||||
if col != 4 {
|
if col != 4 {
|
||||||
crate::vga_buffer::move_cursor((col as u16) - 1, row as u16);
|
crate::vga_buffer::move_cursor((col as u16) - 1, row as u16);
|
||||||
writer.column_position -= 1;
|
writer.column_position -= 1;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
KeyCode::ArrowRight => {
|
KeyCode::ArrowRight => {
|
||||||
let mut writer = crate::vga_buffer::WRITER.lock();
|
let mut writer = crate::vga_buffer::WRITER.lock();
|
||||||
let col = writer.column_position;
|
let col = writer.column_position;
|
||||||
|
@ -111,29 +118,31 @@ extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: InterruptStac
|
||||||
// We don't need a barrier here because if the cursor reaches the end of the line then the VGA buffer stops it automatically
|
// We don't need a barrier here because if the cursor reaches the end of the line then the VGA buffer stops it automatically
|
||||||
crate::vga_buffer::move_cursor((col as u16) + 1, row as u16);
|
crate::vga_buffer::move_cursor((col as u16) + 1, row as u16);
|
||||||
writer.column_position += 1;
|
writer.column_position += 1;
|
||||||
},
|
}
|
||||||
_ => {} // Ignore all other special keys
|
_ => {} // Ignore all other special keys
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
PICS.lock().notify_end_of_interrupt(InterruptIndex::Keyboard.as_u8());
|
PICS.lock()
|
||||||
|
.notify_end_of_interrupt(InterruptIndex::Keyboard.as_u8());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const PIC_1_OFFSET: u8 = 32;
|
pub const PIC_1_OFFSET: u8 = 32;
|
||||||
pub const PIC_2_OFFSET: u8 = PIC_1_OFFSET + 8;
|
pub const PIC_2_OFFSET: u8 = PIC_1_OFFSET + 8;
|
||||||
|
|
||||||
pub static PICS: Mutex<ChainedPics> = Mutex::new(unsafe { ChainedPics::new(PIC_1_OFFSET, PIC_2_OFFSET) });
|
pub static PICS: Mutex<ChainedPics> =
|
||||||
|
Mutex::new(unsafe { ChainedPics::new(PIC_1_OFFSET, PIC_2_OFFSET) });
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum InterruptIndex {
|
pub enum InterruptIndex {
|
||||||
Timer = PIC_1_OFFSET,
|
Timer = PIC_1_OFFSET,
|
||||||
Keyboard
|
Keyboard,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InterruptIndex {
|
impl InterruptIndex {
|
||||||
|
|
|
@ -1,20 +1,17 @@
|
||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
|
|
||||||
#![feature(custom_test_frameworks)]
|
#![feature(custom_test_frameworks)]
|
||||||
#![test_runner(crate::test_runner)]
|
#![test_runner(crate::test_runner)]
|
||||||
#![reexport_test_harness_main = "test_main"]
|
#![reexport_test_harness_main = "test_main"]
|
||||||
|
|
||||||
#![feature(abi_x86_interrupt)]
|
#![feature(abi_x86_interrupt)]
|
||||||
|
|
||||||
mod vga_buffer;
|
|
||||||
mod interrupts;
|
|
||||||
mod gdt;
|
mod gdt;
|
||||||
|
mod interrupts;
|
||||||
mod shell;
|
mod shell;
|
||||||
|
mod vga_buffer;
|
||||||
use core::panic::PanicInfo;
|
use core::panic::PanicInfo;
|
||||||
|
|
||||||
|
|
||||||
#[panic_handler]
|
#[panic_handler]
|
||||||
fn panic(info: &PanicInfo) -> ! {
|
fn panic(info: &PanicInfo) -> ! {
|
||||||
println!("{}", info);
|
println!("{}", info);
|
||||||
|
@ -37,7 +34,6 @@ pub extern "C" fn _start() {
|
||||||
change_color(Color::White, Color::Black);
|
change_color(Color::White, Color::Black);
|
||||||
println!(" ] Initialized GDT and interrupts");
|
println!(" ] Initialized GDT and interrupts");
|
||||||
|
|
||||||
|
|
||||||
print!("Welcome to ");
|
print!("Welcome to ");
|
||||||
change_color(Color::Blue, Color::Black);
|
change_color(Color::Blue, Color::Black);
|
||||||
println!("KarxOS!");
|
println!("KarxOS!");
|
||||||
|
|
13
src/shell.rs
13
src/shell.rs
|
@ -1,8 +1,8 @@
|
||||||
use crate::println;
|
|
||||||
use crate::print;
|
use crate::print;
|
||||||
|
use crate::println;
|
||||||
use crate::vga_buffer::ScreenChar;
|
use crate::vga_buffer::ScreenChar;
|
||||||
use crate::vga_buffer::{change_color, Color};
|
use crate::vga_buffer::{change_color, Color};
|
||||||
use arrayvec::{ArrayVec, ArrayString};
|
use arrayvec::{ArrayString, ArrayVec};
|
||||||
|
|
||||||
pub fn evaluate(command: &str) {
|
pub fn evaluate(command: &str) {
|
||||||
if let Some(stripped) = command.strip_prefix(">>> ") {
|
if let Some(stripped) = command.strip_prefix(">>> ") {
|
||||||
|
@ -16,7 +16,7 @@ pub fn evaluate(command: &str) {
|
||||||
"echo" => echo,
|
"echo" => echo,
|
||||||
"shutdown" => shutdown,
|
"shutdown" => shutdown,
|
||||||
"clear" => clear,
|
"clear" => clear,
|
||||||
_ => default
|
_ => default,
|
||||||
};
|
};
|
||||||
selected(&parts[..]);
|
selected(&parts[..]);
|
||||||
print!(">>> ");
|
print!(">>> ");
|
||||||
|
@ -75,12 +75,15 @@ fn shutdown(_arguments: &[&str]) {
|
||||||
|
|
||||||
fn clear(_arguments: &[&str]) {
|
fn clear(_arguments: &[&str]) {
|
||||||
let mut writer = crate::vga_buffer::WRITER.lock();
|
let mut writer = crate::vga_buffer::WRITER.lock();
|
||||||
|
|
||||||
for row in 0..crate::vga_buffer::BUFFER_HEIGHT {
|
for row in 0..crate::vga_buffer::BUFFER_HEIGHT {
|
||||||
for col in 0..crate::vga_buffer::BUFFER_WIDTH {
|
for col in 0..crate::vga_buffer::BUFFER_WIDTH {
|
||||||
let blank = ScreenChar {
|
let blank = ScreenChar {
|
||||||
ascii_character: b' ',
|
ascii_character: b' ',
|
||||||
color_code: crate::vga_buffer::ColorCode::new(crate::vga_buffer::Color::White, crate::vga_buffer::Color::Black)
|
color_code: crate::vga_buffer::ColorCode::new(
|
||||||
|
crate::vga_buffer::Color::White,
|
||||||
|
crate::vga_buffer::Color::Black,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
writer.buffer.chars[row][col].write(blank);
|
writer.buffer.chars[row][col].write(blank);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use volatile::Volatile;
|
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use spin::Mutex;
|
use spin::Mutex;
|
||||||
|
use volatile::Volatile;
|
||||||
use x86_64::instructions::port::Port;
|
use x86_64::instructions::port::Port;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
@ -23,7 +23,7 @@ pub enum Color {
|
||||||
LightRed = 12,
|
LightRed = 12,
|
||||||
Pink = 13,
|
Pink = 13,
|
||||||
Yellow = 14,
|
Yellow = 14,
|
||||||
White = 15
|
White = 15,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
@ -40,7 +40,7 @@ impl ColorCode {
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct ScreenChar {
|
pub struct ScreenChar {
|
||||||
pub ascii_character: u8,
|
pub ascii_character: u8,
|
||||||
pub(crate) color_code: ColorCode
|
pub(crate) color_code: ColorCode,
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is characters, not pixels
|
// This is characters, not pixels
|
||||||
|
@ -49,16 +49,15 @@ pub(crate) const BUFFER_WIDTH: usize = 80;
|
||||||
|
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub struct Buffer {
|
pub struct Buffer {
|
||||||
pub chars: [[Volatile<ScreenChar>; BUFFER_WIDTH]; BUFFER_HEIGHT] // Use Volatile for futureproofing reads/writes
|
pub chars: [[Volatile<ScreenChar>; BUFFER_WIDTH]; BUFFER_HEIGHT], // Use Volatile for futureproofing reads/writes
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Writer {
|
pub struct Writer {
|
||||||
pub column_position: usize,
|
pub column_position: usize,
|
||||||
color_code: ColorCode,
|
color_code: ColorCode,
|
||||||
pub buffer: &'static mut Buffer
|
pub buffer: &'static mut Buffer,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl Writer {
|
impl Writer {
|
||||||
pub fn write_byte(&mut self, byte: u8) {
|
pub fn write_byte(&mut self, byte: u8) {
|
||||||
match byte {
|
match byte {
|
||||||
|
@ -71,7 +70,7 @@ impl Writer {
|
||||||
let color_code = self.color_code;
|
let color_code = self.color_code;
|
||||||
self.buffer.chars[row][col].write(ScreenChar {
|
self.buffer.chars[row][col].write(ScreenChar {
|
||||||
ascii_character: byte,
|
ascii_character: byte,
|
||||||
color_code
|
color_code,
|
||||||
});
|
});
|
||||||
self.column_position += 1;
|
self.column_position += 1;
|
||||||
}
|
}
|
||||||
|
@ -84,12 +83,11 @@ impl Writer {
|
||||||
match byte {
|
match byte {
|
||||||
// Only write printable ASCII characters
|
// Only write printable ASCII characters
|
||||||
0x20..=0x7e | b'\n' => self.write_byte(byte),
|
0x20..=0x7e | b'\n' => self.write_byte(byte),
|
||||||
_ => self.write_byte(0xfe)
|
_ => self.write_byte(0xfe),
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_line(&mut self) {
|
fn new_line(&mut self) {
|
||||||
// Move all the rows up
|
// Move all the rows up
|
||||||
for row in 1..BUFFER_HEIGHT {
|
for row in 1..BUFFER_HEIGHT {
|
||||||
|
@ -131,7 +129,6 @@ lazy_static! {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! print {
|
macro_rules! print {
|
||||||
($($arg:tt)*) => ($crate::vga_buffer::_print(format_args!($($arg)*)));
|
($($arg:tt)*) => ($crate::vga_buffer::_print(format_args!($($arg)*)));
|
||||||
|
@ -151,9 +148,8 @@ pub fn _print(args: fmt::Arguments) {
|
||||||
// Turn off interrupts to avoid a deadlock
|
// Turn off interrupts to avoid a deadlock
|
||||||
interrupts::without_interrupts(|| {
|
interrupts::without_interrupts(|| {
|
||||||
WRITER.lock().write_fmt(args).unwrap();
|
WRITER.lock().write_fmt(args).unwrap();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn backspace() {
|
pub fn backspace() {
|
||||||
let mut writer = WRITER.lock();
|
let mut writer = WRITER.lock();
|
||||||
|
@ -164,13 +160,12 @@ pub fn backspace() {
|
||||||
if col != 4 {
|
if col != 4 {
|
||||||
writer.buffer.chars[row][col - 1].write(ScreenChar {
|
writer.buffer.chars[row][col - 1].write(ScreenChar {
|
||||||
ascii_character: b' ',
|
ascii_character: b' ',
|
||||||
color_code
|
color_code,
|
||||||
});
|
});
|
||||||
writer.column_position -= 1;
|
writer.column_position -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub struct Cursor {
|
pub struct Cursor {
|
||||||
port_low: Port<u8>,
|
port_low: Port<u8>,
|
||||||
port_high: Port<u8>,
|
port_high: Port<u8>,
|
||||||
|
@ -205,7 +200,7 @@ pub fn move_cursor(x: u16, y: u16) {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn change_color(foreground: Color, background: Color) {
|
pub fn change_color(foreground: Color, background: Color) {
|
||||||
let mut writer = WRITER.lock();
|
let mut writer = WRITER.lock();
|
||||||
let color = ColorCode::new(foreground, background);
|
let color = ColorCode::new(foreground, background);
|
||||||
writer.color_code = color;
|
writer.color_code = color;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue