add ehci and synopsys hcd driver

This commit is contained in:
sakumisu
2022-01-29 23:21:40 +08:00
committed by sakimusu
parent 8588d6943e
commit b5964103e2
4 changed files with 4533 additions and 0 deletions

213
common/usb_hc.h Normal file
View File

@@ -0,0 +1,213 @@
/**
* @file usb_hc.h
* @brief
*
* Copyright (c) 2022 sakumisu
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
*/
#ifndef _USB_HC_H
#define _USB_HC_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef void (*usbh_asynch_callback_t)(void *arg, int nbytes);
typedef void *usbh_epinfo_t;
/**
* @brief USB Endpoint Configuration.
*
* Structure containing the USB endpoint configuration.
*/
struct usbh_endpoint_cfg {
struct usbh_hubport *hport;
/** The number associated with the EP in the device
* configuration structure
* IN EP = 0x80 | \<endpoint number\>
* OUT EP = 0x00 | \<endpoint number\>
*/
uint8_t ep_addr;
/** Endpoint Transfer Type.
* May be Bulk, Interrupt, Control or Isochronous
*/
uint8_t ep_type;
uint8_t ep_interval;
/** Endpoint max packet size */
uint16_t ep_mps;
};
/**
* @brief USB Host Core Layer API
* @defgroup _usb_host_core_api USB Host Core API
* @{
*/
/**
* @brief usb host controller hardware init.
*
* @return int
*/
int usb_hc_init(void);
/**
* @brief reset roothub port
*
* @param port port index
* @return int
*/
int usbh_reset_port(const uint8_t port);
/**
* @brief get roothub port speed
*
* @param port port index
* @return return 0 means USB_SPEED_LOW, 1 means USB_SPEED_FULL and 2 means USB_SPEED_HIGH.
*/
uint8_t usbh_get_port_speed(const uint8_t port);
/**
* @brief reconfig control endpoint.
*
* @param ep A memory location provided by the caller.
* @param dev_addr device address.
* @param ep_mps control endpoint max packet size.
* @param speed port speed
* @return On success will return 0, and others indicate fail.
*/
int usbh_ep0_reconfigure(usbh_epinfo_t ep, uint8_t dev_addr, uint8_t ep_mps, uint8_t speed);
/**
* @brief Allocate and config endpoint
*
* @param ep A memory location provided by the caller in which to save the allocated endpoint info.
* @param ep_cfg Describes the endpoint info to be allocated.
* @return On success will return 0, and others indicate fail.
*/
int usbh_ep_alloc(usbh_epinfo_t *ep, const struct usbh_endpoint_cfg *ep_cfg);
/**
* @brief Free a memory in which saves endpoint info.
*
* @param ep A memory location provided by the caller in which to free the allocated endpoint info.
* @return On success will return 0, and others indicate fail.
*/
int usbh_ep_free(usbh_epinfo_t ep);
/**
* @brief Perform a control transfer.
* This is a blocking method; this method will not return until the transfer has completed.
*
* @param ep The control endpoint to send/receive the control request.
* @param setup Setup packet to be sent.
* @param buffer buffer used for sending the request and for returning any responses. This buffer must be large enough to hold the length value
* in the request description.
* @return On success will return 0, and others indicate fail.
*/
int usbh_control_transfer(usbh_epinfo_t ep, struct usb_setup_packet *setup, uint8_t *buffer);
/**
* @brief Process a request to handle a transfer descriptor. This method will
* enqueue the transfer request and wait for it to complete. Only one transfer may be queued;
* This is a blocking method; this method will not return until the transfer has completed.
*
* @param ep The IN or OUT endpoint descriptor for the device endpoint on which to perform the transfer.
* @param buffer A buffer containing the data to be sent (OUT endpoint) or received (IN endpoint).
* @param buflen The length of the data to be sent or received.
* @return On success, a non-negative value is returned that indicates the number
* of bytes successfully transferred. On a failure, a negated errno value
* is returned that indicates the nature of the failure:
*
* EAGAIN - If devices NAKs the transfer (or NYET or other error where
* it may be appropriate to restart the entire transaction).
* EPERM - If the endpoint stalls
* EIO - On a TX or data toggle error
* EPIPE - Overrun errors
*
*/
int usbh_ep_bulk_transfer(usbh_epinfo_t ep, uint8_t *buffer, uint32_t buflen);
/**
* @brief Process a request to handle a transfer descriptor. This method will
* enqueue the transfer request and wait for it to complete. Only one transfer may be queued;
* This is a blocking method; this method will not return until the transfer has completed.
*
* @param ep The IN or OUT endpoint descriptor for the device endpoint on which to perform the transfer.
* @param buffer A buffer containing the data to be sent (OUT endpoint) or received (IN endpoint).
* @param buflen The length of the data to be sent or received.
* @return On success, a non-negative value is returned that indicates the number
* of bytes successfully transferred. On a failure, a negated errno value
* is returned that indicates the nature of the failure:
*
* EAGAIN - If devices NAKs the transfer (or NYET or other error where
* it may be appropriate to restart the entire transaction).
* EPERM - If the endpoint stalls
* EIO - On a TX or data toggle error
* EPIPE - Overrun errors
*
*/
int usbh_ep_intr_transfer(usbh_epinfo_t ep, uint8_t *buffer, uint32_t buflen);
/**
* @brief Process a request to handle a transfer asynchronously. This method
* will enqueue the transfer request and return immediately. Only one transfer may be queued on a given endpoint
* When the transfer completes, the callback will be invoked with the provided argument.
*
* This method is useful for receiving interrupt transfers which may come infrequently.
*
* @param ep The IN or OUT endpoint descriptor for the device endpoint on which to perform the transfer.
* @param buffer A buffer containing the data to be sent (OUT endpoint) or received (IN endpoint).
* @param buflen The length of the data to be sent or received.
* @param callback This function will be called when the transfer completes.
* @param arg The arbitrary parameter that will be passed to the callback function when the transfer completes.
*
* @return On success will return 0, and others indicate fail.
*/
int usbh_ep_bulk_async_transfer(usbh_epinfo_t ep, uint8_t *buffer, uint32_t buflen, usbh_asynch_callback_t callback, void *arg);
/**
* @brief Process a request to handle a transfer asynchronously. This method
* will enqueue the transfer request and return immediately. Only one transfer may be queued on a given endpoint
* When the transfer completes, the callback will be invoked with the provided argument.
*
* This method is useful for receiving interrupt transfers which may come infrequently.
*
* @param ep The IN or OUT endpoint descriptor for the device endpoint on which to perform the transfer.
* @param buffer A buffer containing the data to be sent (OUT endpoint) or received (IN endpoint).
* @param buflen The length of the data to be sent or received.
* @param callback This function will be called when the transfer completes.
* @param arg The arbitrary parameter that will be passed to the callback function when the transfer completes.
*
* @return On success will return 0, and others indicate fail.
*/
int usbh_ep_intr_async_transfer(usbh_epinfo_t ep, uint8_t *buffer, uint32_t buflen, usbh_asynch_callback_t callback, void *arg);
/**
* @brief Cancel any pending syncrhonous or asynchronous transfer on an endpoint.
*
* @param ep The IN or OUT endpoint descriptor for the device endpoint on which to cancel.
* @return On success will return 0, and others indicate fail.
*/
int usb_ep_cancel(usbh_epinfo_t ep);
#ifdef __cplusplus
}
#endif
#endif

2833
port/ehci/usb_ehci.c Normal file

File diff suppressed because it is too large Load Diff

1006
port/ehci/usb_ehci.h Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,481 @@
#include "usbh_core.h"
#include "stm32f4xx_hal.h"
#define USBH_PID_SETUP 0U
#define USBH_PID_DATA 1U
#define USB_SNOPSYS_RETRY_COUNT 5
#ifndef CONFIG_USBHOST_CHANNELS
#define CONFIG_USBHOST_CHANNELS 12 /* Number of host channels */
#endif
/* This structure retains the state of one host channel. NOTE: Since there
* is only one channel operation active at a time, some of the fields in
* in the structure could be moved in struct stm32_ubhost_s to achieve
* some memory savings.
*/
struct usb_synopsys_chan {
usb_osal_sem_t waitsem; /* Channel wait semaphore */
uint8_t chidx; /* Channel index */
bool inuse; /* True: This channel is "in use" */
#ifdef CONFIG_USBHOST_ASYNCH
usbh_asynch_callback_t callback; /* Transfer complete callback */
void *arg; /* Argument that accompanies the callback */
#endif
};
/* A channel represents on uni-directional endpoint. So, in the case of the
* bi-directional, control endpoint, there must be two channels to represent
* the endpoint.
*/
struct usb_synopsys_ctrlinfo {
uint8_t inndx; /* EP0 IN control channel index */
uint8_t outndx; /* EP0 OUT control channel index */
};
struct usb_synopsys_priv {
HCD_HandleTypeDef *handle;
volatile bool connected; /* Connected to device */
volatile bool pscwait; /* True: Thread is waiting for a port event */
usb_osal_sem_t exclsem; /* Support mutually exclusive access */
struct usb_synopsys_chan chan[CONFIG_USBHOST_CHANNELS];
} g_usbhost;
/****************************************************************************
* Name: usb_synopsys_chan_alloc
*
* Description:
* Allocate a channel.
*
****************************************************************************/
static int usb_synopsys_chan_alloc(struct usb_synopsys_priv *priv)
{
int chidx;
/* Search the table of channels */
for (chidx = 0; chidx < CONFIG_USBHOST_CHANNELS; chidx++) {
/* Is this channel available? */
if (!priv->chan[chidx].inuse) {
/* Yes... make it "in use" and return the index */
priv->chan[chidx].inuse = true;
return chidx;
}
}
/* All of the channels are "in-use" */
return -EBUSY;
}
/****************************************************************************
* Name: usb_synopsys_chan_free
*
* Description:
* Free a previoiusly allocated channel.
*
****************************************************************************/
static void usb_synopsys_chan_free(struct usb_synopsys_priv *priv, int chidx)
{
/* Halt the channel */
HAL_HCD_HC_Halt(priv->handle, chidx);
/* Mark the channel available */
priv->chan[chidx].inuse = false;
}
__WEAK void usb_hc_low_level_init(void)
{
}
int usb_hc_init(void)
{
memset(&g_usbhost, 0, sizeof(struct usb_synopsys_priv));
#ifdef CONFIG_USB_HS
extern HCD_HandleTypeDef hhcd_USB_OTG_HS;
g_usbhost.handle = &hhcd_USB_OTG_HS;
#else
extern HCD_HandleTypeDef hhcd_USB_OTG_FS;
g_usbhost.handle = &hhcd_USB_OTG_FS;
#endif
g_usbhost.exclsem = usb_osal_mutex_create();
for (uint8_t i = 0; i < CONFIG_USBHOST_CHANNELS; i++) {
struct usb_synopsys_chan *chan = &g_usbhost.chan[i];
chan->chidx = i;
/* The waitsem semaphore is used for signaling and, hence, should not
* have priority inheritance enabled.
*/
chan->waitsem = usb_osal_sem_create(0);
}
usb_hc_low_level_init();
HAL_HCD_Start(g_usbhost.handle);
return 0;
}
int usbh_reset_port(const uint8_t port)
{
HAL_HCD_ResetPort(g_usbhost.handle);
return 0;
}
uint8_t usbh_get_port_speed(const uint8_t port)
{
if (HAL_HCD_GetCurrentSpeed(g_usbhost.handle) == 1) {
return USB_SPEED_FULL;
} else if (HAL_HCD_GetCurrentSpeed(g_usbhost.handle) == 2)
return USB_SPEED_LOW;
else
return USB_SPEED_HIGH;
}
int usbh_ep0_reconfigure(usbh_epinfo_t ep, uint8_t dev_addr, uint8_t ep_mps, uint8_t speed)
{
struct usb_synopsys_ctrlinfo *ep0info;
int ret;
ep0info = (struct usb_synopsys_ctrlinfo *)ep;
ret = usb_osal_mutex_take(g_usbhost.exclsem);
if (ret < 0) {
return ret;
}
if (speed == USB_SPEED_FULL) {
speed = HCD_SPEED_FULL;
} else if (speed == USB_SPEED_LOW) {
speed = HCD_SPEED_LOW;
}
ret = HAL_HCD_HC_Init(g_usbhost.handle, ep0info->outndx, 0x00, dev_addr, speed, USB_ENDPOINT_TYPE_CONTROL, ep_mps);
ret = HAL_HCD_HC_Init(g_usbhost.handle, ep0info->inndx, 0x80, dev_addr, speed, USB_ENDPOINT_TYPE_CONTROL, ep_mps);
usb_osal_mutex_give(g_usbhost.exclsem);
return ret;
}
int usbh_ep_alloc(usbh_epinfo_t *ep, const struct usbh_endpoint_cfg *ep_cfg)
{
struct usb_synopsys_ctrlinfo *ep0;
struct usbh_hubport *hport;
int chidx;
int ret;
ret = usb_osal_mutex_take(g_usbhost.exclsem);
if (ret < 0) {
return ret;
}
hport = ep_cfg->hport;
if ((ep_cfg->ep_type & USB_ENDPOINT_TYPE_MASK) == USB_ENDPOINT_TYPE_CONTROL) {
ep0 = usb_malloc(sizeof(struct usb_synopsys_ctrlinfo));
ep0->outndx = usb_synopsys_chan_alloc(&g_usbhost);
ep0->inndx = usb_synopsys_chan_alloc(&g_usbhost);
HAL_HCD_HC_Init(g_usbhost.handle, ep0->outndx, 0x00, hport->dev_addr, hport->speed, USB_ENDPOINT_TYPE_CONTROL, ep_cfg->ep_mps);
HAL_HCD_HC_Init(g_usbhost.handle, ep0->inndx, 0x80, hport->dev_addr, hport->speed, USB_ENDPOINT_TYPE_CONTROL, ep_cfg->ep_mps);
*ep = (usbh_epinfo_t)ep0;
} else {
chidx = usb_synopsys_chan_alloc(&g_usbhost);
HAL_HCD_HC_Init(g_usbhost.handle, chidx, ep_cfg->ep_addr, hport->dev_addr, hport->speed, ep_cfg->ep_type, ep_cfg->ep_mps);
if ((ep_cfg->ep_type & USB_ENDPOINT_TYPE_MASK) == USB_ENDPOINT_TYPE_BULK) {
if (g_usbhost.handle->hc[chidx].ep_is_in) {
g_usbhost.handle->hc[chidx].toggle_in = 0;
} else {
g_usbhost.handle->hc[chidx].toggle_out = 0;
}
}
*ep = (usbh_epinfo_t)chidx;
}
usb_osal_mutex_give(g_usbhost.exclsem);
return 0;
}
int usbh_ep_free(usbh_epinfo_t ep)
{
return 0;
}
int usbh_control_transfer(usbh_epinfo_t ep, struct usb_setup_packet *setup, uint8_t *buffer)
{
uint8_t retries;
int ret;
struct usb_synopsys_ctrlinfo *ep0info = (struct usb_synopsys_ctrlinfo *)ep;
ret = usb_osal_mutex_take(g_usbhost.exclsem);
if (ret < 0) {
return ret;
}
for (retries = 0; retries < USB_SNOPSYS_RETRY_COUNT; retries++) {
ret = HAL_HCD_HC_SubmitRequest(g_usbhost.handle,
ep0info->outndx, /* Pipe index */
0, /* Direction : OUT */
0, /* EP type */
USBH_PID_SETUP, /* Type Data */
(uint8_t *)setup, /* data buffer */
8, /* data length */
0); /* do ping (HS Only)*/
ret = usb_osal_sem_take(g_usbhost.chan[ep0info->outndx].waitsem);
if (ret < 0) {
return ret;
}
if (HAL_HCD_HC_GetURBState(g_usbhost.handle, ep0info->outndx) == URB_DONE) {
break;
}
}
if (retries >= USB_SNOPSYS_RETRY_COUNT) {
goto urb_timeout;
}
if (setup->wLength && buffer) {
if (setup->bmRequestType & 0x80) {
for (retries = 0; retries < USB_SNOPSYS_RETRY_COUNT; retries++) {
ret = HAL_HCD_HC_SubmitRequest(g_usbhost.handle,
ep0info->inndx, /* Pipe index */
1, /* Direction : IN */
0, /* EP type */
USBH_PID_DATA, /* Type Data */
buffer, /* data buffer */
setup->wLength, /* data length */
0); /* do ping (HS Only)*/
usb_osal_sem_take(g_usbhost.chan[ep0info->inndx].waitsem);
if (ret < 0) {
return ret;
}
if (HAL_HCD_HC_GetURBState(g_usbhost.handle, ep0info->inndx) == URB_DONE) {
break;
}
}
if (retries >= USB_SNOPSYS_RETRY_COUNT) {
goto urb_timeout;
}
for (retries = 0; retries < USB_SNOPSYS_RETRY_COUNT; retries++) {
ret = HAL_HCD_HC_SubmitRequest(g_usbhost.handle,
ep0info->outndx, /* Pipe index */
0, /* Direction : OUT */
0, /* EP type */
USBH_PID_DATA, /* Type Data */
NULL, /* data buffer */
0, /* data length */
0); /* do ping (HS Only)*/
usb_osal_sem_take(g_usbhost.chan[ep0info->outndx].waitsem);
if (ret < 0) {
return ret;
}
if (HAL_HCD_HC_GetURBState(g_usbhost.handle, ep0info->outndx) == URB_DONE) {
break;
}
}
if (retries >= USB_SNOPSYS_RETRY_COUNT) {
goto urb_timeout;
}
} else {
for (retries = 0; retries < USB_SNOPSYS_RETRY_COUNT; retries++) {
ret = HAL_HCD_HC_SubmitRequest(g_usbhost.handle,
ep0info->outndx, /* Pipe index */
0, /* Direction : OUT */
0, /* EP type */
USBH_PID_DATA, /* Type Data */
buffer, /* data buffer */
setup->wLength, /* data length */
0); /* do ping (HS Only)*/
usb_osal_sem_take(g_usbhost.chan[ep0info->outndx].waitsem);
if (ret < 0) {
return ret;
}
if (HAL_HCD_HC_GetURBState(g_usbhost.handle, ep0info->outndx) == URB_DONE) {
break;
}
}
if (retries >= USB_SNOPSYS_RETRY_COUNT) {
goto urb_timeout;
}
for (retries = 0; retries < USB_SNOPSYS_RETRY_COUNT; retries++) {
ret = HAL_HCD_HC_SubmitRequest(g_usbhost.handle,
ep0info->inndx, /* Pipe index */
1, /* Direction : IN */
0, /* EP type */
USBH_PID_DATA, /* Type Data */
NULL, /* data buffer */
0, /* data length */
0); /* do ping (HS Only)*/
usb_osal_sem_take(g_usbhost.chan[ep0info->inndx].waitsem);
if (ret < 0) {
return ret;
}
if (HAL_HCD_HC_GetURBState(g_usbhost.handle, ep0info->inndx) == URB_DONE) {
break;
}
}
if (retries >= USB_SNOPSYS_RETRY_COUNT) {
goto urb_timeout;
}
}
} else {
for (retries = 0; retries < USB_SNOPSYS_RETRY_COUNT; retries++) {
ret = HAL_HCD_HC_SubmitRequest(g_usbhost.handle,
ep0info->inndx, /* Pipe index */
1, /* Direction : IN */
0, /* EP type */
USBH_PID_DATA, /* Type Data */
NULL, /* data buffer */
0, /* data length */
0); /* do ping (HS Only)*/
usb_osal_sem_take(g_usbhost.chan[ep0info->inndx].waitsem);
if (ret < 0) {
return ret;
}
usb_osal_msleep(10);
if (HAL_HCD_HC_GetURBState(g_usbhost.handle, ep0info->inndx) == URB_DONE) {
break;
}
}
if (retries >= USB_SNOPSYS_RETRY_COUNT) {
goto urb_timeout;
}
}
usb_osal_mutex_give(g_usbhost.exclsem);
return 0;
urb_timeout:
usb_osal_mutex_give(g_usbhost.exclsem);
return -ETIMEDOUT;
}
int usbh_ep_bulk_transfer(usbh_epinfo_t ep, uint8_t *buffer, uint32_t buflen)
{
int ret;
uint8_t chidx = (uint8_t)ep;
if (g_usbhost.handle->hc[chidx].ep_is_in) {
ret = HAL_HCD_HC_SubmitRequest(g_usbhost.handle,
chidx, /* Pipe index */
1, /* Direction : IN */
2, /* EP type */
USBH_PID_DATA, /* Type Data */
buffer, /* data buffer */
buflen, /* data length */
0); /* do ping (HS Only)*/
usb_osal_sem_take(g_usbhost.chan[chidx].waitsem);
if (ret < 0) {
return ret;
}
return HAL_HCD_HC_GetXferCount(g_usbhost.handle, chidx);
} else {
ret = HAL_HCD_HC_SubmitRequest(g_usbhost.handle,
chidx, /* Pipe index */
0, /* Direction : OUT */
2, /* EP type */
USBH_PID_DATA, /* Type Data */
buffer, /* data buffer */
buflen, /* data length */
0); /* do ping (HS Only)*/
usb_osal_sem_take(g_usbhost.chan[chidx].waitsem);
if (ret < 0) {
return ret;
}
return HAL_HCD_HC_GetXferCount(g_usbhost.handle, chidx);
}
return -1;
}
int usbh_ep_intr_transfer(usbh_epinfo_t ep, uint8_t *buffer, uint32_t buflen)
{
int ret;
uint8_t chidx = (uint8_t)ep;
if (g_usbhost.handle->hc[chidx].ep_is_in) {
ret = HAL_HCD_HC_SubmitRequest(g_usbhost.handle,
chidx, /* Pipe index */
1, /* Direction : IN */
3, /* EP type */
USBH_PID_DATA, /* Type Data */
buffer, /* data buffer */
buflen, /* data length */
0); /* do ping (HS Only)*/
usb_osal_sem_take(g_usbhost.chan[chidx].waitsem);
if (ret < 0) {
return ret;
}
return HAL_HCD_HC_GetXferCount(g_usbhost.handle, chidx);
} else {
ret = HAL_HCD_HC_SubmitRequest(g_usbhost.handle,
chidx, /* Pipe index */
0, /* Direction : OUT */
3, /* EP type */
USBH_PID_DATA, /* Type Data */
buffer, /* data buffer */
buflen, /* data length */
0); /* do ping (HS Only)*/
usb_osal_sem_take(g_usbhost.chan[chidx].waitsem);
if (ret < 0) {
return ret;
}
return HAL_HCD_HC_GetXferCount(g_usbhost.handle, chidx);
}
return -1;
}
int usbh_ep_bulk_async_transfer(usbh_epinfo_t ep, uint8_t *buffer, uint32_t buflen, usbh_asynch_callback_t callback, void *arg)
{
return 0;
}
int usbh_ep_intr_async_transfer(usbh_epinfo_t ep, uint8_t *buffer, uint32_t buflen, usbh_asynch_callback_t callback, void *arg)
{
return 0;
}
int usb_ep_cancel(usbh_epinfo_t ep)
{
return 0;
}
void HAL_Delay(uint32_t Delay)
{
usb_osal_msleep(Delay);
}
void HAL_HCD_Connect_Callback(HCD_HandleTypeDef *hhcd)
{
g_usbhost.connected = true;
extern void usbh_event_notify_handler(uint8_t event, uint8_t rhport);
usbh_event_notify_handler(USBH_EVENT_ATTACHED, 1);
}
/**
* @brief SOF callback.
* @param hhcd: HCD handle
* @retval None
*/
void HAL_HCD_Disconnect_Callback(HCD_HandleTypeDef *hhcd)
{
g_usbhost.connected = false;
extern void usbh_event_notify_handler(uint8_t event, uint8_t rhport);
usbh_event_notify_handler(USBH_EVENT_REMOVED, 1);
}
void HAL_HCD_HC_NotifyURBChange_Callback(HCD_HandleTypeDef *hhcd, uint8_t chnum, HCD_URBStateTypeDef urb_state)
{
usb_osal_sem_give(g_usbhost.chan[chnum].waitsem);
}