From cbea21e2ab658ca1256bfe5f4c535b2b1b9e4060 Mon Sep 17 00:00:00 2001 From: Ben Skeggs Date: Thu, 20 Aug 2015 14:54:16 +1000 Subject: drm/nouveau/object: implement support for new-style nvkm_object Signed-off-by: Ben Skeggs --- drivers/gpu/drm/nouveau/include/nvkm/core/gpuobj.h | 8 +- drivers/gpu/drm/nouveau/include/nvkm/core/object.h | 62 +++++- drivers/gpu/drm/nouveau/nvkm/core/gpuobj.c | 8 +- drivers/gpu/drm/nouveau/nvkm/core/ioctl.c | 17 +- drivers/gpu/drm/nouveau/nvkm/core/object.c | 227 +++++++++++++++++---- drivers/gpu/drm/nouveau/nvkm/core/ramht.c | 2 +- drivers/gpu/drm/nouveau/nvkm/core/subdev.c | 2 +- drivers/gpu/drm/nouveau/nvkm/engine/fifo/gf100.c | 4 +- drivers/gpu/drm/nouveau/nvkm/engine/fifo/gk104.c | 4 +- drivers/gpu/drm/nouveau/nvkm/subdev/devinit/nv50.c | 2 +- 10 files changed, 271 insertions(+), 65 deletions(-) diff --git a/drivers/gpu/drm/nouveau/include/nvkm/core/gpuobj.h b/drivers/gpu/drm/nouveau/include/nvkm/core/gpuobj.h index 46975785ba3c..2260aef3ec3e 100644 --- a/drivers/gpu/drm/nouveau/include/nvkm/core/gpuobj.h +++ b/drivers/gpu/drm/nouveau/include/nvkm/core/gpuobj.h @@ -68,8 +68,12 @@ void _nvkm_gpuobj_wr32(struct nvkm_object *, u64, u32); struct nvkm_gpuobj *_gpuobj = (o); \ (void)_gpuobj; \ } while(0) -#define nvkm_ro32(o,a) nv_ofuncs(o)->rd32(&(o)->object, (a)) -#define nvkm_wo32(o,a,d) nv_ofuncs(o)->wr32(&(o)->object, (a), (d)) +#define nvkm_ro32(o,a) ({ \ + u32 _data; \ + nvkm_object_rd32(&(o)->object, (a), &_data); \ + _data; \ +}) +#define nvkm_wo32(o,a,d) nvkm_object_wr32(&(o)->object, (a), (d)) #define nvkm_mo32(o,a,m,d) ({ \ u32 _addr = (a), _data = nvkm_ro32((o), _addr); \ nvkm_wo32((o), _addr, (_data & ~(m)) | (d)); \ diff --git a/drivers/gpu/drm/nouveau/include/nvkm/core/object.h b/drivers/gpu/drm/nouveau/include/nvkm/core/object.h index 39a4962d3982..84040a336036 100644 --- a/drivers/gpu/drm/nouveau/include/nvkm/core/object.h +++ b/drivers/gpu/drm/nouveau/include/nvkm/core/object.h @@ -2,6 +2,8 @@ #define __NVKM_OBJECT_H__ #include #include +struct nvkm_event; +struct nvkm_gpuobj; #define NV_PARENT_CLASS 0x80000000 #define NV_NAMEDB_CLASS 0x40000000 @@ -13,10 +15,14 @@ #define NV_ENGCTX_CLASS 0x01000000 struct nvkm_object { + const struct nvkm_object_func *func; + struct nvkm_client *client; + struct nvkm_engine *engine; + u32 oclass_name; + u32 handle; + struct nvkm_object *parent; struct nvkm_oclass *oclass; u32 pclass; - struct nvkm_object *parent; - struct nvkm_engine *engine; atomic_t refcount; atomic_t usecount; #if CONFIG_NOUVEAU_DEBUG >= NV_DBG_PARANOIA @@ -26,12 +32,44 @@ struct nvkm_object { #endif }; +struct nvkm_object_func { + void *(*dtor)(struct nvkm_object *); + int (*init)(struct nvkm_object *); + int (*fini)(struct nvkm_object *, bool suspend); + int (*mthd)(struct nvkm_object *, u32 mthd, void *data, u32 size); + int (*ntfy)(struct nvkm_object *, u32 mthd, struct nvkm_event **); + int (*map)(struct nvkm_object *, u64 *addr, u32 *size); + int (*rd08)(struct nvkm_object *, u64 addr, u8 *data); + int (*rd16)(struct nvkm_object *, u64 addr, u16 *data); + int (*rd32)(struct nvkm_object *, u64 addr, u32 *data); + int (*wr08)(struct nvkm_object *, u64 addr, u8 data); + int (*wr16)(struct nvkm_object *, u64 addr, u16 data); + int (*wr32)(struct nvkm_object *, u64 addr, u32 data); + int (*bind)(struct nvkm_object *, struct nvkm_gpuobj *, int align, + struct nvkm_gpuobj **); + int (*sclass)(struct nvkm_object *, int index, struct nvkm_oclass *); +}; + +void nvkm_object_ctor(const struct nvkm_object_func *, + const struct nvkm_oclass *, struct nvkm_object *); +int nvkm_object_new_(const struct nvkm_object_func *, + const struct nvkm_oclass *, void *data, u32 size, + struct nvkm_object **); +int nvkm_object_new(const struct nvkm_oclass *, void *data, u32 size, + struct nvkm_object **); +int nvkm_object_init(struct nvkm_object *); +int nvkm_object_fini(struct nvkm_object *, bool suspend); +int nvkm_object_mthd(struct nvkm_object *, u32 mthd, void *data, u32 size); +int nvkm_object_ntfy(struct nvkm_object *, u32 mthd, struct nvkm_event **); +int nvkm_object_map(struct nvkm_object *, u64 *addr, u32 *size); int nvkm_object_rd08(struct nvkm_object *, u64 addr, u8 *data); int nvkm_object_rd16(struct nvkm_object *, u64 addr, u16 *data); int nvkm_object_rd32(struct nvkm_object *, u64 addr, u32 *data); int nvkm_object_wr08(struct nvkm_object *, u64 addr, u8 data); int nvkm_object_wr16(struct nvkm_object *, u64 addr, u16 data); int nvkm_object_wr32(struct nvkm_object *, u64 addr, u32 data); +int nvkm_object_bind(struct nvkm_object *, struct nvkm_gpuobj *, int align, + struct nvkm_gpuobj **); static inline struct nvkm_object * nv_object(void *obj) @@ -59,6 +97,15 @@ int _nvkm_object_ctor(struct nvkm_object *, struct nvkm_object *, extern struct nvkm_ofuncs nvkm_object_ofuncs; +struct nvkm_sclass { + int minver; + int maxver; + s32 oclass; + const struct nvkm_object_func *func; + int (*ctor)(const struct nvkm_oclass *, void *data, u32 size, + struct nvkm_object **); +}; + /* Don't allocate dynamically, because lockdep needs lock_class_keys to be in * ".data". */ struct nvkm_oclass { @@ -66,6 +113,16 @@ struct nvkm_oclass { struct nvkm_ofuncs * const ofuncs; struct nvkm_omthds * const omthds; struct lock_class_key lock_class_key; + + int (*ctor)(const struct nvkm_oclass *, void *data, u32 size, + struct nvkm_object **); + struct nvkm_sclass base; + const void *priv; + const void *engn; + u64 object; + struct nvkm_client *client; + struct nvkm_object *parent; + struct nvkm_engine *engine; }; #define nv_oclass(o) nv_object(o)->oclass @@ -87,7 +144,6 @@ struct nvkm_omthds { int (*call)(struct nvkm_object *, u32, void *, u32); }; -struct nvkm_event; struct nvkm_ofuncs { int (*ctor)(struct nvkm_object *, struct nvkm_object *, struct nvkm_oclass *, void *data, u32 size, diff --git a/drivers/gpu/drm/nouveau/nvkm/core/gpuobj.c b/drivers/gpu/drm/nouveau/nvkm/core/gpuobj.c index 1ca5479ee38b..bc4b3c2d075e 100644 --- a/drivers/gpu/drm/nouveau/nvkm/core/gpuobj.c +++ b/drivers/gpu/drm/nouveau/nvkm/core/gpuobj.c @@ -182,20 +182,20 @@ u32 _nvkm_gpuobj_rd32(struct nvkm_object *object, u64 addr) { struct nvkm_gpuobj *gpuobj = nv_gpuobj(object); - struct nvkm_ofuncs *pfuncs = nv_ofuncs(gpuobj->parent); + u32 data; if (gpuobj->node) addr += gpuobj->node->offset; - return pfuncs->rd32(gpuobj->parent, addr); + nvkm_object_rd32(gpuobj->parent, addr, &data); + return data; } void _nvkm_gpuobj_wr32(struct nvkm_object *object, u64 addr, u32 data) { struct nvkm_gpuobj *gpuobj = nv_gpuobj(object); - struct nvkm_ofuncs *pfuncs = nv_ofuncs(gpuobj->parent); if (gpuobj->node) addr += gpuobj->node->offset; - pfuncs->wr32(gpuobj->parent, addr, data); + nvkm_object_wr32(gpuobj->parent, addr, data); } static struct nvkm_oclass diff --git a/drivers/gpu/drm/nouveau/nvkm/core/ioctl.c b/drivers/gpu/drm/nouveau/nvkm/core/ioctl.c index 7a15b15bfce0..6e5ff942a6da 100644 --- a/drivers/gpu/drm/nouveau/nvkm/core/ioctl.c +++ b/drivers/gpu/drm/nouveau/nvkm/core/ioctl.c @@ -152,6 +152,8 @@ nvkm_ioctl_new(struct nvkm_handle *handle, void *data, u32 size) if (ret) goto fail_ctor; + object->handle = _handle; + ret = nvkm_object_inc(object); if (ret) goto fail_init; @@ -205,7 +207,6 @@ static int nvkm_ioctl_mthd(struct nvkm_handle *handle, void *data, u32 size) { struct nvkm_object *object = handle->object; - struct nvkm_ofuncs *ofuncs = object->oclass->ofuncs; union { struct nvif_ioctl_mthd_v0 v0; } *args = data; @@ -215,8 +216,7 @@ nvkm_ioctl_mthd(struct nvkm_handle *handle, void *data, u32 size) if (nvif_unpack(args->v0, 0, 0, true)) { nvif_ioctl(object, "mthd vers %d mthd %02x\n", args->v0.version, args->v0.method); - if (ret = -ENODEV, ofuncs->mthd) - ret = ofuncs->mthd(object, args->v0.method, data, size); + ret = nvkm_object_mthd(object, args->v0.method, data, size); } return ret; @@ -296,7 +296,6 @@ static int nvkm_ioctl_map(struct nvkm_handle *handle, void *data, u32 size) { struct nvkm_object *object = handle->object; - struct nvkm_ofuncs *ofuncs = object->oclass->ofuncs; union { struct nvif_ioctl_map_v0 v0; } *args = data; @@ -305,10 +304,8 @@ nvkm_ioctl_map(struct nvkm_handle *handle, void *data, u32 size) nvif_ioctl(object, "map size %d\n", size); if (nvif_unpack(args->v0, 0, 0, false)) { nvif_ioctl(object, "map vers %d\n", args->v0.version); - if (ret = -ENODEV, ofuncs->map) { - ret = ofuncs->map(object, &args->v0.handle, - &args->v0.length); - } + ret = nvkm_object_map(object, &args->v0.handle, + &args->v0.length); } return ret; @@ -335,7 +332,6 @@ static int nvkm_ioctl_ntfy_new(struct nvkm_handle *handle, void *data, u32 size) { struct nvkm_object *object = handle->object; - struct nvkm_ofuncs *ofuncs = object->oclass->ofuncs; union { struct nvif_ioctl_ntfy_new_v0 v0; } *args = data; @@ -346,8 +342,7 @@ nvkm_ioctl_ntfy_new(struct nvkm_handle *handle, void *data, u32 size) if (nvif_unpack(args->v0, 0, 0, true)) { nvif_ioctl(object, "ntfy new vers %d event %02x\n", args->v0.version, args->v0.event); - if (ret = -ENODEV, ofuncs->ntfy) - ret = ofuncs->ntfy(object, args->v0.event, &event); + ret = nvkm_object_ntfy(object, args->v0.event, &event); if (ret == 0) { ret = nvkm_client_notify_new(object, event, data, size); if (ret >= 0) { diff --git a/drivers/gpu/drm/nouveau/nvkm/core/object.c b/drivers/gpu/drm/nouveau/nvkm/core/object.c index 1c117f0a7245..0abee7816874 100644 --- a/drivers/gpu/drm/nouveau/nvkm/core/object.c +++ b/drivers/gpu/drm/nouveau/nvkm/core/object.c @@ -24,75 +24,232 @@ #include #include +int +nvkm_object_mthd(struct nvkm_object *object, u32 mthd, void *data, u32 size) +{ + if (object->oclass) { + if (object->oclass->ofuncs->mthd) + return object->oclass->ofuncs->mthd(object, mthd, data, size); + return -ENODEV; + } + if (likely(object->func->mthd)) + return object->func->mthd(object, mthd, data, size); + return -ENODEV; +} + +int +nvkm_object_ntfy(struct nvkm_object *object, u32 mthd, + struct nvkm_event **pevent) +{ + if (object->oclass) { + if (object->oclass->ofuncs->ntfy) + return object->oclass->ofuncs->ntfy(object, mthd, pevent); + return -ENODEV; + } + if (likely(object->func->ntfy)) + return object->func->ntfy(object, mthd, pevent); + return -ENODEV; +} + +int +nvkm_object_map(struct nvkm_object *object, u64 *addr, u32 *size) +{ + if (object->oclass) { + if (object->oclass->ofuncs->map) + return object->oclass->ofuncs->map(object, addr, size); + return -ENODEV; + } + if (likely(object->func->map)) + return object->func->map(object, addr, size); + return -ENODEV; +} + int nvkm_object_rd08(struct nvkm_object *object, u64 addr, u8 *data) { - const struct nvkm_oclass *oclass = object->oclass; - if (oclass->ofuncs && oclass->ofuncs->rd08) { - *data = oclass->ofuncs->rd08(object, addr); - return 0; + if (object->oclass) { + if (object->oclass->ofuncs->rd08) { + *data = object->oclass->ofuncs->rd08(object, addr); + return 0; + } + *data = 0x00; + return -ENODEV; } - *data = 0x00; + if (likely(object->func->rd08)) + return object->func->rd08(object, addr, data); return -ENODEV; } int nvkm_object_rd16(struct nvkm_object *object, u64 addr, u16 *data) { - const struct nvkm_oclass *oclass = object->oclass; - if (oclass->ofuncs && oclass->ofuncs->rd16) { - *data = oclass->ofuncs->rd16(object, addr); - return 0; + if (object->oclass) { + if (object->oclass->ofuncs->rd16) { + *data = object->oclass->ofuncs->rd16(object, addr); + return 0; + } + *data = 0x0000; + return -ENODEV; } - *data = 0x0000; + if (likely(object->func->rd16)) + return object->func->rd16(object, addr, data); return -ENODEV; } int nvkm_object_rd32(struct nvkm_object *object, u64 addr, u32 *data) { - const struct nvkm_oclass *oclass = object->oclass; - if (oclass->ofuncs && oclass->ofuncs->rd32) { - *data = oclass->ofuncs->rd32(object, addr); - return 0; + if (object->oclass) { + if (object->oclass->ofuncs->rd32) { + *data = object->oclass->ofuncs->rd32(object, addr); + return 0; + } + *data = 0x00000000; + return -ENODEV; } - *data = 0x0000; + if (likely(object->func->rd32)) + return object->func->rd32(object, addr, data); return -ENODEV; } int nvkm_object_wr08(struct nvkm_object *object, u64 addr, u8 data) { - const struct nvkm_oclass *oclass = object->oclass; - if (oclass->ofuncs && oclass->ofuncs->wr08) { - oclass->ofuncs->wr08(object, addr, data); - return 0; + if (object->oclass) { + if (object->oclass->ofuncs->wr08) { + object->oclass->ofuncs->wr08(object, addr, data); + return 0; + } + return -ENODEV; } + if (likely(object->func->wr08)) + return object->func->wr08(object, addr, data); return -ENODEV; } int nvkm_object_wr16(struct nvkm_object *object, u64 addr, u16 data) { - const struct nvkm_oclass *oclass = object->oclass; - if (oclass->ofuncs && oclass->ofuncs->wr16) { - oclass->ofuncs->wr16(object, addr, data); - return 0; + if (object->oclass) { + if (object->oclass->ofuncs->wr16) { + object->oclass->ofuncs->wr16(object, addr, data); + return 0; + } + return -ENODEV; } + if (likely(object->func->wr16)) + return object->func->wr16(object, addr, data); return -ENODEV; } int nvkm_object_wr32(struct nvkm_object *object, u64 addr, u32 data) { - const struct nvkm_oclass *oclass = object->oclass; - if (oclass->ofuncs && oclass->ofuncs->wr32) { - oclass->ofuncs->wr32(object, addr, data); - return 0; + if (object->oclass) { + if (object->oclass->ofuncs->wr32) { + object->oclass->ofuncs->wr32(object, addr, data); + return 0; + } + return -ENODEV; } + if (likely(object->func->wr32)) + return object->func->wr32(object, addr, data); return -ENODEV; } +int +nvkm_object_bind(struct nvkm_object *object, struct nvkm_gpuobj *gpuobj, + int align, struct nvkm_gpuobj **pgpuobj) +{ + if (object->oclass) + return -ENODEV; + if (object->func->bind) + return object->func->bind(object, gpuobj, align, pgpuobj); + return -ENODEV; +} + +int +nvkm_object_fini(struct nvkm_object *object, bool suspend) +{ + if (object->oclass) + return object->oclass->ofuncs->fini(object, suspend); + if (object->func->fini) + return object->func->fini(object, suspend); + return 0; +} + +int +nvkm_object_init(struct nvkm_object *object) +{ + if (object->oclass) + return object->oclass->ofuncs->init(object); + if (object->func->init) + return object->func->init(object); + return 0; +} + +static void +nvkm_object_del(struct nvkm_object **pobject) +{ + struct nvkm_object *object = *pobject; + + if (object && object->oclass) { + object->oclass->ofuncs->dtor(object); + return; + } + + if (object && !WARN_ON(!object->func)) { + if (object->func->dtor) + *pobject = object->func->dtor(object); + kfree(*pobject); + *pobject = NULL; + } +} + +void +nvkm_object_ctor(const struct nvkm_object_func *func, + const struct nvkm_oclass *oclass, struct nvkm_object *object) +{ + object->func = func; + object->client = oclass->client; + object->engine = oclass->engine; + object->oclass_name = oclass->base.oclass; + object->handle = oclass->handle; + object->parent = oclass->parent; + atomic_set(&object->refcount, 1); + atomic_set(&object->usecount, 0); +#ifdef NVKM_OBJECT_MAGIC + object->_magic = NVKM_OBJECT_MAGIC; +#endif +} + +int +nvkm_object_new_(const struct nvkm_object_func *func, + const struct nvkm_oclass *oclass, void *data, u32 size, + struct nvkm_object **pobject) +{ + if (size == 0) { + if (!(*pobject = kzalloc(sizeof(**pobject), GFP_KERNEL))) + return -ENOMEM; + nvkm_object_ctor(func, oclass, *pobject); + return 0; + } + return -ENOSYS; +} + +static const struct nvkm_object_func +nvkm_object_func = { +}; + +int +nvkm_object_new(const struct nvkm_oclass *oclass, void *data, u32 size, + struct nvkm_object **pobject) +{ + const struct nvkm_object_func *func = + oclass->base.func ? oclass->base.func : &nvkm_object_func; + return nvkm_object_new_(func, oclass, data, size, pobject); +} + int nvkm_object_create_(struct nvkm_object *parent, struct nvkm_object *engine, struct nvkm_oclass *oclass, u32 pclass, @@ -182,12 +339,6 @@ nvkm_object_old(struct nvkm_object *parent, struct nvkm_object *engine, return 0; } -static void -nvkm_object_dtor(struct nvkm_object *object) -{ - nv_ofuncs(object)->dtor(object); -} - void nvkm_object_ref(struct nvkm_object *obj, struct nvkm_object **ref) { @@ -198,7 +349,7 @@ nvkm_object_ref(struct nvkm_object *obj, struct nvkm_object **ref) if (*ref) { int dead = atomic_dec_and_test(&(*ref)->refcount); if (dead) - nvkm_object_dtor(*ref); + nvkm_object_del(ref); } *ref = obj; @@ -227,7 +378,7 @@ nvkm_object_inc(struct nvkm_object *object) goto fail_engine; } - ret = nv_ofuncs(object)->init(object); + ret = nvkm_object_init(object); atomic_set(&object->usecount, 1); if (ret) goto fail_self; @@ -251,7 +402,7 @@ fail_parent: static int nvkm_object_decf(struct nvkm_object *object) { - nv_ofuncs(object)->fini(object, false); + nvkm_object_fini(object, false); atomic_set(&object->usecount, 0); if (object->engine) { @@ -271,7 +422,7 @@ nvkm_object_decs(struct nvkm_object *object) { int ret; - ret = nv_ofuncs(object)->fini(object, true); + ret = nvkm_object_fini(object, true); atomic_set(&object->usecount, 0); if (ret) return ret; @@ -300,7 +451,7 @@ fail_parent: } fail_engine: - nv_ofuncs(object)->init(object); + nvkm_object_init(object); return ret; } diff --git a/drivers/gpu/drm/nouveau/nvkm/core/ramht.c b/drivers/gpu/drm/nouveau/nvkm/core/ramht.c index c61bb0fa93f6..4717af0800e9 100644 --- a/drivers/gpu/drm/nouveau/nvkm/core/ramht.c +++ b/drivers/gpu/drm/nouveau/nvkm/core/ramht.c @@ -102,7 +102,7 @@ nvkm_ramht_new(struct nvkm_object *parent, struct nvkm_object *pargpu, int ret; ret = nvkm_gpuobj_create(parent, parent->engine ? - &parent->engine->subdev.object : parent, /* engine->subdev.object : NULL, /* fini(&subdev->object, false); + nvkm_object_fini(&subdev->object, false); nvkm_trace(subdev, "reset\n"); } diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/fifo/gf100.c b/drivers/gpu/drm/nouveau/nvkm/engine/fifo/gf100.c index 1fa38227059f..dd7fe30edf46 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/fifo/gf100.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/fifo/gf100.c @@ -451,8 +451,8 @@ gf100_fifo_recover_work(struct work_struct *work) for (todo = mask; engn = __ffs64(todo), todo; todo &= ~(1 << engn)) { if ((engine = (void *)nvkm_engine(fifo, engn))) { - nv_ofuncs(engine)->fini(engine, false); - WARN_ON(nv_ofuncs(engine)->init(engine)); + nvkm_object_fini(engine, false); + WARN_ON(nvkm_object_init(engine)); } } diff --git a/drivers/gpu/drm/nouveau/nvkm/engine/fifo/gk104.c b/drivers/gpu/drm/nouveau/nvkm/engine/fifo/gk104.c index 3562b791162f..9d75dbaa01c0 100644 --- a/drivers/gpu/drm/nouveau/nvkm/engine/fifo/gk104.c +++ b/drivers/gpu/drm/nouveau/nvkm/engine/fifo/gk104.c @@ -497,8 +497,8 @@ gk104_fifo_recover_work(struct work_struct *work) for (todo = mask; engn = __ffs64(todo), todo; todo &= ~(1 << engn)) { if ((engine = (void *)nvkm_engine(fifo, engn))) { - nv_ofuncs(engine)->fini(engine, false); - WARN_ON(nv_ofuncs(engine)->init(engine)); + nvkm_object_fini(engine, false); + WARN_ON(nvkm_object_init(engine)); } gk104_fifo_runlist_update(fifo, gk104_fifo_engidx(fifo, engn)); } diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/nv50.c b/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/nv50.c index bbcc3080a478..bf40509cda94 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/nv50.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/devinit/nv50.c @@ -117,7 +117,7 @@ nv50_devinit_init(struct nvkm_object *object) * the vbios engineers didn't make the scripts just work... */ if (init->base.post && ibus) - nv_ofuncs(ibus)->init(nv_object(ibus)); + nvkm_object_init(&ibus->object); ret = nvkm_devinit_init(&init->base); if (ret) -- cgit v1.2.3