Pārlūkot izejas kodu

Ported UTP functionality from linux kernel.

Signed-off-by: Trace Russell <b45800@freescale.com>
Trace Russell 11 gadi atpakaļ
vecāks
revīzija
8a4c1509bd

+ 35 - 2
drivers/usb/gadget/f_mass_storage.c

@@ -428,8 +428,14 @@ struct fsg_dev {
 
 	struct usb_ep		*bulk_in;
 	struct usb_ep		*bulk_out;
+#ifdef CONFIG_FSL_UTP
+	void			*utp;
+#endif
 };
 
+#ifdef CONFIG_FSL_UTP
+#include "fsl_updater.h"
+#endif
 
 static inline int __fsg_is_set(struct fsg_common *common,
 			       const char *func, unsigned line)
@@ -1166,6 +1172,13 @@ static int do_request_sense(struct fsg_common *common, struct fsg_buffhd *bh)
 	}
 #endif
 
+#ifdef CONFIG_FSL_UTP
+	if (utp_get_sense(fsg) == 0) {	/* got the sense from the UTP */
+		sd = UTP_CTX(fsg)->sd;
+		sdinfo = UTP_CTX(fsg)->sdinfo;
+		valid = 0;
+	} else
+#endif
 	if (!curlun) {		/* Unsupported LUNs are okay */
 		common->bad_lun_okay = 1;
 		sd = SS_LOGICAL_UNIT_NOT_SUPPORTED;
@@ -1185,6 +1198,9 @@ static int do_request_sense(struct fsg_common *common, struct fsg_buffhd *bh)
 	buf[7] = 18 - 8;			/* Additional sense length */
 	buf[12] = ASC(sd);
 	buf[13] = ASCQ(sd);
+#ifdef CONFIG_FSL_UTP
+	put_unaligned_be32(UTP_CTX(fsg)->sdinfo_h, &buf[8]);
+#endif
 	return 18;
 }
 
@@ -1822,6 +1838,13 @@ static int do_scsi_command(struct fsg_common *common)
 	common->phase_error = 0;
 	common->short_packet_received = 0;
 
+#ifdef CONFIG_FSL_UTP
+	reply = utp_handle_message(fsg, fsg->cmnd, reply);
+
+	if (reply != -EINVAL)
+		return reply;
+#endif
+
 	down_read(&common->filesem);	/* We're using the backing file */
 	switch (common->cmnd[0]) {
 
@@ -2509,10 +2532,11 @@ static struct fsg_common *fsg_common_init(struct fsg_common *common,
 
 	for (i = 0; i < nluns; i++) {
 		common->luns[i].removable = 1;
-
+#ifndef CONFIG_FSL_UTP
 		rc = fsg_lun_open(&common->luns[i], "");
 		if (rc)
 			goto error_luns;
+#endif
 	}
 	common->lun = 0;
 
@@ -2668,11 +2692,16 @@ static void fsg_unbind(struct usb_configuration *c, struct usb_function *f)
 		fsg->common->new_fsg = NULL;
 		raise_exception(fsg->common, FSG_STATE_CONFIG_CHANGE);
 	}
-
+#ifdef CONFIG_FSL_UTP
+	utp_exit(fsg);
+#endif
 	free(fsg->function.descriptors);
 	free(fsg->function.hs_descriptors);
 	kfree(fsg);
 }
+#ifdef CONFIG_FSL_UTP
+#include "fsl_updater.c"
+#endif
 
 static int fsg_bind(struct usb_configuration *c, struct usb_function *f)
 {
@@ -2689,6 +2718,10 @@ static int fsg_bind(struct usb_configuration *c, struct usb_function *f)
 	fsg_intf_desc.bInterfaceNumber = i;
 	fsg->interface_number = i;
 
+#ifdef CONFIG_FSL_UTP
+	utp_init(fsg);
+#endif
+
 	/* Find all the endpoints we will use */
 	ep = usb_ep_autoconfig(gadget, &fsg_fs_bulk_in_desc);
 	if (!ep)

+ 457 - 0
drivers/usb/gadget/fsl_updater.c

@@ -0,0 +1,457 @@
+/*
+ * Freescale UUT driver
+ *
+ * Copyright 2008-2013 Freescale Semiconductor, Inc.
+ * Copyright 2008-2009 Embedded Alley Solutions, Inc All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+static u64 get_be64(u8 *buf)
+{
+	return ((u64)get_unaligned_be32(buf) << 32) |
+		get_unaligned_be32(buf + 4);
+}
+
+static int utp_init(struct fsg_common *common)
+{
+	/* the max message is 64KB */
+	utp_context.buffer = vmalloc(0x10000);
+	if (!utp_context.buffer)
+		return -EIO;
+	utp_context.utp_version = 0x1ull;
+	common->fsg->utp = &utp_context;
+	return 0;
+}
+
+static void utp_exit(struct fsg_common *common)
+{
+	vfree(utp_context.buffer);
+}
+
+static struct utp_user_data *utp_user_data_alloc(size_t size)
+{
+	struct utp_user_data *uud;
+
+	uud = kzalloc(size + sizeof(*uud), GFP_KERNEL);
+	if (!uud)
+		return uud;
+	uud->data.size = size + sizeof(uud->data);
+	return uud;
+}
+
+static void utp_user_data_free(struct utp_user_data *uud)
+{
+	kfree(uud);
+}
+
+/* The routine will not go on if utp_context.queue is empty */
+#define WAIT_ACTIVITY(queue) \
+ wait_event_interruptible(utp_context.wq, !list_empty(&utp_context.queue))
+
+/* Will be called when the host wants to get the sense data */
+static int utp_get_sense(struct fsg_commmon *common)
+{
+	struct fsg_dev *fsg;
+
+	fsg = common->fsg;
+	if (UTP_CTX(fsg)->processed == 0)
+		return -1;
+
+	UTP_CTX(fsg)->processed = 0;
+	return 0;
+}
+
+static int utp_do_read(struct fsg_common *common, void *data, size_t size)
+{
+	struct fsg_buffhd	*bh;
+	struct fsg_dev		*fsg;
+	int			rc;
+	u32			amount_left;
+	unsigned int		amount;
+
+	/* Get the starting Logical Block Address and check that it's
+	 * not too big */
+
+	fsg = common->fsg;
+	amount_left = size;
+	if (unlikely(amount_left == 0))
+		return -EIO;		/* No default reply*/
+
+	pr_debug("%s: sending %d\n", __func__, size);
+	for (;;) {
+		/* Figure out how much we need to read:
+		 * Try to read the remaining amount.
+		 * But don't read more than the buffer size.
+		 * And don't try to read past the end of the file.
+		 * Finally, if we're not at a page boundary, don't read past
+		 *	the next page.
+		 * If this means reading 0 then we were asked to read past
+		 *	the end of file. */
+		amount = min((unsigned int) amount_left, mod_data.buflen);
+
+		/* Wait for the next buffer to become available */
+		bh = fsg->next_buffhd_to_fill;
+		while (bh->state != BUF_STATE_EMPTY) {
+			rc = sleep_thread(fsg);
+			if (rc)
+				return rc;
+		}
+
+		/* If we were asked to read past the end of file,
+		 * end with an empty buffer. */
+		if (amount == 0) {
+			bh->inreq->length = 0;
+			bh->state = BUF_STATE_FULL;
+			break;
+		}
+
+		/* Perform the read */
+		pr_info("Copied to %p, %d bytes started from %d\n",
+				bh->buf, amount, size - amount_left);
+		/* from upt buffer to file_storeage buffer */
+		memcpy(bh->buf, data + size - amount_left, amount);
+		amount_left  -= amount;
+		fsg->residue -= amount;
+
+		bh->inreq->length = amount;
+		bh->state = BUF_STATE_FULL;
+
+		/* Send this buffer and go read some more */
+		bh->inreq->zero = 0;
+
+		/* USB Physical transfer: Data from device to host */
+		start_transfer(fsg, fsg->bulk_in, bh->inreq,
+				&bh->inreq_busy, &bh->state);
+
+		fsg->next_buffhd_to_fill = bh->next;
+
+		if (amount_left <= 0)
+			break;
+	}
+
+	return size - amount_left;
+}
+
+static int utp_do_write(struct fsg_common *common, void *data, size_t size)
+{
+	struct fsg_buffhd	*bh;
+	struct fsg_dev		*fsg;
+	int			get_some_more;
+	u32			amount_left_to_req, amount_left_to_write;
+	unsigned int		amount;
+	int			rc;
+	loff_t			offset;
+
+	fsg = common->fsg;
+	/* Carry out the file writes */
+	get_some_more = 1;
+	amount_left_to_req = amount_left_to_write = size;
+
+	if (unlikely(amount_left_to_write == 0))
+		return -EIO;
+
+	offset = 0;
+	while (amount_left_to_write > 0) {
+
+		/* Queue a request for more data from the host */
+		bh = fsg->next_buffhd_to_fill;
+		if (bh->state == BUF_STATE_EMPTY && get_some_more) {
+
+			/* Figure out how much we want to get:
+			 * Try to get the remaining amount.
+			 * But don't get more than the buffer size.
+			 * And don't try to go past the end of the file.
+			 * If we're not at a page boundary,
+			 *	don't go past the next page.
+			 * If this means getting 0, then we were asked
+			 *	to write past the end of file.
+			 * Finally, round down to a block boundary. */
+			amount = min(amount_left_to_req, mod_data.buflen);
+
+			if (amount == 0) {
+				get_some_more = 0;
+				/* cry now */
+				continue;
+			}
+
+			/* Get the next buffer */
+			amount_left_to_req -= amount;
+			if (amount_left_to_req == 0)
+				get_some_more = 0;
+
+			/* amount is always divisible by 512, hence by
+			 * the bulk-out maxpacket size */
+			bh->outreq->length = bh->bulk_out_intended_length =
+					amount;
+			bh->outreq->short_not_ok = 1;
+			start_transfer(fsg, fsg->bulk_out, bh->outreq,
+					&bh->outreq_busy, &bh->state);
+			fsg->next_buffhd_to_fill = bh->next;
+			continue;
+		}
+
+		/* Write the received data to the backing file */
+		bh = fsg->next_buffhd_to_drain;
+		if (bh->state == BUF_STATE_EMPTY && !get_some_more)
+			break;			/* We stopped early */
+		if (bh->state == BUF_STATE_FULL) {
+			fsg->next_buffhd_to_drain = bh->next;
+			bh->state = BUF_STATE_EMPTY;
+
+			/* Did something go wrong with the transfer? */
+			if (bh->outreq->status != 0)
+				/* cry again, COMMUNICATION_FAILURE */
+				break;
+
+			amount = bh->outreq->actual;
+
+			/* Perform the write */
+			memcpy(data + offset, bh->buf, amount);
+
+			offset += amount;
+			if (signal_pending(current))
+				return -EINTR;		/* Interrupted!*/
+			amount_left_to_write -= amount;
+			fsg->residue -= amount;
+
+			/* Did the host decide to stop early? */
+			if (bh->outreq->actual != bh->outreq->length) {
+				fsg->short_packet_received = 1;
+				break;
+			}
+			continue;
+		}
+
+		/* Wait for something to happen */
+		rc = sleep_thread(fsg);
+		if (rc)
+			return rc;
+	}
+
+	return -EIO;
+}
+
+static inline void utp_set_sense(struct fsg_common *common, u16 code, u64 reply)
+{
+	struct fsg_dev *fsg;
+
+	fsg = common->fsg;
+	UTP_CTX(fsg)->processed = true;
+	UTP_CTX(fsg)->sdinfo = reply & 0xFFFFFFFF;
+	UTP_CTX(fsg)->sdinfo_h = (reply >> 32) & 0xFFFFFFFF;
+	UTP_CTX(fsg)->sd = (UTP_SENSE_KEY << 16) | code;
+}
+
+#if 0
+static void utp_poll(struct fsg_dev *fsg)
+{
+	struct utp_context *ctx = UTP_CTX(fsg);
+	struct utp_user_data *uud = NULL;
+
+	mutex_lock(&ctx->lock);
+	if (!list_empty(&ctx->write))
+		uud = list_first_entry(&ctx->write, struct utp_user_data, link);
+	mutex_unlock(&ctx->lock);
+
+	if (uud) {
+		if (uud->data.flags & UTP_FLAG_STATUS) {
+			printk(KERN_WARNING "%s: exit with status %d\n",
+					__func__, uud->data.status);
+			UTP_SS_EXIT(fsg, uud->data.status);
+		} else {
+			pr_debug("%s: pass\n", __func__);
+			UTP_SS_PASS(fsg);
+		}
+		utp_user_data_free(uud);
+	} else {
+		pr_debug("%s: still busy...\n", __func__);
+		UTP_SS_BUSY(fsg, --ctx->counter);
+	}
+}
+#endif
+
+static int utp_exec(struct fsg_common *common,
+		    char *command,
+		    int cmdsize,
+		    unsigned long long payload)
+{
+	struct fsg_dev *fsg = common->fsg;
+	struct utp_user_data *uud = NULL, *uud2r;
+	struct utp_context *ctx = UTP_CTX(fsg);
+
+	uud2r = utp_user_data_alloc(cmdsize + 1);
+	uud2r->data.flags = UTP_FLAG_COMMAND;
+	uud2r->data.payload = payload;
+	strncpy(uud2r->data.command, command, cmdsize);
+
+	uud = utp_interpret_message(uud2r); /* TODO */
+
+	if (command[0] == '!')	/* there will be no response */
+		return 0;
+#ifdef DEBUG
+	pr_info("UUD:\n\tFlags = %02X\n", uud->data.flags);
+	if (uud->data.flags & UTP_FLAG_DATA) {
+		pr_info("\tbufsize = %d\n", uud->data.bufsize);
+		print_hex_dump(KERN_DEBUG, "\t", DUMP_PREFIX_NONE,
+			16, 2, uud->data.data, uud->data.bufsize, true);
+	}
+	if (uud->data.flags & UTP_FLAG_REPORT_BUSY)
+		pr_info("\tBUSY\n");
+#endif
+
+	if (uud->data.flags & UTP_FLAG_DATA) {
+		memcpy(ctx->buffer, uud->data.data, uud->data.bufsize);
+		UTP_SS_SIZE(fsg, uud->data.bufsize);
+	} else if (uud->data.flags & UTP_FLAG_REPORT_BUSY) {
+		ctx->counter = 0xFFFF;
+		UTP_SS_BUSY(fsg, ctx->counter);
+	} else if (uud->data.flags & UTP_FLAG_STATUS) {
+		printk(KERN_WARNING "%s: exit with status %d\n", __func__,
+				uud->data.status);
+		UTP_SS_EXIT(fsg, uud->data.status);
+	} else {
+		pr_debug("%s: pass\n", __func__);
+		UTP_SS_PASS(fsg);
+	}
+	utp_user_data_free(uud);
+	return 0;
+}
+
+static int utp_send_status(struct fsg_common *common)
+{
+	struct fsg_buffhd	*bh;
+	struct fsg_dev		*fsg = common->fsg;
+	u8			status = USB_STATUS_PASS;
+	struct bulk_cs_wrap	*csw;
+	int 			rc;
+
+	/* Wait for the next buffer to become available */
+	bh = fsg->next_buffhd_to_fill;
+	while (bh->state != BUF_STATE_EMPTY) {
+		rc = sleep_thread(fsg);
+		if (rc)
+			return rc;
+	}
+
+	if (fsg->phase_error) {
+		DBG(fsg, "sending phase-error status\n");
+		status = USB_STATUS_PHASE_ERROR;
+
+	} else if ((UTP_CTX(fsg)->sd & 0xFFFF) != UTP_REPLY_PASS) {
+		status = USB_STATUS_FAIL;
+	}
+
+	csw = bh->buf;
+
+	/* Store and send the Bulk-only CSW */
+	csw->Signature = __constant_cpu_to_le32(USB_BULK_CS_SIG);
+	csw->Tag = fsg->tag;
+	csw->Residue = cpu_to_le32(fsg->residue);
+	csw->Status = status;
+
+	bh->inreq->length = USB_BULK_CS_WRAP_LEN;
+	bh->inreq->zero = 0;
+	start_transfer(fsg, fsg->bulk_in, bh->inreq,
+			&bh->inreq_busy, &bh->state);
+	fsg->next_buffhd_to_fill = bh->next;
+	return 0;
+}
+
+static int utp_handle_message(struct fsg_common *common,
+			      char *cdb_data,
+			      int default_reply)
+{
+	struct utp_msg *m = (struct utp_msg *)cdb_data;
+	struct fsg_dev *fsg = common->fsg;
+	void *data = NULL;
+	int r;
+	struct utp_user_data *uud2r, *uud;
+	unsigned long long param;
+	unsigned long tag;
+
+	if (m->f0 != 0xF0)
+		return default_reply;
+
+	tag = get_unaligned_be32((void *)&m->utp_msg_tag);
+	param = get_be64((void *)&m->param);
+	pr_debug("Type 0x%x, tag 0x%08lx, param %llx\n",
+			m->utp_msg_type, tag, param);
+
+	switch ((enum utp_msg_type)m->utp_msg_type) {
+
+	case UTP_POLL:
+		if (get_be64((void *)&m->param) == 1) {
+			pr_debug("%s: version request\n", __func__);
+			UTP_SS_EXIT(fsg, UTP_CTX(fsg)->utp_version);
+			break;
+		}
+		/* utp_poll(fsg); */
+		break;
+	case UTP_EXEC:
+		pr_debug("%s: EXEC\n", __func__);
+		data = kzalloc(fsg->data_size, GFP_KERNEL);
+		/* copy data from usb buffer to utp buffer */
+		utp_do_write(fsg, data, fsg->data_size);
+		utp_exec(fsg, data, fsg->data_size, param);
+		kfree(data);
+		break;
+	case UTP_GET: /* data from device to host */
+		pr_debug("%s: GET, %d bytes\n", __func__, fsg->data_size);
+		r = utp_do_read(fsg, UTP_CTX(fsg)->buffer, fsg->data_size);
+		UTP_SS_PASS(fsg);
+		break;
+	case UTP_PUT: /* data from host to device */
+		pr_debug("%s: PUT, %d bytes\n", __func__, fsg->data_size);
+		uud2r = utp_user_data_alloc(fsg->data_size);
+		uud2r->data.bufsize = fsg->data_size;
+		uud2r->data.flags = UTP_FLAG_DATA;
+		utp_do_write(fsg, uud2r->data.data, fsg->data_size);
+		/* don't know what will be written */
+		uud = utp_interpret_message(uud2r); /* TODO */
+		/*
+		 * Return PASS or FAIL according to uuc's status
+		 * Please open it if need to check uuc's status
+		 * and use another version uuc
+		 */
+#if 0
+		struct utp_user_data *uud = NULL;
+		struct utp_context *ctx;
+		WAIT_ACTIVITY(write);
+		ctx = UTP_CTX(fsg);
+		mutex_lock(&ctx->lock);
+
+		if (!list_empty(&ctx->write))
+			uud = list_first_entry(&ctx->write,
+					struct utp_user_data, link);
+
+		mutex_unlock(&ctx->lock);
+		if (uud) {
+			if (uud->data.flags & UTP_FLAG_STATUS) {
+				printk(KERN_WARNING "%s: exit with status %d\n",
+					 __func__, uud->data.status);
+				UTP_SS_EXIT(fsg, uud->data.status);
+			} else {
+				pr_debug("%s: pass\n", __func__);
+				UTP_SS_PASS(fsg);
+			}
+			utp_user_data_free(uud);
+		} else{
+			UTP_SS_PASS(fsg);
+		}
+#endif
+		UTP_SS_PASS(fsg);
+		break;
+	}
+
+	utp_send_status(fsg);
+	return -1;
+}
+

+ 142 - 0
drivers/usb/gadget/fsl_updater.h

@@ -0,0 +1,142 @@
+/*
+ * Freescale UUT driver
+ *
+ * Copyright 2008-2013 Freescale Semiconductor, Inc.
+ * Copyright 2008-2009 Embedded Alley Solutions, Inc All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#ifndef __FSL_UPDATER_H
+#define __FSL_UPDATER_H
+
+#include <linux/miscdevice.h>
+#include <linux/list.h>
+#include <linux/vmalloc.h>
+#include <linux/ioctl.h>
+#include <mach/hardware.h>
+
+static int utp_init(struct fsg_dev *fsg);
+static void utp_exit(struct fsg_dev *fsg);
+static ssize_t utp_file_read(struct file *file,
+			     char __user *buf,
+			     size_t size,
+			     loff_t *off);
+
+static ssize_t utp_file_write(struct file *file,
+			      const char __user *buf,
+			      size_t size,
+			      loff_t *off);
+
+static int utp_ioctl(struct inode *inode, struct file *file,
+	      unsigned int cmd, unsigned long arg);
+static struct utp_user_data *utp_user_data_alloc(size_t size);
+static void utp_user_data_free(struct utp_user_data *uud);
+static int utp_get_sense(struct fsg_dev *fsg);
+static int utp_do_read(struct fsg_dev *fsg, void *data, size_t size);
+static int utp_do_write(struct fsg_dev *fsg, void *data, size_t size);
+static inline void utp_set_sense(struct fsg_dev *fsg, u16 code, u64 reply);
+static int utp_handle_message(struct fsg_dev *fsg,
+			      char *cdb_data,
+			      int default_reply);
+
+#define UTP_REPLY_PASS		0
+#define UTP_REPLY_EXIT		0x8001
+#define UTP_REPLY_BUSY		0x8002
+#define UTP_REPLY_SIZE		0x8003
+#define UTP_SENSE_KEY		9
+
+#define UTP_MINOR		222
+/* MISC_DYNAMIC_MINOR would be better, but... */
+
+#define UTP_COMMAND_SIZE	80
+
+#define UTP_SS_EXIT(fsg, r) 	utp_set_sense(fsg, UTP_REPLY_EXIT, (u64)r)
+#define UTP_SS_PASS(fsg)	utp_set_sense(fsg, UTP_REPLY_PASS, 0)
+#define UTP_SS_BUSY(fsg, r)	utp_set_sense(fsg, UTP_REPLY_BUSY, (u64)r)
+#define UTP_SS_SIZE(fsg, r)	utp_set_sense(fsg, UTP_REPLY_SIZE, (u64)r)
+
+#define	UTP_IOCTL_BASE	'U'
+#define	UTP_GET_CPU_ID	_IOR(UTP_IOCTL_BASE, 0, int)
+/* the structure of utp message which is mapped to 16-byte SCSI CBW's CDB */
+#pragma pack(1)
+struct utp_msg {
+	u8  f0;
+	u8  utp_msg_type;
+	u32 utp_msg_tag;
+	union {
+		struct {
+			u32 param_lsb;
+			u32 param_msb;
+		};
+		u64 param;
+	};
+};
+
+enum utp_msg_type {
+	UTP_POLL = 0,
+	UTP_EXEC,
+	UTP_GET,
+	UTP_PUT,
+};
+
+static struct utp_context {
+	u32 sd, sdinfo, sdinfo_h;			/* sense data */
+	int processed;
+	u8 *buffer;
+	u32 counter;
+	u64 utp_version;
+} utp_context;
+
+static const struct file_operations utp_fops = {
+	.open	= nonseekable_open,
+	.read	= utp_file_read,
+	.write	= utp_file_write,
+	.ioctl  = utp_ioctl,
+};
+
+static struct miscdevice utp_dev = {
+	.minor 	= UTP_MINOR,
+	.name 	= "utp",
+	.fops	= &utp_fops,
+};
+
+#define UTP_FLAG_COMMAND	0x00000001
+#define UTP_FLAG_DATA		0x00000002
+#define UTP_FLAG_STATUS		0x00000004
+#define UTP_FLAG_REPORT_BUSY	0x10000000
+struct utp_message {
+	u32 	flags;
+	size_t 	size;
+	union {
+		struct {
+			u64 payload;
+			char command[1];
+		};
+		struct {
+			size_t bufsize;
+			u8 data[1];
+		};
+		u32 status;
+	};
+};
+
+struct utp_user_data {
+	struct  utp_message	data;
+};
+#pragma pack()
+
+static inline struct utp_context *UTP_CTX(struct fsg_dev *fsg)
+{
+	return (struct utp_context *)fsg->utp;
+}
+
+#endif /* __FSL_UPDATER_H */
+