Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
redox-os
kernel
Commits
78e79fc4
Commit
78e79fc4
authored
Jun 21, 2019
by
Jeremy Soller
Browse files
Merge branch '2018' into 'master'
Switch to 2018 edition See merge request
redox-os/kernel!102
parents
1be77c2a
fe705d9b
Changes
73
Hide whitespace changes
Inline
Side-by-side
Cargo.toml
View file @
78e79fc4
...
...
@@ -2,6 +2,7 @@
name
=
"kernel"
version
=
"0.1.54"
build
=
"build.rs"
edition
=
"2018"
[lib]
name
=
"kernel"
...
...
src/allocator/linked_list.rs
View file @
78e79fc4
...
...
@@ -3,7 +3,7 @@ use core::ptr::NonNull;
use
linked_list_allocator
::
Heap
;
use
spin
::
Mutex
;
use
paging
::
ActivePageTable
;
use
crate
::
paging
::
ActivePageTable
;
static
HEAP
:
Mutex
<
Option
<
Heap
>>
=
Mutex
::
new
(
None
);
...
...
@@ -32,10 +32,10 @@ unsafe impl GlobalAlloc for Allocator {
panic!
(
"__rust_allocate: heap not initialized"
);
};
super
::
map_heap
(
&
mut
ActivePageTable
::
new
(),
::
KERNEL_HEAP_OFFSET
+
size
,
::
KERNEL_HEAP_SIZE
);
super
::
map_heap
(
&
mut
ActivePageTable
::
new
(),
crate
::
KERNEL_HEAP_OFFSET
+
size
,
crate
::
KERNEL_HEAP_SIZE
);
if
let
Some
(
ref
mut
heap
)
=
*
HEAP
.lock
()
{
heap
.extend
(::
KERNEL_HEAP_SIZE
);
heap
.extend
(
crate
::
KERNEL_HEAP_SIZE
);
}
else
{
panic!
(
"__rust_allocate: heap not initialized"
);
}
...
...
src/allocator/mod.rs
View file @
78e79fc4
use
paging
::{
ActivePageTable
,
Page
,
VirtualAddress
};
use
paging
::
entry
::
EntryFlags
;
use
paging
::
mapper
::
MapperFlushAll
;
use
crate
::
paging
::{
ActivePageTable
,
Page
,
VirtualAddress
};
use
crate
::
paging
::
entry
::
EntryFlags
;
use
crate
::
paging
::
mapper
::
MapperFlushAll
;
#[cfg(not(feature=
"slab"
))]
pub
use
self
::
linked_list
::
Allocator
;
...
...
@@ -28,8 +28,8 @@ unsafe fn map_heap(active_table: &mut ActivePageTable, offset: usize, size: usiz
}
pub
unsafe
fn
init
(
active_table
:
&
mut
ActivePageTable
)
{
let
offset
=
::
KERNEL_HEAP_OFFSET
;
let
size
=
::
KERNEL_HEAP_SIZE
;
let
offset
=
crate
::
KERNEL_HEAP_OFFSET
;
let
size
=
crate
::
KERNEL_HEAP_SIZE
;
// Map heap pages
map_heap
(
active_table
,
offset
,
size
);
...
...
src/arch/x86_64/debug.rs
View file @
78e79fc4
...
...
@@ -3,12 +3,12 @@ use core::fmt;
use
spin
::
Mutex
;
use
spin
::
MutexGuard
;
use
log
::{
LOG
,
Log
};
use
crate
::
log
::{
LOG
,
Log
};
#[cfg(feature
=
"qemu_debug"
)]
use
syscall
::
io
::
Io
;
use
syscall
::
io
::
Pio
;
use
crate
::
syscall
::
io
::
Pio
;
#[cfg(feature
=
"serial_debug"
)]
use
devices
::
uart_16550
::
SerialPort
;
use
crate
::
devices
::
uart_16550
::
SerialPort
;
#[cfg(feature
=
"graphical_debug"
)]
use
super
::
graphical_debug
::{
DEBUG_DISPLAY
,
DebugDisplay
};
...
...
src/arch/x86_64/device/local_apic.rs
View file @
78e79fc4
...
...
@@ -2,9 +2,9 @@ use core::intrinsics::{volatile_load, volatile_store};
use
x86
::
shared
::
cpuid
::
CpuId
;
use
x86
::
shared
::
msr
::
*
;
use
memory
::
Frame
;
use
paging
::{
ActivePageTable
,
PhysicalAddress
,
Page
,
VirtualAddress
};
use
paging
::
entry
::
EntryFlags
;
use
crate
::
memory
::
Frame
;
use
crate
::
paging
::{
ActivePageTable
,
PhysicalAddress
,
Page
,
VirtualAddress
};
use
crate
::
paging
::
entry
::
EntryFlags
;
pub
static
mut
LOCAL_APIC
:
LocalApic
=
LocalApic
{
address
:
0
,
...
...
@@ -27,12 +27,12 @@ pub struct LocalApic {
impl
LocalApic
{
unsafe
fn
init
(
&
mut
self
,
active_table
:
&
mut
ActivePageTable
)
{
self
.address
=
(
rdmsr
(
IA32_APIC_BASE
)
as
usize
&
0xFFFF_0000
)
+
::
KERNEL_OFFSET
;
self
.address
=
(
rdmsr
(
IA32_APIC_BASE
)
as
usize
&
0xFFFF_0000
)
+
crate
::
KERNEL_OFFSET
;
self
.x2
=
CpuId
::
new
()
.get_feature_info
()
.unwrap
()
.has_x2apic
();
if
!
self
.x2
{
let
page
=
Page
::
containing_address
(
VirtualAddress
::
new
(
self
.address
));
let
frame
=
Frame
::
containing_address
(
PhysicalAddress
::
new
(
self
.address
-
::
KERNEL_OFFSET
));
let
frame
=
Frame
::
containing_address
(
PhysicalAddress
::
new
(
self
.address
-
crate
::
KERNEL_OFFSET
));
let
result
=
active_table
.map_to
(
page
,
frame
,
EntryFlags
::
PRESENT
|
EntryFlags
::
WRITABLE
|
EntryFlags
::
NO_EXECUTE
);
result
.flush
(
active_table
);
}
...
...
src/arch/x86_64/device/mod.rs
View file @
78e79fc4
use
paging
::
ActivePageTable
;
use
crate
::
paging
::
ActivePageTable
;
pub
mod
cpu
;
pub
mod
local_apic
;
...
...
src/arch/x86_64/device/pic.rs
View file @
78e79fc4
use
syscall
::
io
::{
Io
,
Pio
};
use
crate
::
syscall
::
io
::{
Io
,
Pio
};
pub
static
mut
MASTER
:
Pic
=
Pic
::
new
(
0x20
);
pub
static
mut
SLAVE
:
Pic
=
Pic
::
new
(
0xA0
);
...
...
src/arch/x86_64/device/pit.rs
View file @
78e79fc4
use
syscall
::
io
::{
Io
,
Pio
};
use
crate
::
syscall
::
io
::{
Io
,
Pio
};
pub
static
mut
CHAN0
:
Pio
<
u8
>
=
Pio
::
new
(
0x40
);
pub
static
mut
CHAN1
:
Pio
<
u8
>
=
Pio
::
new
(
0x41
);
...
...
src/arch/x86_64/device/rtc.rs
View file @
78e79fc4
use
syscall
::
io
::{
Io
,
Pio
};
use
time
;
use
crate
::
syscall
::
io
::{
Io
,
Pio
};
use
crate
::
time
;
pub
fn
init
()
{
let
mut
rtc
=
Rtc
::
new
();
...
...
src/arch/x86_64/device/serial.rs
View file @
78e79fc4
use
devices
::
uart_16550
::
SerialPort
;
use
syscall
::
io
::
Pio
;
use
crate
::
devices
::
uart_16550
::
SerialPort
;
use
crate
::
syscall
::
io
::
Pio
;
use
spin
::
Mutex
;
pub
static
COM1
:
Mutex
<
SerialPort
<
Pio
<
u8
>>>
=
Mutex
::
new
(
SerialPort
::
<
Pio
<
u8
>>
::
new
(
0x3F8
));
...
...
src/arch/x86_64/gdt.rs
View file @
78e79fc4
...
...
@@ -8,7 +8,7 @@ use x86::shared::dtables::{self, DescriptorTablePointer};
use
x86
::
shared
::
segmentation
::{
self
,
SegmentDescriptor
,
SegmentSelector
};
use
x86
::
shared
::
task
;
use
paging
::
PAGE_SIZE
;
use
crate
::
paging
::
PAGE_SIZE
;
pub
const
GDT_NULL
:
usize
=
0
;
pub
const
GDT_KERNEL_CODE
:
usize
=
1
;
...
...
@@ -94,7 +94,7 @@ pub static mut TSS: TaskStateSegment = TaskStateSegment {
};
pub
unsafe
fn
set_tcb
(
pid
:
usize
)
{
GDT
[
GDT_USER_TLS
]
.set_offset
((::
USER_TCB_OFFSET
+
pid
*
PAGE_SIZE
)
as
u32
);
GDT
[
GDT_USER_TLS
]
.set_offset
((
crate
::
USER_TCB_OFFSET
+
pid
*
PAGE_SIZE
)
as
u32
);
}
#[cfg(feature
=
"pti"
)]
...
...
src/arch/x86_64/idt.rs
View file @
78e79fc4
...
...
@@ -2,8 +2,8 @@ use core::mem;
use
x86
::
current
::
irq
::
IdtEntry
as
X86IdtEntry
;
use
x86
::
shared
::
dtables
::{
self
,
DescriptorTablePointer
};
use
interrupt
::
*
;
use
ipi
::
IpiKind
;
use
crate
::
interrupt
::
*
;
use
crate
::
ipi
::
IpiKind
;
pub
static
mut
INIT_IDTR
:
DescriptorTablePointer
<
X86IdtEntry
>
=
DescriptorTablePointer
{
limit
:
0
,
...
...
src/arch/x86_64/interrupt/exception.rs
View file @
78e79fc4
use
interrupt
::
stack_trace
;
use
syscall
::
flag
::
*
;
use
crate
::
interrupt
::
stack_trace
;
use
crate
::
syscall
::
flag
::
*
;
extern
{
fn
ksignal
(
signal
:
usize
);
...
...
src/arch/x86_64/interrupt/ipi.rs
View file @
78e79fc4
use
core
::
sync
::
atomic
::
Ordering
;
use
x86
::
shared
::
tlb
;
use
context
;
use
device
::
local_apic
::
LOCAL_APIC
;
use
crate
::
context
;
use
crate
::
device
::
local_apic
::
LOCAL_APIC
;
use
super
::
irq
::
PIT_TICKS
;
interrupt!
(
wakeup
,
{
...
...
src/arch/x86_64/interrupt/irq.rs
View file @
78e79fc4
use
core
::
sync
::
atomic
::{
AtomicUsize
,
Ordering
};
use
context
;
use
context
::
timeout
;
use
device
::
pic
;
use
device
::
serial
::{
COM1
,
COM2
};
use
ipi
::{
ipi
,
IpiKind
,
IpiTarget
};
use
scheme
::
debug
::
debug_input
;
use
time
;
use
crate
::
context
;
use
crate
::
context
::
timeout
;
use
crate
::
device
::
pic
;
use
crate
::
device
::
serial
::{
COM1
,
COM2
};
use
crate
::
ipi
::{
ipi
,
IpiKind
,
IpiTarget
};
use
crate
::
scheme
::
debug
::
debug_input
;
use
crate
::
time
;
//resets to 0 in context::switch()
pub
static
PIT_TICKS
:
AtomicUsize
=
AtomicUsize
::
new
(
0
);
...
...
src/arch/x86_64/interrupt/syscall.rs
View file @
78e79fc4
use
arch
::{
gdt
,
pti
};
use
syscall
;
use
crate
::
arch
::{
gdt
,
pti
};
use
crate
::
syscall
;
use
x86
::
shared
::
msr
;
pub
unsafe
fn
init
()
{
...
...
src/arch/x86_64/interrupt/trace.rs
View file @
78e79fc4
...
...
@@ -2,7 +2,7 @@ use core::{mem, str};
use
goblin
::
elf
::
sym
;
use
rustc_demangle
::
demangle
;
use
paging
::{
ActivePageTable
,
VirtualAddress
};
use
crate
::
paging
::{
ActivePageTable
,
VirtualAddress
};
/// Get a stack trace
//TODO: Check for stack being mapped before dereferencing
...
...
@@ -43,10 +43,10 @@ pub unsafe fn symbol_trace(addr: usize) {
use
core
::
slice
;
use
core
::
sync
::
atomic
::
Ordering
;
use
elf
::
Elf
;
use
start
::{
KERNEL_BASE
,
KERNEL_SIZE
};
use
crate
::
elf
::
Elf
;
use
crate
::
start
::{
KERNEL_BASE
,
KERNEL_SIZE
};
let
kernel_ptr
=
(
KERNEL_BASE
.load
(
Ordering
::
SeqCst
)
+
::
KERNEL_OFFSET
)
as
*
const
u8
;
let
kernel_ptr
=
(
KERNEL_BASE
.load
(
Ordering
::
SeqCst
)
+
crate
::
KERNEL_OFFSET
)
as
*
const
u8
;
let
kernel_slice
=
slice
::
from_raw_parts
(
kernel_ptr
,
KERNEL_SIZE
.load
(
Ordering
::
SeqCst
));
if
let
Ok
(
elf
)
=
Elf
::
from
(
kernel_slice
)
{
let
mut
strtab_opt
=
None
;
...
...
src/arch/x86_64/paging/entry.rs
View file @
78e79fc4
//! # Page table entry
//! Some code borrowed from [Phil Opp's Blog](http://os.phil-opp.com/modifying-page-tables.html)
use
memory
::
Frame
;
use
crate
::
memory
::
Frame
;
use
super
::
PhysicalAddress
;
...
...
src/arch/x86_64/paging/mapper.rs
View file @
78e79fc4
use
core
::
mem
;
use
core
::
ptr
::
Unique
;
use
memory
::{
allocate_frames
,
deallocate_frames
,
Frame
};
use
crate
::
memory
::{
allocate_frames
,
deallocate_frames
,
Frame
};
use
super
::{
ActivePageTable
,
Page
,
PAGE_SIZE
,
PhysicalAddress
,
VirtualAddress
};
use
super
::
entry
::
EntryFlags
;
...
...
src/arch/x86_64/paging/mod.rs
View file @
78e79fc4
...
...
@@ -5,7 +5,7 @@ use core::{mem, ptr};
use
core
::
ops
::{
Deref
,
DerefMut
};
use
x86
::
shared
::{
control_regs
,
msr
,
tlb
};
use
memory
::{
allocate_frames
,
Frame
};
use
crate
::
memory
::{
allocate_frames
,
Frame
};
use
self
::
entry
::
EntryFlags
;
use
self
::
mapper
::
Mapper
;
...
...
@@ -63,7 +63,7 @@ unsafe fn init_tcb(cpu_id: usize) -> usize {
let
size
=
&
__tbss_end
as
*
const
_
as
usize
-
&
__tdata_start
as
*
const
_
as
usize
;
let
tbss_offset
=
&
__tbss_start
as
*
const
_
as
usize
-
&
__tdata_start
as
*
const
_
as
usize
;
let
start
=
::
KERNEL_PERCPU_OFFSET
+
::
KERNEL_PERCPU_SIZE
*
cpu_id
;
let
start
=
crate
::
KERNEL_PERCPU_OFFSET
+
crate
::
KERNEL_PERCPU_SIZE
*
cpu_id
;
let
end
=
start
+
size
;
tcb_offset
=
end
-
mem
::
size_of
::
<
usize
>
();
...
...
@@ -110,7 +110,7 @@ pub unsafe fn init(cpu_id: usize, kernel_start: usize, kernel_end: usize, stack_
let
mut
active_table
=
ActivePageTable
::
new
();
let
mut
temporary_page
=
TemporaryPage
::
new
(
Page
::
containing_address
(
VirtualAddress
::
new
(::
USER_TMP_MISC_OFFSET
)));
let
mut
temporary_page
=
TemporaryPage
::
new
(
Page
::
containing_address
(
VirtualAddress
::
new
(
crate
::
USER_TMP_MISC_OFFSET
)));
let
mut
new_table
=
{
let
frame
=
allocate_frames
(
1
)
.expect
(
"no more frames in paging::init new_table"
);
...
...
@@ -120,10 +120,10 @@ pub unsafe fn init(cpu_id: usize, kernel_start: usize, kernel_end: usize, stack_
active_table
.with
(
&
mut
new_table
,
&
mut
temporary_page
,
|
mapper
|
{
// Remap stack writable, no execute
{
let
start_frame
=
Frame
::
containing_address
(
PhysicalAddress
::
new
(
stack_start
-
::
KERNEL_OFFSET
));
let
end_frame
=
Frame
::
containing_address
(
PhysicalAddress
::
new
(
stack_end
-
::
KERNEL_OFFSET
-
1
));
let
start_frame
=
Frame
::
containing_address
(
PhysicalAddress
::
new
(
stack_start
-
crate
::
KERNEL_OFFSET
));
let
end_frame
=
Frame
::
containing_address
(
PhysicalAddress
::
new
(
stack_end
-
crate
::
KERNEL_OFFSET
-
1
));
for
frame
in
Frame
::
range_inclusive
(
start_frame
,
end_frame
)
{
let
page
=
Page
::
containing_address
(
VirtualAddress
::
new
(
frame
.start_address
()
.get
()
+
::
KERNEL_OFFSET
));
let
page
=
Page
::
containing_address
(
VirtualAddress
::
new
(
frame
.start_address
()
.get
()
+
crate
::
KERNEL_OFFSET
));
let
result
=
mapper
.map_to
(
page
,
frame
,
EntryFlags
::
PRESENT
|
EntryFlags
::
GLOBAL
|
EntryFlags
::
NO_EXECUTE
|
EntryFlags
::
WRITABLE
);
// The flush can be ignored as this is not the active table. See later active_table.switch
/* unsafe */
{
result
.ignore
();
}
...
...
@@ -136,7 +136,7 @@ pub unsafe fn init(cpu_id: usize, kernel_start: usize, kernel_end: usize, stack_
let
end_frame
=
Frame
::
containing_address
(
PhysicalAddress
::
new
(
kernel_end
-
1
));
for
frame
in
Frame
::
range_inclusive
(
start_frame
,
end_frame
)
{
let
phys_addr
=
frame
.start_address
()
.get
();
let
virt_addr
=
phys_addr
+
::
KERNEL_OFFSET
;
let
virt_addr
=
phys_addr
+
crate
::
KERNEL_OFFSET
;
macro_rules!
in_section
{
(
$n
:
ident
)
=>
(
...
...
@@ -176,7 +176,7 @@ pub unsafe fn init(cpu_id: usize, kernel_start: usize, kernel_end: usize, stack_
{
let
size
=
&
__tbss_end
as
*
const
_
as
usize
-
&
__tdata_start
as
*
const
_
as
usize
;
let
start
=
::
KERNEL_PERCPU_OFFSET
+
::
KERNEL_PERCPU_SIZE
*
cpu_id
;
let
start
=
crate
::
KERNEL_PERCPU_OFFSET
+
crate
::
KERNEL_PERCPU_SIZE
*
cpu_id
;
let
end
=
start
+
size
;
let
start_page
=
Page
::
containing_address
(
VirtualAddress
::
new
(
start
));
...
...
@@ -214,14 +214,14 @@ pub unsafe fn init_ap(cpu_id: usize, bsp_table: usize, stack_start: usize, stack
let
mut
new_table
=
InactivePageTable
::
from_address
(
bsp_table
);
let
mut
temporary_page
=
TemporaryPage
::
new
(
Page
::
containing_address
(
VirtualAddress
::
new
(::
USER_TMP_MISC_OFFSET
)));
let
mut
temporary_page
=
TemporaryPage
::
new
(
Page
::
containing_address
(
VirtualAddress
::
new
(
crate
::
USER_TMP_MISC_OFFSET
)));
active_table
.with
(
&
mut
new_table
,
&
mut
temporary_page
,
|
mapper
|
{
// Map tdata and tbss
{
let
size
=
&
__tbss_end
as
*
const
_
as
usize
-
&
__tdata_start
as
*
const
_
as
usize
;
let
start
=
::
KERNEL_PERCPU_OFFSET
+
::
KERNEL_PERCPU_SIZE
*
cpu_id
;
let
start
=
crate
::
KERNEL_PERCPU_OFFSET
+
crate
::
KERNEL_PERCPU_SIZE
*
cpu_id
;
let
end
=
start
+
size
;
let
start_page
=
Page
::
containing_address
(
VirtualAddress
::
new
(
start
));
...
...
@@ -238,7 +238,7 @@ pub unsafe fn init_ap(cpu_id: usize, bsp_table: usize, stack_start: usize, stack
let
start_frame
=
Frame
::
containing_address
(
PhysicalAddress
::
new
(
start
));
let
end_frame
=
Frame
::
containing_address
(
PhysicalAddress
::
new
(
end
-
1
));
for
frame
in
Frame
::
range_inclusive
(
start_frame
,
end_frame
)
{
let
page
=
Page
::
containing_address
(
VirtualAddress
::
new
(
frame
.start_address
()
.get
()
+
::
KERNEL_OFFSET
));
let
page
=
Page
::
containing_address
(
VirtualAddress
::
new
(
frame
.start_address
()
.get
()
+
crate
::
KERNEL_OFFSET
));
let
result
=
mapper
.map_to
(
page
,
frame
,
flags
);
// The flush can be ignored as this is not the active table. See later active_table.switch
result
.ignore
();
...
...
@@ -247,7 +247,7 @@ pub unsafe fn init_ap(cpu_id: usize, bsp_table: usize, stack_start: usize, stack
};
// Remap stack writable, no execute
remap
(
stack_start
-
::
KERNEL_OFFSET
,
stack_end
-
::
KERNEL_OFFSET
,
EntryFlags
::
PRESENT
|
EntryFlags
::
GLOBAL
|
EntryFlags
::
NO_EXECUTE
|
EntryFlags
::
WRITABLE
);
remap
(
stack_start
-
crate
::
KERNEL_OFFSET
,
stack_end
-
crate
::
KERNEL_OFFSET
,
EntryFlags
::
PRESENT
|
EntryFlags
::
GLOBAL
|
EntryFlags
::
NO_EXECUTE
|
EntryFlags
::
WRITABLE
);
});
// This switches the active table, which is setup by the bootloader, to a correct table
...
...
@@ -312,14 +312,14 @@ impl ActivePageTable {
let
p4_table
=
temporary_page
.map_table_frame
(
backup
.clone
(),
EntryFlags
::
PRESENT
|
EntryFlags
::
WRITABLE
|
EntryFlags
::
NO_EXECUTE
,
self
);
// overwrite recursive mapping
self
.p4_mut
()[::
RECURSIVE_PAGE_PML4
]
.set
(
table
.p4_frame
.clone
(),
EntryFlags
::
PRESENT
|
EntryFlags
::
WRITABLE
|
EntryFlags
::
NO_EXECUTE
);
self
.p4_mut
()[
crate
::
RECURSIVE_PAGE_PML4
]
.set
(
table
.p4_frame
.clone
(),
EntryFlags
::
PRESENT
|
EntryFlags
::
WRITABLE
|
EntryFlags
::
NO_EXECUTE
);
self
.flush_all
();
// execute f in the new context
f
(
self
);
// restore recursive mapping to original p4 table
p4_table
[::
RECURSIVE_PAGE_PML4
]
.set
(
backup
,
EntryFlags
::
PRESENT
|
EntryFlags
::
WRITABLE
|
EntryFlags
::
NO_EXECUTE
);
p4_table
[
crate
::
RECURSIVE_PAGE_PML4
]
.set
(
backup
,
EntryFlags
::
PRESENT
|
EntryFlags
::
WRITABLE
|
EntryFlags
::
NO_EXECUTE
);
self
.flush_all
();
}
...
...
@@ -342,7 +342,7 @@ impl InactivePageTable {
// now we are able to zero the table
table
.zero
();
// set up recursive mapping for the table
table
[::
RECURSIVE_PAGE_PML4
]
.set
(
frame
.clone
(),
EntryFlags
::
PRESENT
|
EntryFlags
::
WRITABLE
|
EntryFlags
::
NO_EXECUTE
);
table
[
crate
::
RECURSIVE_PAGE_PML4
]
.set
(
frame
.clone
(),
EntryFlags
::
PRESENT
|
EntryFlags
::
WRITABLE
|
EntryFlags
::
NO_EXECUTE
);
}
temporary_page
.unmap
(
active_table
);
...
...
Prev
1
2
3
4
Next
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment