Commit 48822524 authored by Anton Blanchard's avatar Anton Blanchard

rework kernel stack usage

remove old ioctls
parent efe80e09
...@@ -42,10 +42,19 @@ show_syscalls_task: ...@@ -42,10 +42,19 @@ show_syscalls_task:
.long -1 .long -1
#endif #endif
.section ".toc","aw"
.SYS_CALL_TABLE:
.tc .sys_call_table[TC],.sys_call_table
.SYS_CALL_TABLE32:
.tc .sys_call_table32[TC],.sys_call_table32
.section ".text"
.align 3
/* /*
* Handle a system call. * Handle a system call.
*/ */
.text
_GLOBAL(DoSyscall) _GLOBAL(DoSyscall)
std r0,THREAD+LAST_SYSCALL(r13) std r0,THREAD+LAST_SYSCALL(r13)
ld r11,_CCR(r1) /* Clear SO bit in CR */ ld r11,_CCR(r1) /* Clear SO bit in CR */
...@@ -94,7 +103,7 @@ _GLOBAL(DoSyscall) ...@@ -94,7 +103,7 @@ _GLOBAL(DoSyscall)
#ifdef CONFIG_BINFMT_ELF32 #ifdef CONFIG_BINFMT_ELF32
andi. r11,r10,_TIF_32BIT andi. r11,r10,_TIF_32BIT
beq- 15f beq- 15f
LOADADDR(r10,.sys_call_table32) ld r10,.SYS_CALL_TABLE32@toc(2)
/* /*
* Now mung the first 4 parameters into shape, by making certain that * Now mung the first 4 parameters into shape, by making certain that
* the high bits (most significant 32 bits in 64 bit reg) are 0 * the high bits (most significant 32 bits in 64 bit reg) are 0
...@@ -111,7 +120,7 @@ _GLOBAL(DoSyscall) ...@@ -111,7 +120,7 @@ _GLOBAL(DoSyscall)
b 17f b 17f
15: 15:
#endif #endif
LOADADDR(r10,.sys_call_table) ld r10,.SYS_CALL_TABLE@toc(2)
17: slwi r0,r0,3 17: slwi r0,r0,3
ldx r10,r10,r0 /* Fetch system call handler [ptr] */ ldx r10,r10,r0 /* Fetch system call handler [ptr] */
mtlr r10 mtlr r10
...@@ -167,7 +176,7 @@ _GLOBAL(ret_from_syscall_1) ...@@ -167,7 +176,7 @@ _GLOBAL(ret_from_syscall_1)
ld r10,TI_FLAGS(r10) ld r10,TI_FLAGS(r10)
andi. r11,r10,_TIF_32BIT andi. r11,r10,_TIF_32BIT
beq- 55f beq- 55f
LOADADDR(r10,.sys_call_table32) ld r10,.SYS_CALL_TABLE32@toc(2)
/* /*
* Now mung the first 4 parameters into shape, by making certain that * Now mung the first 4 parameters into shape, by making certain that
* the high bits (most significant 32 bits in 64 bit reg) are 0 * the high bits (most significant 32 bits in 64 bit reg) are 0
...@@ -184,7 +193,7 @@ _GLOBAL(ret_from_syscall_1) ...@@ -184,7 +193,7 @@ _GLOBAL(ret_from_syscall_1)
b 57f b 57f
55: 55:
#endif #endif
LOADADDR(r10,.sys_call_table) ld r10,.SYS_CALL_TABLE@toc(2)
57: 57:
slwi r0,r0,3 slwi r0,r0,3
ldx r10,r10,r0 /* Fetch system call handler [ptr] */ ldx r10,r10,r0 /* Fetch system call handler [ptr] */
...@@ -255,38 +264,33 @@ _GLOBAL(ppc64_rt_sigreturn) ...@@ -255,38 +264,33 @@ _GLOBAL(ppc64_rt_sigreturn)
* Note: there are two ways to get to the "going out" portion * Note: there are two ways to get to the "going out" portion
* of this code; either by coming in via the entry (_switch) * of this code; either by coming in via the entry (_switch)
* or via "fork" which must set up an environment equivalent * or via "fork" which must set up an environment equivalent
* to the "_switch" path. If you change this (or in particular, the * to the "_switch" path. If you change this you'll have to change
* SAVE_REGS macro), you'll have to change the fork code also. * the fork code also.
* *
* The code which creates the new task context is in 'copy_thread' * The code which creates the new task context is in 'copy_thread'
* in arch/ppc/kernel/process.c * in arch/ppc64/kernel/process.c
*/ */
_GLOBAL(_switch) _GLOBAL(_switch)
stdu r1,-INT_FRAME_SIZE(r1) mflr r0
ld r6,0(r1) std r0,16(r1)
std r6,GPR1(r1) stdu r1,-SWITCH_FRAME_SIZE(r1)
/* r3-r13 are caller saved -- Cort */ /* r3-r13 are caller saved -- Cort */
SAVE_GPR(2, r1)
SAVE_8GPRS(14, r1) SAVE_8GPRS(14, r1)
SAVE_10GPRS(22, r1) SAVE_10GPRS(22, r1)
mflr r20 /* Return to switch caller */ mflr r20 /* Return to switch caller */
mfmsr r22 mfmsr r22
andi. r21, r22, MSR_FP
beq+ 1f
li r6,MSR_FP /* Disable floating-point */ li r6,MSR_FP /* Disable floating-point */
andc r22,r22,r6 andc r22,r22,r6
mtmsrd r22 mtmsrd r22
isync isync
std r20,_NIP(r1) 1: std r20,_NIP(r1)
std r22,_MSR(r1) mfcr r23
std r20,_LINK(r1) std r23,_CCR(r1)
mfcr r20
std r20,_CCR(r1)
li r6,0x0ff0
std r6,TRAP(r1)
std r1,KSP(r3) /* Set old stack pointer */ std r1,KSP(r3) /* Set old stack pointer */
mfspr r5,SPRG3 /* Get Paca */ mfspr r5,SPRG3 /* Get Paca */
/* XXX remove - Anton */
addi r3,r3,-THREAD /* old 'current' for return value */
addi r13,r4,-THREAD /* Convert THREAD to 'current' */ addi r13,r4,-THREAD /* Convert THREAD to 'current' */
std r13,PACACURRENT(r5) /* Set new 'current' */ std r13,PACACURRENT(r5) /* Set new 'current' */
...@@ -298,6 +302,7 @@ _GLOBAL(_switch) ...@@ -298,6 +302,7 @@ _GLOBAL(_switch)
insrdi r9,r7,1,63 /* Insert run light into CTRL */ insrdi r9,r7,1,63 /* Insert run light into CTRL */
mtspr CTRLT,r9 mtspr CTRLT,r9
#endif #endif
ld r1,KSP(r4) /* Load new stack pointer */ ld r1,KSP(r4) /* Load new stack pointer */
ld r6,_CCR(r1) ld r6,_CCR(r1)
mtcrf 0xFF,r6 mtcrf 0xFF,r6
...@@ -306,8 +311,8 @@ _GLOBAL(_switch) ...@@ -306,8 +311,8 @@ _GLOBAL(_switch)
REST_10GPRS(22, r1) REST_10GPRS(22, r1)
ld r7,_NIP(r1) /* Return to _switch caller in new task */ ld r7,_NIP(r1) /* Return to _switch caller in new task */
ld r1,GPR1(r1)
mtlr r7 mtlr r7
addi r1,r1,SWITCH_FRAME_SIZE
blr blr
_GLOBAL(ret_from_fork) _GLOBAL(ret_from_fork)
...@@ -377,6 +382,7 @@ restore: ...@@ -377,6 +382,7 @@ restore:
mtlr r0 mtlr r0
ld r3,_XER(r1) ld r3,_XER(r1)
mtspr XER,r3 mtspr XER,r3
REST_8GPRS(5, r1) REST_8GPRS(5, r1)
REST_10GPRS(14, r1) REST_10GPRS(14, r1)
REST_8GPRS(24, r1) REST_8GPRS(24, r1)
......
...@@ -3737,8 +3737,6 @@ COMPATIBLE_IOCTL(BLKROSET), ...@@ -3737,8 +3737,6 @@ COMPATIBLE_IOCTL(BLKROSET),
COMPATIBLE_IOCTL(BLKROGET), COMPATIBLE_IOCTL(BLKROGET),
COMPATIBLE_IOCTL(BLKRRPART), COMPATIBLE_IOCTL(BLKRRPART),
COMPATIBLE_IOCTL(BLKFLSBUF), COMPATIBLE_IOCTL(BLKFLSBUF),
COMPATIBLE_IOCTL(BLKRASET),
COMPATIBLE_IOCTL(BLKFRASET),
COMPATIBLE_IOCTL(BLKSECTSET), COMPATIBLE_IOCTL(BLKSECTSET),
COMPATIBLE_IOCTL(BLKSSZGET), COMPATIBLE_IOCTL(BLKSSZGET),
COMPATIBLE_IOCTL(BLKBSZGET), COMPATIBLE_IOCTL(BLKBSZGET),
...@@ -4311,10 +4309,8 @@ HANDLE_IOCTL(SIOCDELRT, routing_ioctl), ...@@ -4311,10 +4309,8 @@ HANDLE_IOCTL(SIOCDELRT, routing_ioctl),
HANDLE_IOCTL(SIOCRTMSG, ret_einval), HANDLE_IOCTL(SIOCRTMSG, ret_einval),
HANDLE_IOCTL(SIOCGSTAMP, do_siocgstamp), HANDLE_IOCTL(SIOCGSTAMP, do_siocgstamp),
HANDLE_IOCTL(HDIO_GETGEO, hdio_getgeo), HANDLE_IOCTL(HDIO_GETGEO, hdio_getgeo),
HANDLE_IOCTL(BLKRAGET, w_long),
HANDLE_IOCTL(BLKGETSIZE, w_long), HANDLE_IOCTL(BLKGETSIZE, w_long),
HANDLE_IOCTL(0x1260, broken_blkgetsize), HANDLE_IOCTL(0x1260, broken_blkgetsize),
HANDLE_IOCTL(BLKFRAGET, w_long),
HANDLE_IOCTL(BLKSECTGET, w_long), HANDLE_IOCTL(BLKSECTGET, w_long),
HANDLE_IOCTL(BLKPG, blkpg_ioctl_trans), HANDLE_IOCTL(BLKPG, blkpg_ioctl_trans),
HANDLE_IOCTL(HDIO_GET_KEEPSETTINGS, hdio_ioctl_trans), HANDLE_IOCTL(HDIO_GET_KEEPSETTINGS, hdio_ioctl_trans),
......
...@@ -496,25 +496,15 @@ _GLOBAL(cvt_df) ...@@ -496,25 +496,15 @@ _GLOBAL(cvt_df)
* kernel_thread(fn, arg, flags) * kernel_thread(fn, arg, flags)
*/ */
_GLOBAL(kernel_thread) _GLOBAL(kernel_thread)
/* XXX fix this when we optimise syscall entry to not save volatiles */
mr r6,r3 /* function */ mr r6,r3 /* function */
ori r3,r5,CLONE_VM /* flags */ ori r3,r5,CLONE_VM /* flags */
li r0,__NR_clone li r0,__NR_clone
sc sc
cmpi 0,r3,0 /* parent or child? */ cmpi 0,r3,0 /* parent or child? */
bnelr /* return if parent */ bnelr /* return if parent */
li r0,0
li r0,0 /* clear out p->thread.regs */ stdu r0,-STACK_FRAME_OVERHEAD(r1)
std r0,THREAD+PT_REGS(r13) /* since we don't have user ctx */
clrrdi r5,r1,THREAD_SHIFT
ld r0,TI_FLAGS(r5)
li r7,_TIF_32BIT
andc r0,r0,r7
#ifdef CONFIG_PPC_ISERIES
ori r0,r0,_TIF_RUN_LIGHT /* Run light on */
#endif
std r0,TI_FLAGS(r5)
ld r2,8(r6) ld r2,8(r6)
ld r6,0(r6) ld r6,0(r6)
mtlr r6 /* fn addr in lr */ mtlr r6 /* fn addr in lr */
......
...@@ -115,12 +115,14 @@ main(void) ...@@ -115,12 +115,14 @@ main(void)
/* Interrupt register frame */ /* Interrupt register frame */
DEFINE(STACK_FRAME_OVERHEAD, STACK_FRAME_OVERHEAD); DEFINE(STACK_FRAME_OVERHEAD, STACK_FRAME_OVERHEAD);
DEFINE(SWITCH_FRAME_SIZE, STACK_FRAME_OVERHEAD + sizeof(struct pt_regs));
/* 288 = # of volatile regs, int & fp, for leaf routines */ /* 288 = # of volatile regs, int & fp, for leaf routines */
/* which do not stack a frame. See the PPC64 ABI. */ /* which do not stack a frame. See the PPC64 ABI. */
DEFINE(INT_FRAME_SIZE, STACK_FRAME_OVERHEAD + sizeof(struct pt_regs) + 288); DEFINE(INT_FRAME_SIZE, STACK_FRAME_OVERHEAD + sizeof(struct pt_regs) + 288);
/* Create extra stack space for SRR0 and SRR1 when calling prom/rtas. */ /* Create extra stack space for SRR0 and SRR1 when calling prom/rtas. */
DEFINE(PROM_FRAME_SIZE, STACK_FRAME_OVERHEAD + sizeof(struct pt_regs) + 16 + 288); DEFINE(PROM_FRAME_SIZE, STACK_FRAME_OVERHEAD + sizeof(struct pt_regs) + 16);
DEFINE(RTAS_FRAME_SIZE, STACK_FRAME_OVERHEAD + sizeof(struct pt_regs) + 16 + 288); DEFINE(RTAS_FRAME_SIZE, STACK_FRAME_OVERHEAD + sizeof(struct pt_regs) + 16);
DEFINE(GPR0, STACK_FRAME_OVERHEAD+offsetof(struct pt_regs, gpr[0])); DEFINE(GPR0, STACK_FRAME_OVERHEAD+offsetof(struct pt_regs, gpr[0]));
DEFINE(GPR1, STACK_FRAME_OVERHEAD+offsetof(struct pt_regs, gpr[1])); DEFINE(GPR1, STACK_FRAME_OVERHEAD+offsetof(struct pt_regs, gpr[1]));
DEFINE(GPR2, STACK_FRAME_OVERHEAD+offsetof(struct pt_regs, gpr[2])); DEFINE(GPR2, STACK_FRAME_OVERHEAD+offsetof(struct pt_regs, gpr[2]));
......
/* /*
* linux/arch/ppc/kernel/process.c * linux/arch/ppc64/kernel/process.c
* *
* Derived from "arch/i386/kernel/process.c" * Derived from "arch/i386/kernel/process.c"
* Copyright (C) 1995 Linus Torvalds * Copyright (C) 1995 Linus Torvalds
...@@ -45,8 +45,9 @@ ...@@ -45,8 +45,9 @@
#include <asm/iSeries/HvCallHpt.h> #include <asm/iSeries/HvCallHpt.h>
#include <asm/Naca.h> #include <asm/Naca.h>
#include "ppc_defs.h"
int dump_fpu(struct pt_regs *regs, elf_fpregset_t *fpregs); int dump_fpu(struct pt_regs *regs, elf_fpregset_t *fpregs);
extern unsigned long _get_SP(void);
struct task_struct *last_task_used_math = NULL; struct task_struct *last_task_used_math = NULL;
...@@ -56,8 +57,6 @@ struct mm_struct ioremap_mm = { pgd : ioremap_dir ...@@ -56,8 +57,6 @@ struct mm_struct ioremap_mm = { pgd : ioremap_dir
char *sysmap = NULL; char *sysmap = NULL;
unsigned long sysmap_size = 0; unsigned long sysmap_size = 0;
extern char __toc_start;
void void
enable_kernel_fp(void) enable_kernel_fp(void)
{ {
...@@ -84,10 +83,7 @@ void ...@@ -84,10 +83,7 @@ void
_switch_to(struct task_struct *prev, struct task_struct *new) _switch_to(struct task_struct *prev, struct task_struct *new)
{ {
struct thread_struct *new_thread, *old_thread; struct thread_struct *new_thread, *old_thread;
unsigned long s; unsigned long flags;
__save_flags(s);
__cli();
#ifdef CONFIG_SMP #ifdef CONFIG_SMP
/* avoid complexity of lazy save/restore of fpu /* avoid complexity of lazy save/restore of fpu
...@@ -99,14 +95,16 @@ _switch_to(struct task_struct *prev, struct task_struct *new) ...@@ -99,14 +95,16 @@ _switch_to(struct task_struct *prev, struct task_struct *new)
* every switch, just a save. * every switch, just a save.
* -- Cort * -- Cort
*/ */
if ( prev->thread.regs && (prev->thread.regs->msr & MSR_FP) ) if (prev->thread.regs && (prev->thread.regs->msr & MSR_FP))
giveup_fpu(prev); giveup_fpu(prev);
#endif /* CONFIG_SMP */ #endif /* CONFIG_SMP */
new_thread = &new->thread; new_thread = &new->thread;
old_thread = &current->thread; old_thread = &current->thread;
__save_and_cli(flags);
_switch(old_thread, new_thread); _switch(old_thread, new_thread);
__restore_flags(s); __restore_flags(flags);
} }
void show_regs(struct pt_regs * regs) void show_regs(struct pt_regs * regs)
...@@ -172,45 +170,50 @@ release_thread(struct task_struct *t) ...@@ -172,45 +170,50 @@ release_thread(struct task_struct *t)
int int
copy_thread(int nr, unsigned long clone_flags, unsigned long usp, copy_thread(int nr, unsigned long clone_flags, unsigned long usp,
unsigned long unused, unsigned long unused,
struct task_struct * p, struct pt_regs * regs) struct task_struct *p, struct pt_regs *regs)
{ {
unsigned long msr;
struct pt_regs *childregs, *kregs; struct pt_regs *childregs, *kregs;
extern void ret_from_fork(void); extern void ret_from_fork(void);
unsigned long sp = (unsigned long)p->thread_info + THREAD_SIZE;
/* XXX get rid of the -2 Anton */
/* Copy registers */ /* Copy registers */
childregs = ((struct pt_regs *) sp -= sizeof(struct pt_regs);
((unsigned long)p->thread_info + THREAD_SIZE childregs = (struct pt_regs *) sp;
- STACK_FRAME_OVERHEAD)) - 2;
*childregs = *regs; *childregs = *regs;
childregs->gpr[3] = 0; /* Result from fork() */ if ((childregs->msr & MSR_PR) == 0) {
/* for kernel thread, set `current' and stackptr in new task */
childregs->gpr[1] = sp + sizeof(struct pt_regs);
childregs->gpr[13] = (unsigned long) p;
p->thread.regs = NULL; /* no user register state */
clear_ti_thread_flag(p->thread_info, TIF_32BIT);
#ifdef CONFIG_PPC_ISERIES
set_ti_thread_flag(p->thread_info, TIF_RUN_LIGHT);
#endif
} else
p->thread.regs = childregs; p->thread.regs = childregs;
p->thread.ksp = (unsigned long) childregs - STACK_FRAME_OVERHEAD; childregs->gpr[3] = 0; /* Result from fork() */
p->thread.ksp -= sizeof(struct pt_regs ) + STACK_FRAME_OVERHEAD; sp -= STACK_FRAME_OVERHEAD;
kregs = (struct pt_regs *)(p->thread.ksp + STACK_FRAME_OVERHEAD);
/* The PPC64 compiler makes use of a TOC to contain function /*
* The way this works is that at some point in the future
* some task will call _switch to switch to the new task.
* That will pop off the stack frame created below and start
* the new task running at ret_from_fork. The new task will
* do some house keeping and then return from the fork or clone
* system call, using the stack frame created above.
*/
sp -= sizeof(struct pt_regs);
kregs = (struct pt_regs *) sp;
sp -= STACK_FRAME_OVERHEAD;
p->thread.ksp = sp;
/*
* The PPC64 ABI makes use of a TOC to contain function
* pointers. The function (ret_from_except) is actually a pointer * pointers. The function (ret_from_except) is actually a pointer
* to the TOC entry. The first entry is a pointer to the actual * to the TOC entry. The first entry is a pointer to the actual
* function. * function.
*/ */
kregs->nip = *((unsigned long *)ret_from_fork); kregs->nip = *((unsigned long *)ret_from_fork);
asm volatile("mfmsr %0" : "=r" (msr):);
kregs->msr = msr;
kregs->gpr[1] = (unsigned long)childregs - STACK_FRAME_OVERHEAD;
kregs->gpr[2] = (((unsigned long)&__toc_start) + 0x8000);
if (usp >= (unsigned long) regs) {
/* Stack is in kernel space - must adjust */
childregs->gpr[1] = (unsigned long)(childregs + 1);
*((unsigned long *) childregs->gpr[1]) = 0;
childregs->gpr[13] = (unsigned long) p;
} else {
/* Provided stack is in user space */
childregs->gpr[1] = usp;
}
p->thread.last_syscall = -1;
/* /*
* copy fpu info - assume lazy fpu switch now always * copy fpu info - assume lazy fpu switch now always
...@@ -223,6 +226,8 @@ copy_thread(int nr, unsigned long clone_flags, unsigned long usp, ...@@ -223,6 +226,8 @@ copy_thread(int nr, unsigned long clone_flags, unsigned long usp,
memcpy(&p->thread.fpr, &current->thread.fpr, sizeof(p->thread.fpr)); memcpy(&p->thread.fpr, &current->thread.fpr, sizeof(p->thread.fpr));
p->thread.fpscr = current->thread.fpscr; p->thread.fpscr = current->thread.fpscr;
p->thread.last_syscall = -1;
return 0; return 0;
} }
...@@ -239,7 +244,6 @@ void start_thread(struct pt_regs *regs, unsigned long nip, unsigned long sp) ...@@ -239,7 +244,6 @@ void start_thread(struct pt_regs *regs, unsigned long nip, unsigned long sp)
unsigned long *entry = (unsigned long *)nip; unsigned long *entry = (unsigned long *)nip;
unsigned long *toc = entry + 1; unsigned long *toc = entry + 1;
set_fs(USER_DS); set_fs(USER_DS);
memset(regs->gpr, 0, sizeof(regs->gpr)); memset(regs->gpr, 0, sizeof(regs->gpr));
memset(&regs->ctr, 0, 4 * sizeof(regs->ctr)); memset(&regs->ctr, 0, 4 * sizeof(regs->ctr));
...@@ -252,51 +256,25 @@ void start_thread(struct pt_regs *regs, unsigned long nip, unsigned long sp) ...@@ -252,51 +256,25 @@ void start_thread(struct pt_regs *regs, unsigned long nip, unsigned long sp)
current->thread.fpscr = 0; current->thread.fpscr = 0;
} }
asmlinkage int sys_clone(int p1, int p2, int p3, int p4, int p5, int p6, int sys_clone(int p1, int p2, int p3, int p4, int p5, int p6,
struct pt_regs *regs) struct pt_regs *regs)
{ {
unsigned long clone_flags = p1; return do_fork(p1, regs->gpr[1], regs, 0);
int res;
res = do_fork(clone_flags, regs->gpr[1], regs, 0);
#ifdef CONFIG_SMP
/* When we clone the idle task we keep the same pid but
* the return value of 0 for both causes problems.
* -- Cort
*/
if ((current->pid == 0) && (current == &init_task))
res = 1;
#endif /* CONFIG_SMP */
return res;
} }
asmlinkage int sys_fork(int p1, int p2, int p3, int p4, int p5, int p6, int sys_fork(int p1, int p2, int p3, int p4, int p5, int p6,
struct pt_regs *regs) struct pt_regs *regs)
{ {
int res; return do_fork(SIGCHLD, regs->gpr[1], regs, 0);
res = do_fork(SIGCHLD, regs->gpr[1], regs, 0);
#ifdef CONFIG_SMP
/* When we clone the idle task we keep the same pid but
* the return value of 0 for both causes problems.
* -- Cort
*/
if ((current->pid == 0) && (current == &init_task))
res = 1;
#endif /* CONFIG_SMP */
return res;
} }
asmlinkage int sys_vfork(int p1, int p2, int p3, int p4, int p5, int p6, int sys_vfork(int p1, int p2, int p3, int p4, int p5, int p6,
struct pt_regs *regs) struct pt_regs *regs)
{ {
return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs->gpr[1], regs, 0); return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs->gpr[1], regs, 0);
} }
asmlinkage int sys_execve(unsigned long a0, unsigned long a1, unsigned long a2, int sys_execve(unsigned long a0, unsigned long a1, unsigned long a2,
unsigned long a3, unsigned long a4, unsigned long a5, unsigned long a3, unsigned long a4, unsigned long a5,
struct pt_regs *regs) struct pt_regs *regs)
{ {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment