gallium/gbm: Switch to auxiliary/pipe-loader.

Reviewed-by: Jakob Bornecrantz <jakob@vmware.com>
This commit is contained in:
Francisco Jerez 2012-04-20 16:31:23 +02:00
parent 66f7fd99fa
commit b52a0f2281
13 changed files with 52 additions and 574 deletions

View File

@ -1483,6 +1483,7 @@ if test "x$enable_gallium_gbm" = xyes; then
GALLIUM_STATE_TRACKERS_DIRS="gbm $GALLIUM_STATE_TRACKERS_DIRS"
GALLIUM_TARGET_DIRS="$GALLIUM_TARGET_DIRS gbm"
HAVE_ST_GBM="yes"
enable_gallium_loader=yes
fi
dnl

View File

@ -190,10 +190,7 @@ gbm_gallium_drm_destroy(struct gbm_device *gbm)
{
struct gbm_gallium_drm_device *gdrm = gbm_gallium_drm_device(gbm);
gdrm->screen->destroy(gdrm->screen);
FREE(gdrm->base.driver_name);
gallium_screen_destroy(gdrm);
FREE(gdrm);
}

View File

@ -71,4 +71,7 @@ gbm_gallium_drm_device_create(int fd);
int
gallium_screen_create(struct gbm_gallium_drm_device *gdrm);
void
gallium_screen_destroy(struct gbm_gallium_drm_device *gdrm);
#endif

View File

@ -3,192 +3,39 @@
TOP = ../../../..
include $(TOP)/configs/current
PIPE_PREFIX := pipe_
GBM_BACKEND = gbm_gallium_drm
GBM_SOURCES = gbm.c pipe_loader.c
GBM_SOURCES = gbm.c
GBM_INCLUDES = \
-I$(TOP)/include \
-I$(TOP)/src/gallium/state_trackers/gbm \
-I$(TOP)/src/gbm/main \
-I$(TOP)/src/gallium/auxiliary \
-I$(TOP)/src/gallium/include \
-I$(TOP)/src/gallium/winsys \
-I$(TOP)/src/gallium/include
GBM_LIBS = $(LIBUDEV_LIBS) $(LIBDRM_LIB) -lm \
$(TOP)/src/gallium/state_trackers/gbm/libgbm.a \
$(TOP)/src/gallium/drivers/identity/libidentity.a \
$(TOP)/src/gallium/drivers/galahad/libgalahad.a \
$(TOP)/src/gallium/drivers/trace/libtrace.a \
$(TOP)/src/gallium/drivers/rbug/librbug.a \
$(GALLIUM_AUXILIARIES)
$(GALLIUM_PIPE_LOADER_LIBS) $(GALLIUM_AUXILIARIES)
GBM_CFLAGS = \
-DGBM_BACKEND_SEARCH_DIR=\"$(INSTALL_LIB_DIR)/gbm\" \
-DPIPE_PREFIX=\"$(PIPE_PREFIX)\" \
-DPIPE_SEARCH_DIR=\"$(PIPE_INSTALL_DIR)\" \
$(GALLIUM_PIPE_LOADER_DEFINES) \
$(LIBUDEV_CFLAGS) \
$(LIBDRM_CFLAGS)
$(LIBDRM_CFLAGS)
pipe_INCLUDES = \
-I$(TOP)/include \
-I$(TOP)/src/gallium/auxiliary \
-I$(TOP)/src/gallium/drivers \
-I$(TOP)/src/gallium/include \
-I$(TOP)/src/gallium/winsys
pipe_LIBS = \
$(TOP)/src/gallium/drivers/identity/libidentity.a \
$(TOP)/src/gallium/drivers/trace/libtrace.a \
$(TOP)/src/gallium/drivers/rbug/librbug.a \
$(GALLIUM_AUXILIARIES)
# as if we are DRI modules
pipe_SYS = $(DRI_LIB_DEPS)
pipe_CLFLAGS = \
-DGALLIUM_RBUG -DGALLIUM_TRACE -DGALLIUM_GALAHAD \
$(LIBDRM_CFLAGS)
pipe_LDFLAGS = -Wl,--no-undefined
# i915 pipe driver
i915_LIBS = \
$(TOP)/src/gallium/winsys/i915/drm/libi915drm.a \
$(TOP)/src/gallium/drivers/i915/libi915.a
i915_SYS = -ldrm_intel
# nouveau pipe driver
nouveau_LIBS = \
$(TOP)/src/gallium/winsys/nouveau/drm/libnouveaudrm.a \
$(TOP)/src/gallium/drivers/nv30/libnv30.a \
$(TOP)/src/gallium/drivers/nv50/libnv50.a \
$(TOP)/src/gallium/drivers/nvc0/libnvc0.a \
$(TOP)/src/gallium/drivers/nouveau/libnouveau.a
nouveau_SYS = -ldrm_nouveau
# r300 pipe driver
r300_LIBS = \
$(TOP)/src/gallium/winsys/radeon/drm/libradeonwinsys.a \
$(TOP)/src/gallium/drivers/r300/libr300.a
r300_SYS += -ldrm_radeon
# r600 pipe driver
r600_LIBS = \
$(TOP)/src/gallium/winsys/radeon/drm/libradeonwinsys.a \
$(TOP)/src/gallium/drivers/r600/libr600.a
r600_SYS += -ldrm_radeon
# radeonsi pipe driver
radeonsi_LIBS = \
$(TOP)/src/gallium/winsys/radeon/drm/libradeonwinsys.a \
$(TOP)/src/gallium/drivers/radeonsi/libradeonsi.a
radeonsi_SYS += -ldrm_radeon
# vmwgfx pipe driver
vmwgfx_LIBS = \
$(TOP)/src/gallium/winsys/svga/drm/libsvgadrm.a \
$(TOP)/src/gallium/drivers/svga/libsvga.a
# LLVM
ifeq ($(MESA_LLVM),1)
pipe_SYS += $(LLVM_LIBS)
pipe_LDFLAGS += $(LLVM_LDFLAGS)
endif
ifneq ($(findstring llvmpipe,$(GALLIUM_DRIVERS_DIRS)),)
pipe_LIBS += $(TOP)/src/gallium/drivers/llvmpipe/libllvmpipe.a
endif
# determine the targets/sources
_pipe_TARGETS_CC =
_pipe_TARGETS_CXX =
pipe_SOURCES =
ifneq ($(findstring i915/drm,$(GALLIUM_WINSYS_DIRS)),)
_pipe_TARGETS_CC += $(PIPE_PREFIX)i915.so
pipe_SOURCES += pipe_i915.c
endif
ifneq ($(findstring nouveau/drm,$(GALLIUM_WINSYS_DIRS)),)
_pipe_TARGETS_CXX += $(PIPE_PREFIX)nouveau.so
pipe_SOURCES += pipe_nouveau.c
endif
ifneq ($(findstring radeon/drm,$(GALLIUM_WINSYS_DIRS)),)
ifneq ($(findstring r300,$(GALLIUM_DRIVERS_DIRS)),)
_pipe_TARGETS_CC += $(PIPE_PREFIX)r300.so
pipe_SOURCES += pipe_r300.c
endif
endif
ifneq ($(findstring radeon/drm,$(GALLIUM_WINSYS_DIRS)),)
ifneq ($(findstring r600,$(GALLIUM_DRIVERS_DIRS)),)
_pipe_TARGETS_CC += $(PIPE_PREFIX)r600.so
pipe_SOURCES += pipe_r600.c
endif
endif
ifneq ($(findstring radeon/drm,$(GALLIUM_WINSYS_DIRS)),)
ifneq ($(findstring radeonsi,$(GALLIUM_DRIVERS_DIRS)),)
_pipe_TARGETS_CC += $(PIPE_PREFIX)radeonsi.so
pipe_SOURCES += pipe_radeonsi.c
endif
endif
ifneq ($(findstring svga/drm,$(GALLIUM_WINSYS_DIRS)),)
_pipe_TARGETS_CC += $(PIPE_PREFIX)vmwgfx.so
pipe_SOURCES += pipe_vmwgfx.c
endif
pipe_OBJECTS = $(pipe_SOURCES:.c=.o)
ifeq ($(MESA_LLVM),1)
pipe_TARGETS_CXX = $(_pipe_TARGETS_CXX) $(_pipe_TARGETS_CC)
pipe_TARGETS_CC =
else
pipe_TARGETS_CXX = $(_pipe_TARGETS_CXX)
pipe_TARGETS_CC = $(_pipe_TARGETS_CC)
endif
GBM_EXTRA_TARGETS = $(addprefix $(TOP)/$(LIB_DIR)/gbm/, $(pipe_TARGETS_CC)) $(addprefix $(TOP)/$(LIB_DIR)/gbm/, $(pipe_TARGETS_CXX))
GBM_EXTRA_TARGETS = pipes
GBM_EXTRA_INSTALL = install-pipes
GBM_EXTRA_CLEAN = clean-pipes
GBM_EXTRA_SOURCES = $(pipe_SOURCES)
include $(TOP)/src/gbm/backends/Makefile.template
PIPE_SRC_DIR = $(TOP)/src/gallium/targets/pipe-loader
PIPE_INSTALL_DIR = $(INSTALL_LIB_DIR)/gbm
$(GBM_EXTRA_TARGETS): $(TOP)/$(LIB_DIR)/gbm/%: %
@$(INSTALL) -d $(dir $@)
$(INSTALL) $< $(dir $@)
$(pipe_TARGETS_CC): $(PIPE_PREFIX)%.so: pipe_%.o $(pipe_LIBS) $($*_LIBS)
$(MKLIB) -o $@ -noprefix -linker '$(CC)' \
-ldflags '-L$(TOP)/$(LIB_DIR) $(pipe_LDFLAGS) $(LDFLAGS)' \
$(MKLIB_OPTIONS) $< \
-Wl,--start-group $(pipe_LIBS) $($*_LIBS) -Wl,--end-group \
$(pipe_SYS) $($*_SYS)
$(pipe_TARGETS_CXX): $(PIPE_PREFIX)%.so: pipe_%.o $(pipe_LIBS) $($*_LIBS)
$(MKLIB) -o $@ -noprefix -linker '$(CXX)' \
-ldflags '-L$(TOP)/$(LIB_DIR) $(pipe_LDFLAGS) $(LDFLAGS)' \
$(MKLIB_OPTIONS) $< \
-Wl,--start-group $(pipe_LIBS) $($*_LIBS) -Wl,--end-group \
$(pipe_SYS) $($*_SYS)
$(pipe_OBJECTS): %.o: %.c
$(CC) -c -o $@ $< $(pipe_INCLUDES) $(pipe_CFLAGS) $(CFLAGS)
install-pipes: $(GBM_EXTRA_TARGETS)
$(INSTALL) -d $(DESTDIR)$(INSTALL_LIB_DIR)/gbm
for tgt in $(GBM_EXTRA_TARGETS); do \
$(MINSTALL) "$$tgt" $(DESTDIR)$(INSTALL_LIB_DIR)/gbm; \
done
pipes:
@$(MAKE) -C $(PIPE_SRC_DIR)
install-pipes:
@$(MAKE) -C $(PIPE_SRC_DIR) PIPE_INSTALL_DIR=$(PIPE_INSTALL_DIR) install
clean-pipes:
rm -f $(pipe_TARGETS)
rm -f $(pipe_OBJECTS)
@$(MAKE) -C $(PIPE_SRC_DIR) clean

View File

@ -25,36 +25,56 @@
* Benjamin Franzke <benjaminfranzke@googlemail.com>
*/
#include "util/u_inlines.h"
#include "gbm_gallium_drmint.h"
#include "pipe_loader.h"
static struct pipe_screen *
create_drm_screen(const char *name, int fd)
#include "util/u_memory.h"
#include "util/u_inlines.h"
#include "pipe-loader/pipe_loader.h"
static const char *
get_library_search_path(void)
{
struct pipe_module *pmod = get_pipe_module(name);
return (pmod && pmod->drmdd && pmod->drmdd->create_screen) ?
pmod->drmdd->create_screen(fd) : NULL;
const char *search_path = NULL;
/* don't allow setuid apps to use GBM_BACKENDS_PATH */
if (geteuid() == getuid())
search_path = getenv("GBM_BACKENDS_PATH");
if (search_path == NULL)
search_path = PIPE_SEARCH_DIR;
return search_path;
}
int
gallium_screen_create(struct gbm_gallium_drm_device *gdrm)
{
gdrm->base.driver_name = drm_fd_get_screen_name(gdrm->base.base.fd);
if (gdrm->base.driver_name == NULL)
struct pipe_loader_device *dev;
int ret;
ret = pipe_loader_drm_probe_fd(&dev, gdrm->base.base.fd);
if (!ret)
return -1;
gdrm->screen = create_drm_screen(gdrm->base.driver_name, gdrm->base.base.fd);
gdrm->screen = pipe_loader_create_screen(dev, get_library_search_path());
if (gdrm->screen == NULL) {
debug_printf("failed to load driver: %s\n", gdrm->base.driver_name);
pipe_loader_release(&dev, 1);
return -1;
};
gdrm->driver = dev;
gdrm->base.driver_name = strdup(dev->driver_name);
return 0;
}
void
gallium_screen_destroy(struct gbm_gallium_drm_device *gdrm)
{
FREE(gdrm->base.driver_name);
gdrm->screen->destroy(gdrm->screen);
pipe_loader_release((struct pipe_loader_device **)&gdrm->driver, 1);
}
GBM_EXPORT struct gbm_backend gbm_backend = {
.backend_name = "gallium_drm",
.create_device = gbm_gallium_drm_device_create,

View File

@ -1,27 +0,0 @@
#include "target-helpers/inline_debug_helper.h"
#include "state_tracker/drm_driver.h"
#include "i915/drm/i915_drm_public.h"
#include "i915/i915_public.h"
static struct pipe_screen *
create_screen(int fd)
{
struct i915_winsys *iws;
struct pipe_screen *screen;
iws = i915_drm_winsys_create(fd);
if (!iws)
return NULL;
screen = i915_screen_create(iws);
if (!screen)
return NULL;
screen = debug_screen_wrap(screen);
return screen;
}
PUBLIC
DRM_DRIVER_DESCRIPTOR("i915", "i915", create_screen, NULL)

View File

@ -1,192 +0,0 @@
/*
* Copyright © 2011 Intel Corporation
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
* Authors:
* Kristian Høgsberg <krh@bitplanet.net>
* Benjamin Franzke <benjaminfranzke@googlemail.com>
*/
#include <stdio.h>
#include "util/u_string.h"
#include "util/u_memory.h"
#include <libudev.h>
#include "gbm_gallium_drmint.h"
#include "pipe_loader.h"
#define DRIVER_MAP_GALLIUM_ONLY
#include "pci_ids/pci_id_driver_map.h"
static struct pipe_module pipe_modules[16];
static INLINE char *
loader_strdup(const char *str)
{
return mem_dup(str, strlen(str) + 1);
}
char *
drm_fd_get_screen_name(int fd)
{
struct udev *udev;
struct udev_device *device, *parent;
const char *pci_id;
char *driver = NULL;
int vendor_id, chip_id, i, j;
udev = udev_new();
device = _gbm_udev_device_new_from_fd(udev, fd);
if (device == NULL)
return NULL;
parent = udev_device_get_parent(device);
if (parent == NULL) {
fprintf(stderr, "gbm: could not get parent device");
goto out;
}
pci_id = udev_device_get_property_value(parent, "PCI_ID");
if (pci_id == NULL ||
sscanf(pci_id, "%x:%x", &vendor_id, &chip_id) != 2) {
fprintf(stderr, "gbm: malformed or no PCI ID");
goto out;
}
for (i = 0; driver_map[i].driver; i++) {
if (vendor_id != driver_map[i].vendor_id)
continue;
if (driver_map[i].num_chips_ids == -1) {
driver = loader_strdup(driver_map[i].driver);
_gbm_log("pci id for %d: %04x:%04x, driver %s",
fd, vendor_id, chip_id, driver);
goto out;
}
for (j = 0; j < driver_map[i].num_chips_ids; j++)
if (driver_map[i].chip_ids[j] == chip_id) {
driver = loader_strdup(driver_map[i].driver);
_gbm_log("pci id for %d: %04x:%04x, driver %s",
fd, vendor_id, chip_id, driver);
goto out;
}
}
out:
udev_device_unref(device);
udev_unref(udev);
return driver;
}
static void
find_pipe_module(struct pipe_module *pmod, const char *name)
{
char *search_paths, *end, *next, *p;
char path[PATH_MAX];
int ret;
search_paths = NULL;
if (geteuid() == getuid()) {
/* don't allow setuid apps to use GBM_BACKENDS_PATH */
search_paths = getenv("GBM_BACKENDS_PATH");
}
if (search_paths == NULL)
search_paths = GBM_BACKEND_SEARCH_DIR;
end = search_paths + strlen(search_paths);
for (p = search_paths; p < end && pmod->lib == NULL; p = next + 1) {
int len;
next = strchr(p, ':');
if (next == NULL)
next = end;
len = next - p;
if (len) {
ret = util_snprintf(path, sizeof(path),
"%.*s/" PIPE_PREFIX "%s" UTIL_DL_EXT, len, p, pmod->name);
}
else {
ret = util_snprintf(path, sizeof(path),
PIPE_PREFIX "%s" UTIL_DL_EXT, pmod->name);
}
if (ret > 0 && ret < sizeof(path)) {
pmod->lib = util_dl_open(path);
debug_printf("loaded %s\n", path);
}
}
}
static boolean
load_pipe_module(struct pipe_module *pmod, const char *name)
{
pmod->name = loader_strdup(name);
if (!pmod->name)
return FALSE;
find_pipe_module(pmod, name);
if (pmod->lib) {
pmod->drmdd = (const struct drm_driver_descriptor *)
util_dl_get_proc_address(pmod->lib, "driver_descriptor");
/* sanity check on the name */
if (pmod->drmdd && strcmp(pmod->drmdd->name, pmod->name) != 0)
pmod->drmdd = NULL;
if (!pmod->drmdd) {
util_dl_close(pmod->lib);
pmod->lib = NULL;
}
}
return (pmod->drmdd != NULL);
}
struct pipe_module *
get_pipe_module(const char *name)
{
struct pipe_module *pmod = NULL;
int i;
if (!name)
return NULL;
for (i = 0; i < Elements(pipe_modules); i++) {
if (!pipe_modules[i].initialized ||
strcmp(pipe_modules[i].name, name) == 0) {
pmod = &pipe_modules[i];
break;
}
}
if (!pmod)
return NULL;
if (!pmod->initialized) {
load_pipe_module(pmod, name);
pmod->initialized = TRUE;
}
return pmod;
}

View File

@ -1,48 +0,0 @@
/*
* Copyright © 2011 Intel Corporation
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
* Authors:
* Benjamin Franzke <benjaminfranzke@googlemail.com>
*/
#ifndef _PIPE_LOADER_H_
#define _PIPE_LOADER_H_
#include "pipe/p_compiler.h"
#include "util/u_dl.h"
#include "state_tracker/drm_driver.h"
struct pipe_module {
boolean initialized;
char *name;
struct util_dl_library *lib;
const struct drm_driver_descriptor *drmdd;
};
struct pipe_module *
get_pipe_module(const char *name);
char *
drm_fd_get_screen_name(int fd);
#endif

View File

@ -1,21 +0,0 @@
#include "target-helpers/inline_debug_helper.h"
#include "state_tracker/drm_driver.h"
#include "nouveau/drm/nouveau_drm_public.h"
static struct pipe_screen *
create_screen(int fd)
{
struct pipe_screen *screen;
screen = nouveau_drm_screen_create(fd);
if (!screen)
return NULL;
screen = debug_screen_wrap(screen);
return screen;
}
PUBLIC
DRM_DRIVER_DESCRIPTOR("nouveau", "nouveau", create_screen, NULL)

View File

@ -1,27 +0,0 @@
#include "target-helpers/inline_debug_helper.h"
#include "state_tracker/drm_driver.h"
#include "radeon/drm/radeon_drm_public.h"
#include "r300/r300_public.h"
static struct pipe_screen *
create_screen(int fd)
{
struct radeon_winsys *sws;
struct pipe_screen *screen;
sws = radeon_drm_winsys_create(fd);
if (!sws)
return NULL;
screen = r300_screen_create(sws);
if (!screen)
return NULL;
screen = debug_screen_wrap(screen);
return screen;
}
PUBLIC
DRM_DRIVER_DESCRIPTOR("r300", "radeon", create_screen, NULL)

View File

@ -1,26 +0,0 @@
#include "state_tracker/drm_driver.h"
#include "target-helpers/inline_debug_helper.h"
#include "radeon/drm/radeon_drm_public.h"
#include "r600/r600_public.h"
static struct pipe_screen *
create_screen(int fd)
{
struct radeon_winsys *rw;
struct pipe_screen *screen;
rw = radeon_drm_winsys_create(fd);
if (!rw)
return NULL;
screen = r600_screen_create(rw);
if (!screen)
return NULL;
screen = debug_screen_wrap(screen);
return screen;
}
PUBLIC
DRM_DRIVER_DESCRIPTOR("r600", "radeon", create_screen, NULL)

View File

@ -1,22 +0,0 @@
#include "target-helpers/inline_sw_helper.h"
#include "target-helpers/inline_debug_helper.h"
#include "state_tracker/drm_driver.h"
PUBLIC struct pipe_screen *
swrast_create_screen(struct sw_winsys *ws);
PUBLIC
DRM_DRIVER_DESCRIPTOR("swrast", NULL, NULL, NULL)
struct pipe_screen *
swrast_create_screen(struct sw_winsys *ws)
{
struct pipe_screen *screen;
screen = sw_screen_create(ws);
if (screen)
screen = debug_screen_wrap(screen);
return screen;
}

View File

@ -1,27 +0,0 @@
#include "target-helpers/inline_debug_helper.h"
#include "state_tracker/drm_driver.h"
#include "svga/drm/svga_drm_public.h"
#include "svga/svga_public.h"
static struct pipe_screen *
create_screen(int fd)
{
struct svga_winsys_screen *sws;
struct pipe_screen *screen;
sws = svga_drm_winsys_screen_create(fd);
if (!sws)
return NULL;
screen = svga_screen_create(sws);
if (!screen)
return NULL;
screen = debug_screen_wrap(screen);
return screen;
}
PUBLIC
DRM_DRIVER_DESCRIPTOR("vmwgfx", "vmwgfx", create_screen, NULL)