32x: split sh2 code, compiler stub

git-svn-id: file:///home/notaz/opt/svn/PicoDrive@810 be3aeb3a-fb24-0410-a615-afba39da0efa
This commit is contained in:
notaz 2009-10-08 19:47:31 +00:00
parent 1d29444dfc
commit 4139770121
16 changed files with 227 additions and 205 deletions

36
cpu/drc/cmn.c Normal file
View file

@ -0,0 +1,36 @@
#include <stdio.h>
#if defined(__linux__) && defined(ARM)
#include <sys/mman.h>
#endif
#include "cmn.h"
#ifndef ARM
unsigned int tcache[SSP_TCACHE_SIZE/4];
unsigned int *ssp_block_table[0x5090/2];
unsigned int *ssp_block_table_iram[15][0x800/2];
char ssp_align[SSP_BLOCKTAB_ALIGN_SIZE];
#endif
void drc_cmn_init(void)
{
#if defined(__linux__) && defined(ARM)
void *tmp;
tmp = mmap(tcache, SSP_DRC_SIZE, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
printf("mmap tcache: %p, asked %p\n", tmp, tcache);
#endif
}
// TODO: add calls in core, possibly to cart.c?
void drc_cmn_cleanup(void)
{
#if defined(__linux__) && defined(ARM)
int ret;
ret = munmap(tcache, SSP_DRC_SIZE);
printf("munmap tcache: %i\n", ret);
#endif
}

13
cpu/drc/cmn.h Normal file
View file

@ -0,0 +1,13 @@
#define SSP_TCACHE_SIZE (512*1024)
#define SSP_BLOCKTAB_SIZE (0x5090/2*4)
#define SSP_BLOCKTAB_IRAM_SIZE (15*0x800/2*4)
#define SSP_BLOCKTAB_ALIGN_SIZE 3808
#define SSP_DRC_SIZE (SSP_TCACHE_SIZE + SSP_BLOCKTAB_SIZE + SSP_BLOCKTAB_IRAM_SIZE + SSP_BLOCKTAB_ALIGN_SIZE)
extern unsigned int tcache[SSP_TCACHE_SIZE/4];
extern unsigned int *ssp_block_table[SSP_BLOCKTAB_SIZE/4];
extern unsigned int *ssp_block_table_iram[15][0x800/2];
void drc_cmn_init(void);
void drc_cmn_cleanup(void);

25
cpu/drc/cmn_arm.S Normal file
View file

@ -0,0 +1,25 @@
@ vim:filetype=armasm
.if 0
#include "cmn.h"
.endif
.global tcache
.global ssp_block_table
.global ssp_block_table_iram
@ translation cache buffer + pointer table
.data
.align 12 @ 4096
@.size tcache, SSP_TCACHE_SIZE
@.size ssp_block_table, SSP_BLOCKTAB_SIZE
@.size ssp_block_table_iram, SSP_BLOCKTAB_IRAM_SIZE
tcache:
.space SSP_TCACHE_SIZE
ssp_block_table:
.space SSP_BLOCKTAB_SIZE
ssp_block_table_iram:
.space SSP_BLOCKTAB_IRAM_SIZE
.space SSP_BLOCKTAB_ALIGN_SIZE

10
cpu/sh2/compiler.c Normal file
View file

@ -0,0 +1,10 @@
#include "../sh2.h"
void sh2_execute(SH2 *sh2, int cycles)
{
unsigned int pc = sh2->pc;
int op;
op = p32x_sh2_read16(pc);
}

View file

@ -100,7 +100,7 @@
*****************************************************************************/ *****************************************************************************/
//#include "debugger.h" //#include "debugger.h"
#include "sh2.h" //#include "sh2.h"
//#include "sh2comn.h" //#include "sh2comn.h"
#define INLINE static #define INLINE static
@ -115,7 +115,7 @@
#define LOG(x) do { if (VERBOSE) logerror x; } while (0) #define LOG(x) do { if (VERBOSE) logerror x; } while (0)
int sh2_icount; //int sh2_icount;
SH2 *sh2; SH2 *sh2;
#if 0 #if 0

View file

@ -1,4 +1,4 @@
#include <string.h> #include "../sh2.h"
// MAME types // MAME types
typedef signed char INT8; typedef signed char INT8;
@ -8,14 +8,6 @@ typedef unsigned int UINT32;
typedef unsigned short UINT16; typedef unsigned short UINT16;
typedef unsigned char UINT8; typedef unsigned char UINT8;
// pico memhandlers
unsigned int p32x_sh2_read8(unsigned int a, int id);
unsigned int p32x_sh2_read16(unsigned int a, int id);
unsigned int p32x_sh2_read32(unsigned int a, int id);
void p32x_sh2_write8(unsigned int a, unsigned int d, int id);
void p32x_sh2_write16(unsigned int a, unsigned int d, int id);
void p32x_sh2_write32(unsigned int a, unsigned int d, int id);
#define RB(a) p32x_sh2_read8(a,sh2->is_slave) #define RB(a) p32x_sh2_read8(a,sh2->is_slave)
#define RW(a) p32x_sh2_read16(a,sh2->is_slave) #define RW(a) p32x_sh2_read16(a,sh2->is_slave)
#define RL(a) p32x_sh2_read32(a,sh2->is_slave) #define RL(a) p32x_sh2_read32(a,sh2->is_slave)
@ -37,42 +29,18 @@ void p32x_sh2_write32(unsigned int a, unsigned int d, int id);
#define Rn ((opcode>>8)&15) #define Rn ((opcode>>8)&15)
#define Rm ((opcode>>4)&15) #define Rm ((opcode>>4)&15)
#define sh2_icount sh2->icount
#include "sh2.c" #include "sh2.c"
void sh2_reset(SH2 *sh2) void sh2_execute(SH2 *sh2_, int cycles)
{
sh2->pc = RL(0);
sh2->r[15] = RL(4);
sh2->sr = I;
sh2->vbr = 0;
sh2->pending_int_irq = 0;
}
static void sh2_do_irq(SH2 *sh2, int level, int vector)
{
sh2->irq_callback(sh2->is_slave, level);
sh2->r[15] -= 4;
WL(sh2->r[15], sh2->sr); /* push SR onto stack */
sh2->r[15] -= 4;
WL(sh2->r[15], sh2->pc); /* push PC onto stack */
/* set I flags in SR */
sh2->sr = (sh2->sr & ~I) | (level << 4);
/* fetch PC */
sh2->pc = RL(sh2->vbr + vector * 4);
/* 13 cycles at best */
sh2_icount -= 13;
}
/* Execute cycles - returns number of cycles actually run */
int sh2_execute(SH2 *sh2_, int cycles)
{ {
sh2 = sh2_; sh2 = sh2_;
sh2_icount = cycles;
sh2->cycles_aim += cycles; sh2->cycles_aim += cycles;
sh2->icount = cycles = sh2->cycles_aim - sh2->cycles_done;
if (sh2->icount <= 0)
return;
do do
{ {
@ -121,36 +89,10 @@ int sh2_execute(SH2 *sh2_, int cycles)
sh2_internal_irq(sh2, sh2->pending_int_irq, sh2->pending_int_vector); sh2_internal_irq(sh2, sh2->pending_int_irq, sh2->pending_int_vector);
sh2->test_irq = 0; sh2->test_irq = 0;
} }
sh2_icount--; sh2->icount--;
} }
while (sh2_icount > 0 || sh2->delay); /* can't interrupt before delay */ while (sh2->icount > 0 || sh2->delay); /* can't interrupt before delay */
return cycles - sh2_icount; sh2->cycles_done += cycles - sh2->icount;
}
void sh2_init(SH2 *sh2, int is_slave)
{
memset(sh2, 0, sizeof(*sh2));
sh2->is_slave = is_slave;
}
void sh2_irl_irq(SH2 *sh2, int level)
{
sh2->pending_irl = level;
if (level <= ((sh2->sr >> 4) & 0x0f))
return;
sh2_do_irq(sh2, level, 64 + level/2);
}
void sh2_internal_irq(SH2 *sh2, int level, int vector)
{
sh2->pending_int_irq = level;
sh2->pending_int_vector = vector;
if (level <= ((sh2->sr >> 4) & 0x0f))
return;
sh2_do_irq(sh2, level, vector);
sh2->pending_int_irq = 0; // auto-clear
} }

60
cpu/sh2/sh2.c Normal file
View file

@ -0,0 +1,60 @@
#include <string.h>
#include "sh2.h"
#define I 0xf0
void sh2_init(SH2 *sh2, int is_slave)
{
memset(sh2, 0, sizeof(*sh2));
sh2->is_slave = is_slave;
}
void sh2_reset(SH2 *sh2)
{
sh2->pc = p32x_sh2_read32(0, sh2->is_slave);
sh2->r[15] = p32x_sh2_read32(4, sh2->is_slave);
sh2->sr = I;
sh2->vbr = 0;
sh2->pending_int_irq = 0;
}
static void sh2_do_irq(SH2 *sh2, int level, int vector)
{
sh2->irq_callback(sh2->is_slave, level);
sh2->r[15] -= 4;
p32x_sh2_write32(sh2->r[15], sh2->sr, sh2->is_slave); /* push SR onto stack */
sh2->r[15] -= 4;
p32x_sh2_write32(sh2->r[15], sh2->pc, sh2->is_slave); /* push PC onto stack */
/* set I flags in SR */
sh2->sr = (sh2->sr & ~I) | (level << 4);
/* fetch PC */
sh2->pc = p32x_sh2_read32(sh2->vbr + vector * 4, sh2->is_slave);
/* 13 cycles at best */
sh2->cycles_done += 13;
// sh2->icount -= 13;
}
void sh2_irl_irq(SH2 *sh2, int level)
{
sh2->pending_irl = level;
if (level <= ((sh2->sr >> 4) & 0x0f))
return;
sh2_do_irq(sh2, level, 64 + level/2);
}
void sh2_internal_irq(SH2 *sh2, int level, int vector)
{
sh2->pending_int_irq = level;
sh2->pending_int_vector = vector;
if (level <= ((sh2->sr >> 4) & 0x0f))
return;
sh2_do_irq(sh2, level, vector);
sh2->pending_int_irq = 0; // auto-clear
}

48
cpu/sh2/sh2.h Normal file
View file

@ -0,0 +1,48 @@
#ifndef __SH2_H__
#define __SH2_H__
// pico memhandlers
// XXX: move somewhere else
unsigned int p32x_sh2_read8(unsigned int a, int id);
unsigned int p32x_sh2_read16(unsigned int a, int id);
unsigned int p32x_sh2_read32(unsigned int a, int id);
void p32x_sh2_write8(unsigned int a, unsigned int d, int id);
void p32x_sh2_write16(unsigned int a, unsigned int d, int id);
void p32x_sh2_write32(unsigned int a, unsigned int d, int id);
typedef struct
{
unsigned int r[16];
unsigned int ppc;
unsigned int pc;
unsigned int pr;
unsigned int sr;
unsigned int gbr, vbr;
unsigned int mach, macl;
unsigned int ea;
unsigned int delay;
unsigned int test_irq;
int pending_irl;
int pending_int_irq; // internal irq
int pending_int_vector;
void (*irq_callback)(int id, int level);
int is_slave;
int icount; // cycles left in current timeslice
unsigned int cycles_aim; // subtract sh2_icount to get global counter
unsigned int cycles_done;
} SH2;
extern SH2 *sh2; // active sh2
void sh2_init(SH2 *sh2, int is_slave);
void sh2_reset(SH2 *sh2);
void sh2_irl_irq(SH2 *sh2, int level);
void sh2_internal_irq(SH2 *sh2, int level, int vector);
void sh2_execute(SH2 *sh2, int cycles);
#endif /* __SH2_H__ */

View file

@ -1,65 +0,0 @@
/*****************************************************************************
*
* sh2.h
* Portable Hitachi SH-2 (SH7600 family) emulator interface
*
* Copyright Juergen Buchmueller <pullmoll@t-online.de>,
* all rights reserved.
*
* - This source code is released as freeware for non-commercial purposes.
* - You are free to use and redistribute this code in modified or
* unmodified form, provided you list me in the credits.
* - If you modify this source code, you must add a notice to each modified
* source file that it has been changed. If you're a nice person, you
* will clearly mark each change too. :)
* - If you wish to use this for commercial purposes, please contact me at
* pullmoll@t-online.de
* - The author of this copywritten work reserves the right to change the
* terms of its usage and license at any time, including retroactively
* - This entire notice must remain in the source code.
*
* This work is based on <tiraniddo@hotmail.com> C/C++ implementation of
* the SH-2 CPU core and was heavily changed to the MAME CPU requirements.
* Thanks also go to Chuck Mason <chukjr@sundail.net> and Olivier Galibert
* <galibert@pobox.com> for letting me peek into their SEMU code :-)
*
*****************************************************************************/
#pragma once
#ifndef __SH2_H__
#define __SH2_H__
typedef struct
{
unsigned int r[16];
unsigned int ppc;
unsigned int pc;
unsigned int pr;
unsigned int sr;
unsigned int gbr, vbr;
unsigned int mach, macl;
unsigned int ea;
unsigned int delay;
unsigned int test_irq;
int pending_irl;
int pending_int_irq; // internal irq
int pending_int_vector;
void (*irq_callback)(int id, int level);
int is_slave;
unsigned int cycles_aim; // subtract sh2_icount to get global counter
} SH2;
SH2 *sh2; // active sh2
extern int sh2_icount;
void sh2_init(SH2 *sh2, int is_slave);
void sh2_reset(SH2 *sh2);
int sh2_execute(SH2 *sh2_, int cycles);
void sh2_irl_irq(SH2 *sh2, int level);
void sh2_internal_irq(SH2 *sh2, int level, int vector);
#endif /* __SH2_H__ */

View file

@ -4,6 +4,7 @@
// Free for non-commercial use. // Free for non-commercial use.
#include "../../pico_int.h" #include "../../pico_int.h"
#include "../../../cpu/drc/cmn.h"
#include "compiler.h" #include "compiler.h"
#define u32 unsigned int #define u32 unsigned int
@ -23,10 +24,6 @@ extern ssp1601_t *ssp;
#ifndef ARM #ifndef ARM
#define DUMP_BLOCK 0x0c9a #define DUMP_BLOCK 0x0c9a
u32 tcache[SSP_TCACHE_SIZE/4];
u32 *ssp_block_table[0x5090/2];
u32 *ssp_block_table_iram[15][0x800/2];
char ssp_align[SSP_BLOCKTAB_ALIGN_SIZE];
void ssp_drc_next(void){} void ssp_drc_next(void){}
void ssp_drc_next_patch(void){} void ssp_drc_next_patch(void){}
void ssp_drc_end(void){} void ssp_drc_end(void){}
@ -1795,6 +1792,8 @@ static void ssp1601_state_load(void)
int ssp1601_dyn_startup(void) int ssp1601_dyn_startup(void)
{ {
drc_cmn_init();
memset(tcache, 0, SSP_TCACHE_SIZE); memset(tcache, 0, SSP_TCACHE_SIZE);
memset(ssp_block_table, 0, sizeof(ssp_block_table)); memset(ssp_block_table, 0, sizeof(ssp_block_table));
memset(ssp_block_table_iram, 0, sizeof(ssp_block_table_iram)); memset(ssp_block_table_iram, 0, sizeof(ssp_block_table_iram));

View file

@ -1,13 +1,3 @@
#define SSP_TCACHE_SIZE (512*1024)
#define SSP_BLOCKTAB_SIZE (0x5090/2*4)
#define SSP_BLOCKTAB_IRAM_SIZE (15*0x800/2*4)
#define SSP_BLOCKTAB_ALIGN_SIZE 3808
#define SSP_DRC_SIZE (SSP_TCACHE_SIZE + SSP_BLOCKTAB_SIZE + SSP_BLOCKTAB_IRAM_SIZE + SSP_BLOCKTAB_ALIGN_SIZE)
extern unsigned int tcache[SSP_TCACHE_SIZE/4];
extern unsigned int *ssp_block_table[0x5090/2];
extern unsigned int *ssp_block_table_iram[15][0x800/2];
int ssp_drc_entry(int cycles); int ssp_drc_entry(int cycles);
void ssp_drc_next(void); void ssp_drc_next(void);
void ssp_drc_next_patch(void); void ssp_drc_next_patch(void);

View file

@ -5,14 +5,6 @@
@ (c) Copyright 2008, Grazvydas "notaz" Ignotas @ (c) Copyright 2008, Grazvydas "notaz" Ignotas
@ Free for non-commercial use. @ Free for non-commercial use.
.if 0
#include "compiler.h"
.endif
.global tcache
.global ssp_block_table
.global ssp_block_table_iram
.global ssp_drc_entry .global ssp_drc_entry
.global ssp_drc_next .global ssp_drc_next
.global ssp_drc_next_patch .global ssp_drc_next_patch
@ -26,24 +18,9 @@
.global ssp_hle_11_384 .global ssp_hle_11_384
.global ssp_hle_11_38a .global ssp_hle_11_38a
@ translation cache buffer + pointer table
.data
.align 12 @ 4096
@.size tcache, SSP_TCACHE_SIZE
@.size ssp_block_table, SSP_BLOCKTAB_SIZE
@.size ssp_block_table_iram, SSP_BLOCKTAB_IRAM_SIZE
tcache:
.space SSP_TCACHE_SIZE
ssp_block_table:
.space SSP_BLOCKTAB_SIZE
ssp_block_table_iram:
.space SSP_BLOCKTAB_IRAM_SIZE
.space SSP_BLOCKTAB_ALIGN_SIZE
.text .text
.align 2 .align 2
@ SSP_GR0, SSP_X, SSP_Y, SSP_A, @ SSP_GR0, SSP_X, SSP_Y, SSP_A,
@ SSP_ST, SSP_STACK, SSP_PC, SSP_P, @ SSP_ST, SSP_STACK, SSP_PC, SSP_P,
@ SSP_PM0, SSP_PM1, SSP_PM2, SSP_XST, @ SSP_PM0, SSP_PM1, SSP_PM2, SSP_XST,

View file

@ -8,9 +8,6 @@
#include "../../pico_int.h" #include "../../pico_int.h"
#include "compiler.h" #include "compiler.h"
#if defined(__linux__) && defined(ARM)
#include <sys/mman.h>
#endif
svp_t *svp = NULL; svp_t *svp = NULL;
int PicoSVPCycles = 850; // cycles/line, just a guess int PicoSVPCycles = 850; // cycles/line, just a guess
@ -100,20 +97,6 @@ static int PicoSVPDma(unsigned int source, int len, unsigned short **srcp, unsig
void PicoSVPInit(void) void PicoSVPInit(void)
{ {
#if defined(__linux__) && defined(ARM)
int ret;
ret = munmap(tcache, SSP_DRC_SIZE);
printf("munmap tcache: %i\n", ret);
#endif
}
static void PicoSVPShutdown(void)
{
#if defined(__linux__) && defined(ARM)
// also unmap tcache
PicoSVPInit();
#endif
} }
@ -135,16 +118,12 @@ void PicoSVPStartup(void)
svp = (void *) ((char *)tmp + 0x200000); svp = (void *) ((char *)tmp + 0x200000);
memset(svp, 0, sizeof(*svp)); memset(svp, 0, sizeof(*svp));
#if defined(__linux__) && defined(ARM)
tmp = mmap(tcache, SSP_DRC_SIZE, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
printf("mmap tcache: %p, asked %p\n", tmp, tcache);
#endif
// init SVP compiler // init SVP compiler
svp_dyn_ready = 0; svp_dyn_ready = 0;
#ifndef PSP #ifndef PSP
if (PicoOpt & POPT_EN_SVP_DRC) { if (PicoOpt & POPT_EN_SVP_DRC) {
if (ssp1601_dyn_startup()) return; if (ssp1601_dyn_startup())
return;
svp_dyn_ready = 1; svp_dyn_ready = 1;
} }
#endif #endif
@ -154,7 +133,6 @@ void PicoSVPStartup(void)
PicoDmaHook = PicoSVPDma; PicoDmaHook = PicoSVPDma;
PicoResetHook = PicoSVPReset; PicoResetHook = PicoSVPReset;
PicoLineHook = PicoSVPLine; PicoLineHook = PicoSVPLine;
PicoCartUnloadHook = PicoSVPShutdown;
// save state stuff // save state stuff
svp_states[0].ptr = svp->iram_rom; svp_states[0].ptr = svp->iram_rom;

View file

@ -237,14 +237,14 @@ typedef void (z80_write_f)(unsigned int a, unsigned char data);
// ----------------------- SH2 CPU ----------------------- // ----------------------- SH2 CPU -----------------------
#include "cpu/sh2mame/sh2.h" #include "cpu/sh2/sh2.h"
extern SH2 sh2s[2]; extern SH2 sh2s[2];
#define msh2 sh2s[0] #define msh2 sh2s[0]
#define ssh2 sh2s[1] #define ssh2 sh2s[1]
#define ash2_end_run(after) if (sh2_icount > (after)) sh2_icount = after #define ash2_end_run(after) if (sh2->icount > (after)) sh2->icount = after
#define ash2_cycles_done() (sh2->cycles_aim - sh2_icount) #define ash2_cycles_done() (sh2->cycles_aim - sh2->icount)
#define sh2_pc(c) (c) ? ssh2.ppc : msh2.ppc #define sh2_pc(c) (c) ? ssh2.ppc : msh2.ppc
#define sh2_reg(c, x) (c) ? ssh2.r[x] : msh2.r[x] #define sh2_reg(c, x) (c) ? ssh2.r[x] : msh2.r[x]

View file

@ -112,10 +112,13 @@ else
DEFINC += -D_USE_DRZ80 DEFINC += -D_USE_DRZ80
OBJS += cpu/DrZ80/drz80.o OBJS += cpu/DrZ80/drz80.o
endif endif
OBJS += cpu/sh2/sh2.o
ifeq "$(use_sh2mame)" "1" ifeq "$(use_sh2mame)" "1"
OBJS += cpu/sh2mame/sh2pico.o OBJS += cpu/sh2/mame/sh2pico.o
else else
endif endif
OBJS += cpu/drc/cmn.o
OBJS += cpu/drc/cmn_arm.o
vpath %.c = ../.. vpath %.c = ../..
vpath %.s = ../.. vpath %.s = ../..
@ -123,7 +126,7 @@ vpath %.S = ../..
DIRS = platform platform/gp2x platform/linux platform/common pico pico/cd pico/pico pico/32x \ DIRS = platform platform/gp2x platform/linux platform/common pico pico/cd pico/pico pico/32x \
pico/sound pico/carthw/svp zlib unzip cpu cpu/musashi cpu/Cyclone/proj cpu/Cyclone/tools \ pico/sound pico/carthw/svp zlib unzip cpu cpu/musashi cpu/Cyclone/proj cpu/Cyclone/tools \
cpu/mz80 cpu/DrZ80 cpu/sh2mame cpu/mz80 cpu/DrZ80 cpu/sh2/mame cpu/drc
all: mkdirs PicoDrive all: mkdirs PicoDrive

View file

@ -2,6 +2,7 @@
use_musashi = 1 use_musashi = 1
#use_fame = 1 #use_fame = 1
#use_mz80 = 1 #use_mz80 = 1
#use_sh2drc = 1
#profile = 1 #profile = 1
#fake_in_gp2x = 1 #fake_in_gp2x = 1
@ -72,7 +73,12 @@ DEFINES += _USE_CZ80
OBJS += cpu/cz80/cz80.o OBJS += cpu/cz80/cz80.o
endif endif
# sh2 # sh2
OBJS += cpu/sh2mame/sh2pico.o OBJS += cpu/sh2/sh2.o
ifeq "$(use_sh2drc)" "1"
else
OBJS += cpu/sh2/mame/sh2pico.o
endif
OBJS += cpu/drc/cmn.o
# misc # misc
ifeq "$(use_fame)" "1" ifeq "$(use_fame)" "1"
ifeq "$(use_musashi)" "1" ifeq "$(use_musashi)" "1"
@ -85,7 +91,7 @@ CFLAGS += $(addprefix -D,$(DEFINES))
vpath %.c = ../.. vpath %.c = ../..
DIRS = platform platform/gp2x platform/common pico pico/cd pico/pico pico/sound pico/carthw/svp \ DIRS = platform platform/gp2x platform/common pico pico/cd pico/pico pico/sound pico/carthw/svp \
pico/32x zlib unzip cpu cpu/musashi cpu/fame cpu/mz80 cpu/cz80 cpu/sh2mame pico/32x zlib unzip cpu cpu/musashi cpu/fame cpu/mz80 cpu/cz80 cpu/sh2/mame cpu/drc
all: mkdirs PicoDrive all: mkdirs PicoDrive
clean: tidy clean: tidy