[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <1531374448-26532-19-git-send-email-pawell@cadence.com>
Date: Thu, 12 Jul 2018 06:47:15 +0100
From: Pawel Laszczak <pawell@...ence.com>
To: unlisted-recipients:; (no To-header on input)
CC: Greg Kroah-Hartman <gregkh@...uxfoundation.org>,
<linux-usb@...r.kernel.org>, Felipe Balbi <balbi@...nel.org>,
<linux-kernel@...r.kernel.org>, <ltyrala@...ence.com>,
<adouglas@...ence.com>, <pawell@...ence.com>
Subject: [PATCH 18/31] usb: usbssp: added handling of Port Reset event.
Patch adds functionality used during resetting USB port.
During this process driver must finish all queued transfers,
and enter the controller to default state.
Signed-off-by: Pawel Laszczak <pawell@...ence.com>
---
drivers/usb/usbssp/gadget-dbg.c | 9 +
drivers/usb/usbssp/gadget-mem.c | 100 +++++++++++
drivers/usb/usbssp/gadget.c | 304 +++++++++++++++++++++++++++++++-
drivers/usb/usbssp/gadget.h | 12 ++
4 files changed, 424 insertions(+), 1 deletion(-)
diff --git a/drivers/usb/usbssp/gadget-dbg.c b/drivers/usb/usbssp/gadget-dbg.c
index 88e4e8d38e4b..cb744e26ab39 100644
--- a/drivers/usb/usbssp/gadget-dbg.c
+++ b/drivers/usb/usbssp/gadget-dbg.c
@@ -12,6 +12,15 @@
#include "gadget.h"
+char *usbssp_get_slot_state(struct usbssp_udc *usbssp_data,
+ struct usbssp_container_ctx *ctx)
+{
+ struct usbssp_slot_ctx *slot_ctx = usbssp_get_slot_ctx(usbssp_data, ctx);
+ int state = GET_SLOT_STATE(le32_to_cpu(slot_ctx->dev_state));
+
+ return usbssp_slot_state_string(state);
+}
+
void usbssp_dbg_trace(struct usbssp_udc *usbssp_data,
void (*trace)(struct va_format *),
const char *fmt, ...)
diff --git a/drivers/usb/usbssp/gadget-mem.c b/drivers/usb/usbssp/gadget-mem.c
index fd3c0557feef..60c78d1d7f11 100644
--- a/drivers/usb/usbssp/gadget-mem.c
+++ b/drivers/usb/usbssp/gadget-mem.c
@@ -710,6 +710,106 @@ int usbssp_alloc_priv_device(struct usbssp_udc *usbssp_data, gfp_t flags)
return 0;
}
+void usbssp_copy_ep0_dequeue_into_input_ctx(struct usbssp_udc *usbssp_data)
+{
+ struct usbssp_device *priv_dev;
+ struct usbssp_ep_ctx *ep0_ctx;
+ struct usbssp_ring *ep_ring;
+
+ priv_dev = &usbssp_data->devs;
+ ep0_ctx = usbssp_get_ep_ctx(usbssp_data, priv_dev->in_ctx, 0);
+ ep_ring = priv_dev->eps[0].ring;
+ /*
+ * We don't keep track of the dequeue pointer very well after a
+ * Set TR dequeue pointer, so we're setting the dequeue pointer of the
+ * device to our enqueue pointer. This should only be called after a
+ * configured device has reset, so all control transfers should have
+ * been completed or cancelled before the reset.
+ */
+ ep0_ctx->deq = cpu_to_le64(usbssp_trb_virt_to_dma(ep_ring->enq_seg,
+ ep_ring->enqueue) | ep_ring->cycle_state);
+}
+
+/* Setup an DC private device for a Set Address command */
+int usbssp_setup_addressable_priv_dev(struct usbssp_udc *usbssp_data)
+{
+ struct usbssp_device *dev_priv;
+ struct usbssp_ep_ctx *ep0_ctx;
+ struct usbssp_slot_ctx *slot_ctx;
+ u32 max_packets;
+
+ dev_priv = &usbssp_data->devs;
+ /* Slot ID 0 is reserved */
+ if (usbssp_data->slot_id == 0 || !dev_priv->gadget) {
+ usbssp_warn(usbssp_data,
+ "Slot ID %d is not assigned to this device\n",
+ usbssp_data->slot_id);
+ return -EINVAL;
+ }
+
+ ep0_ctx = usbssp_get_ep_ctx(usbssp_data, dev_priv->in_ctx, 0);
+ slot_ctx = usbssp_get_slot_ctx(usbssp_data, dev_priv->in_ctx);
+
+ /* 3) Only the control endpoint is valid - one endpoint context */
+ slot_ctx->dev_info |= cpu_to_le32(LAST_CTX(1) /*| udev->route*/);
+
+ switch (dev_priv->gadget->speed) {
+ case USB_SPEED_SUPER_PLUS:
+ slot_ctx->dev_info |= cpu_to_le32(SLOT_SPEED_SSP);
+ max_packets = MAX_PACKET(512);
+ break;
+ case USB_SPEED_SUPER:
+ slot_ctx->dev_info |= cpu_to_le32(SLOT_SPEED_SS);
+ max_packets = MAX_PACKET(512);
+ break;
+ case USB_SPEED_HIGH:
+ slot_ctx->dev_info |= cpu_to_le32(SLOT_SPEED_HS);
+ max_packets = MAX_PACKET(64);
+ break;
+ case USB_SPEED_FULL:
+ slot_ctx->dev_info |= cpu_to_le32(SLOT_SPEED_FS);
+ max_packets = MAX_PACKET(64);
+ break;
+ case USB_SPEED_LOW:
+ slot_ctx->dev_info |= cpu_to_le32(SLOT_SPEED_LS);
+ max_packets = MAX_PACKET(8);
+ break;
+ case USB_SPEED_WIRELESS:
+ usbssp_dbg(usbssp_data,
+ "USBSSP doesn't support wireless speeds\n");
+ return -EINVAL;
+ default:
+ /* Speed was not set , this shouldn't happen. */
+ return -EINVAL;
+ }
+
+ if (!usbssp_data->devs.port_num)
+ return -EINVAL;
+
+ slot_ctx->dev_info2 |=
+ cpu_to_le32(ROOT_DEV_PORT(usbssp_data->devs.port_num));
+ slot_ctx->dev_state |= (usbssp_data->device_address & DEV_ADDR_MASK);
+
+ ep0_ctx->tx_info = EP_AVG_TRB_LENGTH(0x8);
+ /*cpu_to_le32(EP_MAX_ESIT_PAYLOAD_LO(max_esit_payload) |*/
+
+ /* Step 4 - ring already allocated */
+ /* Step 5 */
+ ep0_ctx->ep_info2 = cpu_to_le32(EP_TYPE(CTRL_EP));
+
+ /* EP 0 can handle "burst" sizes of 1, so Max Burst Size field is 0 */
+ ep0_ctx->ep_info2 |= cpu_to_le32(MAX_BURST(0) | ERROR_COUNT(3) |
+ max_packets);
+
+ ep0_ctx->deq = cpu_to_le64(dev_priv->eps[0].ring->first_seg->dma |
+ dev_priv->eps[0].ring->cycle_state);
+
+ trace_usbssp_setup_addressable_priv_device(dev_priv);
+ /* Steps 7 and 8 were done in usbssp_alloc_priv_device() */
+
+ return 0;
+}
+
struct usbssp_command *usbssp_alloc_command(struct usbssp_udc *usbssp_data,
bool allocate_completion,
gfp_t mem_flags)
diff --git a/drivers/usb/usbssp/gadget.c b/drivers/usb/usbssp/gadget.c
index 0ae4e1c07035..f198d7e308c6 100644
--- a/drivers/usb/usbssp/gadget.c
+++ b/drivers/usb/usbssp/gadget.c
@@ -72,7 +72,27 @@ void usbssp_bottom_irq(struct work_struct *work)
}
if (usbssp_data->defered_event & EVENT_USB_RESET) {
- /*TODO: implement handling of USB_RESET*/
+ __le32 __iomem *port_regs;
+ u32 temp;
+
+ usbssp_dbg(usbssp_data, "Beginning USB reset device sequence\n");
+
+ /*Reset Device Command*/
+ usbssp_data->defered_event &= ~EVENT_USB_RESET;
+ usbssp_reset_device(usbssp_data);
+ usbssp_data->devs.eps[0].ep_state |= USBSSP_EP_ENABLED;
+ usbssp_data->defered_event &= ~EVENT_DEV_CONNECTED;
+
+ usbssp_enable_device(usbssp_data);
+ if ((usbssp_data->gadget.speed == USB_SPEED_SUPER) ||
+ (usbssp_data->gadget.speed == USB_SPEED_SUPER_PLUS)) {
+ usbssp_dbg(usbssp_data, "Set U1/U2 enable\n");
+ port_regs = usbssp_get_port_io_addr(usbssp_data);
+ temp = readl(port_regs+PORTPMSC);
+ temp &= ~(PORT_U1_TIMEOUT_MASK | PORT_U2_TIMEOUT_MASK);
+ temp |= PORT_U1_TIMEOUT(1) | PORT_U2_TIMEOUT(1);
+ writel(temp, port_regs+PORTPMSC);
+ }
}
/*handle setup packet*/
@@ -495,6 +515,107 @@ int usbssp_halt_endpoint(struct usbssp_udc *usbssp_data, struct usbssp_ep *dep,
return 0;
}
+/*
+ * This submits a Reset Device Command, which will set the device state to 0,
+ * set the device address to 0, and disable all the endpoints except the default
+ * control endpoint. The USB core should come back and call
+ * usbssp_address_device(), and then re-set up the configuration.
+ *
+ * Wait for the Reset Device command to finish. Remove all structures
+ * associated with the endpoints that were disabled. Clear the input device
+ * structure? Reset the control endpoint 0 max packet size?
+ */
+int usbssp_reset_device(struct usbssp_udc *usbssp_data)
+{
+ struct usbssp_device *dev_priv;
+ struct usbssp_command *reset_device_cmd;
+ struct usbssp_slot_ctx *slot_ctx;
+ int slot_state;
+ int ret = 0;
+
+ ret = usbssp_check_args(usbssp_data, NULL, 0, false, __func__);
+ if (ret <= 0)
+ return ret;
+
+ dev_priv = &usbssp_data->devs;
+
+ /* If device is not setup, there is no point in resetting it */
+ slot_ctx = usbssp_get_slot_ctx(usbssp_data, dev_priv->out_ctx);
+ slot_state = GET_SLOT_STATE(le32_to_cpu(slot_ctx->dev_state));
+ pr_info("usbssp_reset_deviceslot_stated\n");
+ if (slot_state == SLOT_STATE_DISABLED ||
+ slot_state == SLOT_STATE_ENABLED ||
+ slot_state == SLOT_STATE_DEFAULT) {
+ usbssp_dbg(usbssp_data,
+ "Slot in DISABLED/ENABLED state - reset not allowed\n");
+ return 0;
+ }
+
+ trace_usbssp_reset_device(slot_ctx);
+
+ usbssp_dbg(usbssp_data, "Resetting device with slot ID %u\n",
+ usbssp_data->slot_id);
+ /* Allocate the command structure that holds the struct completion.
+ */
+ reset_device_cmd = usbssp_alloc_command(usbssp_data, true, GFP_ATOMIC);
+
+ if (!reset_device_cmd) {
+ usbssp_dbg(usbssp_data,
+ "Couldn't allocate command structure.\n");
+ return -ENOMEM;
+ }
+
+ /* Attempt to submit the Reset Device command to the command ring */
+ ret = usbssp_queue_reset_device(usbssp_data, reset_device_cmd);
+ if (ret) {
+ usbssp_dbg(usbssp_data,
+ "FIXME: allocate a command ring segment\n");
+ goto command_cleanup;
+ }
+ usbssp_ring_cmd_db(usbssp_data);
+
+ spin_unlock_irqrestore(&usbssp_data->irq_thread_lock,
+ usbssp_data->irq_thread_flag);
+
+ /* Wait for the Reset Device command to finish */
+ wait_for_completion(reset_device_cmd->completion);
+ spin_lock_irqsave(&usbssp_data->irq_thread_lock,
+ usbssp_data->irq_thread_flag);
+
+ /* The Reset Device command can't fail, according to spec,
+ * unless we tried to reset a slot ID that wasn't enabled,
+ * or the device wasn't in the addressed or configured state.
+ */
+ ret = reset_device_cmd->status;
+ switch (ret) {
+ case COMP_COMMAND_ABORTED:
+ case COMP_COMMAND_RING_STOPPED:
+ usbssp_warn(usbssp_data,
+ "Timeout waiting for reset device command\n");
+ ret = -ETIME;
+ goto command_cleanup;
+ case COMP_SLOT_NOT_ENABLED_ERROR: /*completion code for bad slot ID */
+ case COMP_CONTEXT_STATE_ERROR: /* completion code for same thing */
+ usbssp_dbg(usbssp_data, "Not freeing device rings.\n");
+ ret = 0;
+ goto command_cleanup;
+ case COMP_SUCCESS:
+ usbssp_dbg(usbssp_data, "Successful reset device command.\n");
+ break;
+ default:
+ usbssp_warn(usbssp_data, "Unknown completion code %u for "
+ "reset device command.\n", ret);
+ ret = -EINVAL;
+ goto command_cleanup;
+ }
+
+ ret = 0;
+
+command_cleanup:
+ usbssp_free_command(usbssp_data, reset_device_cmd);
+ return ret;
+}
+
/*
* At this point, the struct usb_device is about to go away, the device has
* disconnected, and all traffic has been stopped and the endpoints have been
@@ -605,6 +726,187 @@ int usbssp_alloc_dev(struct usbssp_udc *usbssp_data)
return 0;
}
+/*
+ * Issue an Address Device command
+ */
+static int usbssp_setup_device(struct usbssp_udc *usbssp_data,
+ enum usbssp_setup_dev setup)
+{
+ const char *act = setup == SETUP_CONTEXT_ONLY ? "context" : "address";
+ struct usbssp_device *dev_priv;
+ int ret = 0;
+ struct usbssp_slot_ctx *slot_ctx;
+ struct usbssp_input_control_ctx *ctrl_ctx;
+ u64 temp_64;
+ struct usbssp_command *command = NULL;
+ int dev_state = 0;
+ int slot_id = usbssp_data->slot_id;
+
+ if (usbssp_data->usbssp_state) {/* dying, removing or halted */
+ ret = -ESHUTDOWN;
+ goto out;
+ }
+
+ if (!slot_id) {
+ usbssp_dbg_trace(usbssp_data, trace_usbssp_dbg_address,
+ "Bad Slot ID %d", slot_id);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ dev_priv = &usbssp_data->devs;
+
+ slot_ctx = usbssp_get_slot_ctx(usbssp_data, dev_priv->out_ctx);
+ trace_usbssp_setup_device_slot(slot_ctx);
+
+ dev_state = GET_SLOT_STATE(le32_to_cpu(slot_ctx->dev_state));
+
+ if (setup == SETUP_CONTEXT_ONLY) {
+ if (dev_state == SLOT_STATE_DEFAULT) {
+ usbssp_dbg(usbssp_data,
+ "Slot already in default state\n");
+ goto out;
+ }
+ }
+
+ command = usbssp_alloc_command(usbssp_data, true, GFP_ATOMIC);
+ if (!command) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ command->in_ctx = dev_priv->in_ctx;
+
+ slot_ctx = usbssp_get_slot_ctx(usbssp_data, dev_priv->in_ctx);
+ ctrl_ctx = usbssp_get_input_control_ctx(dev_priv->in_ctx);
+
+ if (!ctrl_ctx) {
+ usbssp_warn(usbssp_data,
+ "%s: Could not get input context, bad type.\n",
+ __func__);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /*
+ * If this is the first Set Address (BSR=0) or driver trays
+ * transition to Default (BSR=1) since device plug-in or
+ * priv device reallocation after a resume with an USBSSP power loss,
+ * then set up the slot context or update device address in slot
+ * context.
+ */
+ if (!slot_ctx->dev_info || dev_state == SLOT_STATE_DEFAULT)
+ usbssp_setup_addressable_priv_dev(usbssp_data);
+
+ if (dev_state == SLOT_STATE_DEFAULT)
+ usbssp_copy_ep0_dequeue_into_input_ctx(usbssp_data);
+
+ ctrl_ctx->add_flags = cpu_to_le32(SLOT_FLAG | EP0_FLAG);
+ ctrl_ctx->drop_flags = 0;
+
+ trace_usbssp_address_ctx(usbssp_data, dev_priv->in_ctx,
+ le32_to_cpu(slot_ctx->dev_info) >> 27);
+
+ ret = usbssp_queue_address_device(usbssp_data, command,
+ dev_priv->in_ctx->dma, setup);
+
+ if (ret) {
+ usbssp_dbg_trace(usbssp_data, trace_usbssp_dbg_address,
+ "Prabably command ring segment is full");
+ goto out;
+ }
+
+ usbssp_ring_cmd_db(usbssp_data);
+
+ spin_unlock_irqrestore(&usbssp_data->irq_thread_lock,
+ usbssp_data->irq_thread_flag);
+ wait_for_completion(command->completion);
+ spin_lock_irqsave(&usbssp_data->irq_thread_lock,
+ usbssp_data->irq_thread_flag);
+
+ switch (command->status) {
+ case COMP_COMMAND_ABORTED:
+ case COMP_COMMAND_RING_STOPPED:
+ usbssp_warn(usbssp_data,
+ "Timeout while waiting for setup device command\n");
+ ret = -ETIME;
+ break;
+ case COMP_CONTEXT_STATE_ERROR:
+ case COMP_SLOT_NOT_ENABLED_ERROR:
+ usbssp_err(usbssp_data,
+ "Setup ERROR: setup %s command for slot %d.\n",
+ act, slot_id);
+ ret = -EINVAL;
+ break;
+ case COMP_INCOMPATIBLE_DEVICE_ERROR:
+ dev_warn(usbssp_data->dev,
+ "ERROR: Incompatible device for setup %s command\n",
+ act);
+ ret = -ENODEV;
+ break;
+ case COMP_SUCCESS:
+ usbssp_dbg_trace(usbssp_data, trace_usbssp_dbg_address,
+ "Successful setup %s command", act);
+ break;
+ default:
+ usbssp_err(usbssp_data,
+ "ERROR: unexpected setup %s command completion code 0x%x.\n",
+ act, command->status);
+
+ trace_usbssp_address_ctx(usbssp_data, dev_priv->out_ctx, 1);
+ ret = -EINVAL;
+ break;
+ }
+
+ if (ret)
+ goto out;
+
+ temp_64 = usbssp_read_64(usbssp_data, &usbssp_data->op_regs->dcbaa_ptr);
+ usbssp_dbg_trace(usbssp_data, trace_usbssp_dbg_address,
+ "Op regs DCBAA ptr = %#016llx", temp_64);
+ usbssp_dbg_trace(usbssp_data, trace_usbssp_dbg_address,
+ "Slot ID %d dcbaa entry @%p = %#016llx",
+ slot_id, &usbssp_data->dcbaa->dev_context_ptrs[slot_id],
+ (unsigned long long)
+ le64_to_cpu(usbssp_data->dcbaa->dev_context_ptrs[slot_id]));
+ usbssp_dbg_trace(usbssp_data, trace_usbssp_dbg_address,
+ "Output Context DMA address = %#08llx",
+ (unsigned long long)dev_priv->out_ctx->dma);
+
+ trace_usbssp_address_ctx(usbssp_data, dev_priv->in_ctx,
+ le32_to_cpu(slot_ctx->dev_info) >> 27);
+
+ slot_ctx = usbssp_get_slot_ctx(usbssp_data, dev_priv->out_ctx);
+ trace_usbssp_address_ctx(usbssp_data, dev_priv->out_ctx,
+ le32_to_cpu(slot_ctx->dev_info) >> 27);
+ /* Zero the input context control for later use */
+ ctrl_ctx->add_flags = 0;
+ ctrl_ctx->drop_flags = 0;
+
+ usbssp_dbg_trace(usbssp_data, trace_usbssp_dbg_address,
+ "Internal device address = %d",
+ le32_to_cpu(slot_ctx->dev_state) & DEV_ADDR_MASK);
+
+ if (setup == SETUP_CONTEXT_ADDRESS)
+ usbssp_status_stage(usbssp_data);
+out:
+ if (command) {
+ kfree(command->completion);
+ kfree(command);
+ }
+ return ret;
+}
+
+int usbssp_address_device(struct usbssp_udc *usbssp_data)
+{
+ return usbssp_setup_device(usbssp_data, SETUP_CONTEXT_ADDRESS);
+}
+
+int usbssp_enable_device(struct usbssp_udc *usbssp_data)
+{
+ return usbssp_setup_device(usbssp_data, SETUP_CONTEXT_ONLY);
+}
+
int usbssp_gen_setup(struct usbssp_udc *usbssp_data)
{
int retval;
diff --git a/drivers/usb/usbssp/gadget.h b/drivers/usb/usbssp/gadget.h
index 45b568d33723..5f653f3caabd 100644
--- a/drivers/usb/usbssp/gadget.h
+++ b/drivers/usb/usbssp/gadget.h
@@ -1673,6 +1673,8 @@ static inline void usbssp_write_64(struct usbssp_udc *usbssp_data,
}
/* USBSSP memory management */
+char *usbssp_get_slot_state(struct usbssp_udc *usbssp_data,
+ struct usbssp_container_ctx *ctx);
void usbssp_dbg_trace(struct usbssp_udc *usbssp_data,
void (*trace)(struct va_format *),
const char *fmt, ...);
@@ -1681,6 +1683,8 @@ void usbssp_mem_cleanup(struct usbssp_udc *usbssp_data);
int usbssp_mem_init(struct usbssp_udc *usbssp_data, gfp_t flags);
void usbssp_free_priv_device(struct usbssp_udc *usbssp_data);
int usbssp_alloc_priv_device(struct usbssp_udc *usbssp_data, gfp_t flags);
+int usbssp_setup_addressable_priv_dev(struct usbssp_udc *usbssp_data);
+void usbssp_copy_ep0_dequeue_into_input_ctx(struct usbssp_udc *usbssp_data);
unsigned int usbssp_get_endpoint_index(const struct usb_endpoint_descriptor *desc);
unsigned int usbssp_last_valid_endpoint(u32 added_ctxs);
int usbssp_ring_expansion(struct usbssp_udc *usbssp_data,
@@ -1717,6 +1721,8 @@ irqreturn_t usbssp_irq(int irq, void *priv);
int usbssp_alloc_dev(struct usbssp_udc *usbssp_data);
void usbssp_free_dev(struct usbssp_udc *usbssp_data);
+int usbssp_address_device(struct usbssp_udc *usbssp_data);
+int usbssp_enable_device(struct usbssp_udc *usbssp_data);
/* USBSSP ring, segment, TRB, and TD functions */
dma_addr_t usbssp_trb_virt_to_dma(struct usbssp_segment *seg,
union usbssp_trb *trb);
@@ -1730,6 +1736,9 @@ int usbssp_is_vendor_info_code(struct usbssp_udc *usbssp_data,
void usbssp_ring_cmd_db(struct usbssp_udc *usbssp_data);
int usbssp_queue_slot_control(struct usbssp_udc *usbssp_data,
struct usbssp_command *cmd, u32 trb_type);
+int usbssp_queue_address_device(struct usbssp_udc *usbssp_data,
+ struct usbssp_command *cmd,
+ dma_addr_t in_ctx_ptr, enum usbssp_setup_dev setup);
int usbssp_queue_stop_endpoint(struct usbssp_udc *usbssp_data,
struct usbssp_command *cmd,
unsigned int ep_index, int suspend);
@@ -1744,6 +1753,8 @@ void usbssp_cleanup_halted_endpoint(struct usbssp_udc *usbssp_data,
int usbssp_queue_halt_endpoint(struct usbssp_udc *usbssp_data,
struct usbssp_command *cmd,
unsigned int ep_index);
+int usbssp_queue_reset_device(struct usbssp_udc *usbssp_data,
+ struct usbssp_command *cmd);
void usbssp_handle_command_timeout(struct work_struct *work);
void usbssp_cleanup_command_queue(struct usbssp_udc *usbssp_data);
@@ -1778,6 +1789,7 @@ int usbssp_cmd_stop_ep(struct usbssp_udc *usbssp_data, struct usb_gadget *g,
struct usbssp_ep *ep_priv);
int usbssp_status_stage(struct usbssp_udc *usbssp_data);
+int usbssp_reset_device(struct usbssp_udc *usbssp_data);
static inline char *usbssp_slot_state_string(u32 state)
{
switch (state) {
--
2.17.1
Powered by blists - more mailing lists