base_hw: Use TLB-specific 'struct Page_flags'.

'Page_flags' maps application-specific memory attributes
to the TLB-specific memory attributes. Thereby it avoids
functions with lots of parameters, by declaring appropriate
bitfields on a single POD value.
This commit is contained in:
Martin Stein 2012-11-09 14:36:19 +01:00 committed by Norman Feske
parent 47690b8802
commit 4723b08322
9 changed files with 175 additions and 162 deletions

View File

@ -31,6 +31,13 @@ class Tlb : public Arm_v6::Section_table
void * operator new (Genode::size_t, void * p) { return p; } void * operator new (Genode::size_t, void * p) { return p; }
}; };
/**
* Board specific mapping attributes
*/
struct Page_flags : Arm::Page_flags { };
typedef Arm::page_flags_t page_flags_t;
/** /**
* TLB of core * TLB of core
* *
@ -42,11 +49,8 @@ class Core_tlb : public Tlb
Core_tlb() Core_tlb()
{ {
/* map RAM */ map_core_area(Board::RAM_0_BASE, Board::RAM_0_SIZE, 0);
translate_dpm_off(Board::RAM_0_BASE, Board::RAM_0_SIZE, 0, 1); map_core_area(Board::MMIO_0_BASE, Board::MMIO_0_SIZE, 1);
/* map MMIO */
translate_dpm_off(Board::MMIO_0_BASE, Board::MMIO_0_SIZE, 1, 0);
} }
}; };

View File

@ -22,6 +22,45 @@ namespace Arm
{ {
using namespace Genode; using namespace Genode;
/**
* Map app-specific mem attributes to a TLB-specific POD
*/
struct Page_flags : Register<8>
{
struct W : Bitfield<0, 1> { }; /* writeable */
struct X : Bitfield<1, 1> { }; /* executable */
struct K : Bitfield<2, 1> { }; /* privileged */
struct G : Bitfield<3, 1> { }; /* global */
struct D : Bitfield<4, 1> { }; /* device */
struct C : Bitfield<5, 1> { }; /* cacheable */
/**
* Create flag POD for Genode pagers
*/
static access_t
resolve_and_wait_for_fault(bool const writeable,
bool const write_combined,
bool const io_mem) {
return W::bits(writeable) | X::bits(1) | K::bits(0) | G::bits(0) |
D::bits(io_mem) | C::bits(!write_combined & !io_mem); }
/**
* Create flag POD for kernel when it creates the core space
*/
static access_t map_core_area(bool const io_mem) {
return W::bits(1) | X::bits(1) | K::bits(0) | G::bits(0) |
D::bits(io_mem) | C::bits(!io_mem); }
/**
* Create flag POD for the mode transition region
*/
static access_t mode_transition() {
return W::bits(1) | X::bits(1) | K::bits(1) | G::bits(1) |
D::bits(0) | C::bits(1); }
};
typedef Page_flags::access_t page_flags_t;
/** /**
* Check if 'p' is aligned to 1 << 'alignm_log2' * Check if 'p' is aligned to 1 << 'alignm_log2'
*/ */
@ -65,11 +104,10 @@ namespace Arm
* \return descriptor value with requested perms and the rest left zero * \return descriptor value with requested perms and the rest left zero
*/ */
template <typename T> template <typename T>
static typename T::access_t access_permission_bits(bool const w, static typename T::access_t
bool const x, access_permission_bits(page_flags_t const flags)
bool const k)
{ {
/* lookup table for AP bitfield values according to 'w' and 'k' */ /* lookup table for AP bitfield values according to 'w' and 'k' flag */
typedef typename T::Ap_1_0 Ap_1_0; typedef typename T::Ap_1_0 Ap_1_0;
typedef typename T::Ap_2 Ap_2; typedef typename T::Ap_2 Ap_2;
static typename T::access_t const ap_bits[2][2] = {{ static typename T::access_t const ap_bits[2][2] = {{
@ -85,9 +123,10 @@ namespace Arm
Ap_1_0::bits(Ap_1_0::USER_NO_ACCESS) | /* wk */ Ap_1_0::bits(Ap_1_0::USER_NO_ACCESS) | /* wk */
Ap_2::bits(Ap_2::KERNEL_RW_OR_NO_ACCESS) } Ap_2::bits(Ap_2::KERNEL_RW_OR_NO_ACCESS) }
}; };
/* combine XN and AP bitfield values according to 'w', 'x' and 'k' */ /* combine XN and AP bitfield values according to the flags */
typedef typename T::Xn Xn; typedef typename T::Xn Xn;
return Xn::bits(!x) | ap_bits[w][k]; return Xn::bits(!Page_flags::X::get(flags)) |
ap_bits[Page_flags::W::get(flags)][Page_flags::K::get(flags)];
} }
/** /**
@ -102,7 +141,7 @@ namespace Arm
* Memory region attributes for the translation descriptor 'T' * Memory region attributes for the translation descriptor 'T'
*/ */
template <typename T> template <typename T>
static typename T::access_t memory_region_attr(bool const d, bool const c) static typename T::access_t memory_region_attr(page_flags_t const flags)
{ {
typedef typename T::Tex Tex; typedef typename T::Tex Tex;
typedef typename T::C C; typedef typename T::C C;
@ -111,12 +150,14 @@ namespace Arm
/* /*
* FIXME: upgrade to write-back & write-allocate when !d & c * FIXME: upgrade to write-back & write-allocate when !d & c
*/ */
if(d) return Tex::bits(2) | C::bits(0) | B::bits(0); if(Page_flags::D::get(flags))
return Tex::bits(2) | C::bits(0) | B::bits(0);
if(cache_support()) { if(cache_support()) {
if(c) return Tex::bits(6) | C::bits(1) | B::bits(0); if(Page_flags::C::get(flags))
return Tex::bits(4) | C::bits(0) | B::bits(0); return Tex::bits(6) | C::bits(1) | B::bits(0);
return Tex::bits(4) | C::bits(0) | B::bits(0);
} }
return Tex::bits(4) | C::bits(0) | B::bits(0); return Tex::bits(4) | C::bits(0) | B::bits(0);
} }
/** /**
@ -253,16 +294,13 @@ namespace Arm
/** /**
* Compose descriptor value * Compose descriptor value
*/ */
static access_t create(bool const w, bool const x, static access_t create(page_flags_t const flags,
bool const k, bool const g,
bool const d, bool const c,
addr_t const pa) addr_t const pa)
{ {
access_t v = access_permission_bits<Small_page>(w, x, k) | access_t v = access_permission_bits<Small_page>(flags) |
memory_region_attr<Small_page>(d, c) | memory_region_attr<Small_page>(flags) |
Ng::bits(!g) | Ng::bits(!Page_flags::G::get(flags)) |
S::bits(0) | S::bits(0) | Pa_31_12::masked(pa);
Pa_31_12::masked(pa);
Descriptor::type(v, Descriptor::SMALL_PAGE); Descriptor::type(v, Descriptor::SMALL_PAGE);
return v; return v;
} }
@ -335,12 +373,7 @@ namespace Arm
* \param pa base of the physical backing store * \param pa base of the physical backing store
* \param size_log2 log2(Size of the translated region), * \param size_log2 log2(Size of the translated region),
* must be supported by this table * must be supported by this table
* \param w see 'Section_table::insert_translation' * \param flags mapping flags
* \param x see 'Section_table::insert_translation'
* \param k see 'Section_table::insert_translation'
* \param g see 'Section_table::insert_translation'
* \param d see 'Section_table::insert_translation'
* \param c see 'Section_table::insert_translation'
* *
* This method overrides an existing translation in case * This method overrides an existing translation in case
* that it spans the the same virtual range and is not * that it spans the the same virtual range and is not
@ -348,9 +381,7 @@ namespace Arm
*/ */
void insert_translation(addr_t const vo, addr_t const pa, void insert_translation(addr_t const vo, addr_t const pa,
unsigned long const size_log2, unsigned long const size_log2,
bool const w, bool const x, page_flags_t const flags)
bool const k, bool const g,
bool const d, bool const c)
{ {
/* validate virtual address */ /* validate virtual address */
unsigned long i; unsigned long i;
@ -363,7 +394,7 @@ namespace Arm
{ {
/* compose new descriptor value */ /* compose new descriptor value */
Descriptor::access_t const entry = Descriptor::access_t const entry =
Small_page::create(w, x, k, g, d, c, pa); Small_page::create(flags, pa);
/* check if we can we write to the targeted entry */ /* check if we can we write to the targeted entry */
if (Descriptor::valid(_entries[i])) if (Descriptor::valid(_entries[i]))
@ -614,16 +645,13 @@ namespace Arm
/** /**
* Compose descriptor value * Compose descriptor value
*/ */
static access_t create(bool const w, bool const x, static access_t create(page_flags_t const flags,
bool const k, bool const g,
bool const d, bool const c,
addr_t const pa) addr_t const pa)
{ {
access_t v = access_permission_bits<Section>(w, x, k) | access_t v = access_permission_bits<Section>(flags) |
memory_region_attr<Section>(d, c) | memory_region_attr<Section>(flags) |
Domain::bits(DOMAIN) | Domain::bits(DOMAIN) | S::bits(0) |
S::bits(0) | Ng::bits(!Page_flags::G::get(flags)) |
Ng::bits(!g) |
Pa_31_20::masked(pa); Pa_31_20::masked(pa);
Descriptor::type(v, Descriptor::SECTION); Descriptor::type(v, Descriptor::SECTION);
return v; return v;
@ -693,17 +721,7 @@ namespace Arm
* region represented by this table * region represented by this table
* \param pa base of the physical backing store * \param pa base of the physical backing store
* \param size_log2 size log2 of the translated region * \param size_log2 size log2 of the translated region
* \param w if one can write trough this translation * \param flags mapping flags
* \param x if one can execute trough this translation
* \param k If set to 1, the given permissions apply
* in kernel mode, while in user mode this
* translations grants no type of access.
* If set to 0, the given permissions apply
* in user mode, while in kernel mode this
* translation grants any type of access.
* \param g if the translation applies to all spaces
* \param d wether 'pa' addresses device IO-memory
* \param c if access shall be cacheable
* \param extra_space If > 0, it must point to a portion of * \param extra_space If > 0, it must point to a portion of
* size-aligned memory space wich may be used * size-aligned memory space wich may be used
* furthermore by the table for the incurring * furthermore by the table for the incurring
@ -727,9 +745,7 @@ namespace Arm
template <typename ST> template <typename ST>
unsigned long insert_translation(addr_t const vo, addr_t const pa, unsigned long insert_translation(addr_t const vo, addr_t const pa,
unsigned long const size_log2, unsigned long const size_log2,
bool const w, bool const x, page_flags_t const flags,
bool const k, bool const g,
bool const d, bool const c,
ST * const st, ST * const st,
void * const extra_space = 0) void * const extra_space = 0)
{ {
@ -768,14 +784,14 @@ namespace Arm
/* insert translation */ /* insert translation */
pt->insert_translation(vo - Section::Pa_31_20::masked(vo), pt->insert_translation(vo - Section::Pa_31_20::masked(vo),
pa, size_log2, w, x, k, g, d, c); pa, size_log2, flags);
return 0; return 0;
} }
if (size_log2 == Section::VIRT_SIZE_LOG2) if (size_log2 == Section::VIRT_SIZE_LOG2)
{ {
/* compose section descriptor */ /* compose section descriptor */
Descriptor::access_t const entry = Descriptor::access_t const entry =
Section::create(w, x, k, g, d, c, pa, st); Section::create(flags, pa, st);
/* check if we can we write to the targeted entry */ /* check if we can we write to the targeted entry */
if (Descriptor::valid(_entries[i])) if (Descriptor::valid(_entries[i]))
@ -914,25 +930,30 @@ namespace Arm
/** /**
* Insert translations for given area, do not permit displacement * Insert translations for given area, do not permit displacement
* *
* \param vo virtual offset within this table * \param vo virtual offset within this table
* \param s area size * \param s area size
* \param d wether area maps device IO memory * \param flags mapping flags
* \param c wether area maps cacheable memory
*/ */
template <typename ST> template <typename ST>
void translate_dpm_off(addr_t vo, size_t s, void map_core_area(addr_t vo, size_t s, bool io_mem, ST * st)
bool const d, bool const c, ST * st)
{ {
/* initialize parameters */
page_flags_t const flags = Page_flags::map_core_area(io_mem);
unsigned tsl2 = translation_size_l2(vo, s); unsigned tsl2 = translation_size_l2(vo, s);
size_t ts = 1 << tsl2; size_t ts = 1 << tsl2;
while (1) {
if(st->insert_translation(vo, vo, tsl2, 1,1,0,0,d,c)) { /* walk through the area and map all offsets */
while (1)
{
/* map current offset without displacement */
if(st->insert_translation(vo, vo, tsl2, flags)) {
PDBG("Displacement not permitted"); PDBG("Displacement not permitted");
return; return;
} }
/* update parameters for next round or exit */
vo += ts; vo += ts;
s = ts < s ? s - ts : 0; s = ts < s ? s - ts : 0;
if (!s) break; if (!s) return;
tsl2 = translation_size_l2(vo, s); tsl2 = translation_size_l2(vo, s);
ts = 1 << tsl2; ts = 1 << tsl2;
} }

View File

@ -53,14 +53,10 @@ namespace Arm_v6
/** /**
* Compose descriptor value * Compose descriptor value
*/ */
static access_t create(bool const w, bool const x, static access_t create(Arm::page_flags_t const flags,
bool const k, bool const g, addr_t const pa, Section_table *)
bool const d, bool const c,
addr_t const pa,
Section_table *)
{ {
return Arm::Section_table::Section::create(w, x, k, g, return Arm::Section_table::Section::create(flags, pa) |
d, c, pa) |
P::bits(0); P::bits(0);
} }
}; };
@ -70,33 +66,25 @@ namespace Arm_v6
* *
* For details see 'Arm::Section_table::insert_translation' * For details see 'Arm::Section_table::insert_translation'
*/ */
unsigned long insert_translation(addr_t const vo, addr_t const pa, unsigned long
unsigned long const size_log2, insert_translation(addr_t const vo, addr_t const pa,
bool const w, bool const x, unsigned long const size_log2,
bool const k, bool const g, Arm::page_flags_t const flags,
bool const d, bool const c, void * const extra_space = 0) {
void * const extra_space = 0)
{
return Arm::Section_table:: return Arm::Section_table::
insert_translation<Section_table>(vo, pa, size_log2, w, insert_translation<Section_table>(vo, pa, size_log2, flags,
x, k, g, d, c, this, this, extra_space); }
extra_space);
}
/** /**
* Insert translations for given area, do not permit displacement * Insert translations for given area, do not permit displacement
* *
* \param vo virtual offset within this table * \param vo virtual offset within this table
* \param s area size * \param s area size
* \param d wether area maps device IO memory * \param io_mem wether the area maps MMIO
* \param c wether area maps cacheable memory
*/ */
void translate_dpm_off(addr_t vo, size_t s, void map_core_area(addr_t vo, size_t s, bool const io_mem) {
bool const d, bool const c) Arm::Section_table::map_core_area<Section_table>(vo, s, io_mem,
{ this); }
Arm::Section_table::
translate_dpm_off<Section_table>(vo, s, d, c, this);
}
}; };
} }

View File

@ -57,13 +57,11 @@ namespace Arm_v7
/** /**
* Compose descriptor value * Compose descriptor value
*/ */
static access_t create(bool const w, bool const x, static access_t create(Arm::page_flags_t const flags,
bool const k, bool const g,
bool const d, bool const c,
addr_t const pa, addr_t const pa,
Section_table * const st) Section_table * const st)
{ {
return Arm::Section_table::Section::create(w, x, k, g, d, c, pa) | return Arm::Section_table::Section::create(flags, pa) |
Ns::bits(!st->secure()); Ns::bits(!st->secure());
} }
}; };
@ -85,33 +83,25 @@ namespace Arm_v7
* *
* For details see 'Arm::Section_table::insert_translation' * For details see 'Arm::Section_table::insert_translation'
*/ */
unsigned long insert_translation(addr_t const vo, addr_t const pa, unsigned long
unsigned long const size_log2, insert_translation(addr_t const vo, addr_t const pa,
bool const w, bool const x, unsigned long const size_log2,
bool const k, bool const g, Arm::page_flags_t const flags,
bool const d, bool const c, void * const extra_space = 0) {
void * const extra_space = 0)
{
return Arm::Section_table:: return Arm::Section_table::
insert_translation<Section_table>(vo, pa, size_log2, w, insert_translation<Section_table>(vo, pa, size_log2, flags,
x, k, g, d, c, this, this, extra_space); }
extra_space);
}
/** /**
* Insert translations for given area, do not permit displacement * Insert translations for given area, do not permit displacement
* *
* \param vo virtual offset within this table * \param vo virtual offset within this table
* \param s area size * \param s area size
* \param d wether area maps device IO memory * \param io_mem wether the area maps MMIO
* \param c wether area maps cacheable memory
*/ */
void translate_dpm_off(addr_t vo, size_t s, void map_core_area(addr_t vo, size_t s, bool const io_mem) {
bool const d, bool const c) Arm::Section_table::map_core_area<Section_table>(vo, s, io_mem,
{ this); }
Arm::Section_table::
translate_dpm_off<Section_table>(vo, s, d, c, this);
}
/*************** /***************
** Accessors ** ** Accessors **

View File

@ -801,12 +801,11 @@ namespace Kernel
Pd(Tlb * const t) : _tlb(t) Pd(Tlb * const t) : _tlb(t)
{ {
/* try to add translation for mode transition region */ /* try to add translation for mode transition region */
enum Mtc_attributes { W = 1, X = 1, K = 1, G = 1, D = 0, C = 1 }; page_flags_t const flags = Page_flags::mode_transition();
unsigned const slog2 = unsigned const slog2 =
tlb()->insert_translation(mtc()->VIRT_BASE, tlb()->insert_translation(mtc()->VIRT_BASE,
mtc()->phys_base(), mtc()->phys_base(),
mtc()->SIZE_LOG2, mtc()->SIZE_LOG2, flags);
W, X, K, G, D, C);
/* extra space needed to translate mode transition region */ /* extra space needed to translate mode transition region */
if (slog2) if (slog2)
@ -824,8 +823,7 @@ namespace Kernel
/* translate mode transition region globally */ /* translate mode transition region globally */
tlb()->insert_translation(mtc()->VIRT_BASE, tlb()->insert_translation(mtc()->VIRT_BASE,
mtc()->phys_base(), mtc()->phys_base(),
mtc()->SIZE_LOG2, mtc()->SIZE_LOG2, flags,
W, X, K, G, D, C,
(void *)aligned_es); (void *)aligned_es);
} }
} }

View File

@ -33,6 +33,13 @@ class Tlb : public Arm_v7::Section_table
void * operator new (Genode::size_t, void * p) { return p; } void * operator new (Genode::size_t, void * p) { return p; }
}; };
/**
* Board specific mapping attributes
*/
struct Page_flags : Arm::Page_flags { };
typedef Arm::page_flags_t page_flags_t;
/** /**
* TLB of core * TLB of core
* *
@ -45,13 +52,9 @@ class Core_tlb : public Tlb
Core_tlb() Core_tlb()
{ {
using namespace Genode; using namespace Genode;
map_core_area(Board::RAM_0_BASE, Board::RAM_0_SIZE, 0);
/* map RAM */ map_core_area(Board::MMIO_0_BASE, Board::MMIO_0_SIZE, 1);
translate_dpm_off(Board::RAM_0_BASE, Board::RAM_0_SIZE, 0, 1); map_core_area(Board::MMIO_1_BASE, Board::MMIO_1_SIZE, 1);
/* map MMIO */
translate_dpm_off(Board::MMIO_0_BASE, Board::MMIO_0_SIZE, 1, 0);
translate_dpm_off(Board::MMIO_1_BASE, Board::MMIO_1_SIZE, 1, 0);
} }
}; };

View File

@ -33,6 +33,13 @@ class Tlb : public Arm_v7::Section_table
void * operator new (Genode::size_t, void * p) { return p; } void * operator new (Genode::size_t, void * p) { return p; }
}; };
/**
* Board specific mapping attributes
*/
struct Page_flags : Arm::Page_flags { };
typedef Arm::page_flags_t page_flags_t;
/** /**
* TLB of core * TLB of core
* *
@ -45,14 +52,10 @@ class Core_tlb : public Tlb
Core_tlb() Core_tlb()
{ {
using namespace Genode; using namespace Genode;
map_core_area(Board::RAM_0_BASE, Board::RAM_0_SIZE, 0);
/* map RAM */ map_core_area(Board::RAM_1_BASE, Board::RAM_1_SIZE, 0);
translate_dpm_off(Board::RAM_0_BASE, Board::RAM_0_SIZE, 0, 1); map_core_area(Board::MMIO_0_BASE, Board::MMIO_0_SIZE, 1);
translate_dpm_off(Board::RAM_1_BASE, Board::RAM_1_SIZE, 0, 1); map_core_area(Board::MMIO_1_BASE, Board::MMIO_1_SIZE, 1);
/* map MMIO */
translate_dpm_off(Board::MMIO_0_BASE, Board::MMIO_0_SIZE, 1, 0);
translate_dpm_off(Board::MMIO_1_BASE, Board::MMIO_1_SIZE, 1, 0);
} }
}; };

View File

@ -57,16 +57,17 @@ void Ipc_pager::resolve_and_wait_for_fault()
/* valid mapping? */ /* valid mapping? */
assert(_mapping.valid()); assert(_mapping.valid());
/* do we need extra space to resolve pagefault? */ /* prepare mapping */
Tlb * const tlb = _pagefault.tlb; Tlb * const tlb = _pagefault.tlb;
enum { X = 1, K = 0, G = 0 }; page_flags_t const flags =
bool c = !_mapping.write_combined && !_mapping.io_mem; Page_flags::resolve_and_wait_for_fault(_mapping.writable,
bool d = _mapping.io_mem; _mapping.write_combined,
_mapping.io_mem);
/* insert mapping into TLB */ /* insert mapping into TLB */
unsigned sl2 = tlb->insert_translation(_mapping.virt_address, unsigned sl2;
_mapping.phys_address, _mapping.size_log2, sl2 = tlb->insert_translation(_mapping.virt_address, _mapping.phys_address,
_mapping.writable, X, K, G, d, c); _mapping.size_log2, flags);
if (sl2) if (sl2)
{ {
/* try to get some natural aligned space */ /* try to get some natural aligned space */
@ -76,8 +77,7 @@ void Ipc_pager::resolve_and_wait_for_fault()
/* try to translate again with extra space */ /* try to translate again with extra space */
sl2 = tlb->insert_translation(_mapping.virt_address, sl2 = tlb->insert_translation(_mapping.virt_address,
_mapping.phys_address, _mapping.phys_address,
_mapping.size_log2, _mapping.size_log2, flags, space);
_mapping.writable, X, K, G, d, c, space);
assert(!sl2); assert(!sl2);
} }
/* try to wake up faulter */ /* try to wake up faulter */

View File

@ -33,28 +33,34 @@ class Tlb : public Arm_v7::Section_table
void * operator new (Genode::size_t, void * p) { return p; } void * operator new (Genode::size_t, void * p) { return p; }
}; };
/**
* Board specific mapping attributes
*/
struct Page_flags : Arm::Page_flags { };
typedef Arm::page_flags_t page_flags_t;
/** /**
* TLB of core * TLB of core
*
* Must ensure that core never gets a pagefault.
*/ */
class Core_tlb : public Tlb class Core_tlb : public Tlb
{ {
public: public:
/**
* Constructor
*
* Must ensure that core never gets a pagefault.
*/
Core_tlb() Core_tlb()
{ {
using namespace Genode; using namespace Genode;
map_core_area(Board::RAM_0_BASE, Board::RAM_0_SIZE, 0);
/* map RAM */ map_core_area(Board::RAM_1_BASE, Board::RAM_1_SIZE, 0);
translate_dpm_off(Board::RAM_0_BASE, Board::RAM_0_SIZE, 0, 1); map_core_area(Board::RAM_2_BASE, Board::RAM_2_SIZE, 0);
translate_dpm_off(Board::RAM_1_BASE, Board::RAM_1_SIZE, 0, 1); map_core_area(Board::RAM_3_BASE, Board::RAM_3_SIZE, 0);
translate_dpm_off(Board::RAM_2_BASE, Board::RAM_2_SIZE, 0, 1); map_core_area(Board::MMIO_0_BASE, Board::MMIO_0_SIZE, 1);
translate_dpm_off(Board::RAM_3_BASE, Board::RAM_3_SIZE, 0, 1); map_core_area(Board::MMIO_1_BASE, Board::MMIO_1_SIZE, 1);
/* map MMIO */
translate_dpm_off(Board::MMIO_0_BASE, Board::MMIO_0_SIZE, 1, 0);
translate_dpm_off(Board::MMIO_1_BASE, Board::MMIO_1_SIZE, 1, 0);
} }
}; };