| 
 | 1 | +/*  | 
 | 2 | + * Copyright (c) 2025 Nordic Semiconductor ASA  | 
 | 3 | + *  | 
 | 4 | + * SPDX-License-Identifier: Apache-2.0  | 
 | 5 | + */  | 
 | 6 | + | 
 | 7 | +#include <zephyr/ztest.h>  | 
 | 8 | +#include <zephyr/usb/usbh.h>  | 
 | 9 | +#include <zephyr/usb/usbh.h>  | 
 | 10 | + | 
 | 11 | +#include "usbh_ch9.h"  | 
 | 12 | +#include "usbh_class_api.h"  | 
 | 13 | +#include "usbh_desc.h"  | 
 | 14 | +#include "usbh_host.h"  | 
 | 15 | + | 
 | 16 | +#include "test_descriptor.h"  | 
 | 17 | + | 
 | 18 | +#include <zephyr/logging/log.h>  | 
 | 19 | +LOG_MODULE_REGISTER(usb_test, LOG_LEVEL_DBG);  | 
 | 20 | + | 
 | 21 | +USBH_CONTROLLER_DEFINE(uhs_ctx, DEVICE_DT_GET(DT_NODELABEL(zephyr_uhc0)));  | 
 | 22 | + | 
 | 23 | +static const uint8_t test_hub_descriptor[] = {TEST_HUB_DESCRIPTOR};  | 
 | 24 | +struct usb_device test_udev = {};  | 
 | 25 | + | 
 | 26 | +/* Private class data, here just an integer but usually a custom struct. */  | 
 | 27 | +struct test_class_priv {  | 
 | 28 | +	enum {  | 
 | 29 | +		/* Test value stored before the class is initialized */  | 
 | 30 | +		TEST_CLASS_PRIV_INACTIVE,  | 
 | 31 | +		/* Test value stored after the class is initialized */  | 
 | 32 | +		TEST_CLASS_PRIV_IDLE,  | 
 | 33 | +		/* Test value stored after the class is probed */  | 
 | 34 | +		TEST_CLASS_PRIV_ENABLED,  | 
 | 35 | +		/* Test value stored after the class is initialized */  | 
 | 36 | +		TEST_CLASS_PRIV_INITIALIZED,  | 
 | 37 | +		/* Test value stored after the class is suspended */  | 
 | 38 | +		TEST_CLASS_PRIV_SUSPENDED,  | 
 | 39 | +	} state;  | 
 | 40 | +};  | 
 | 41 | + | 
 | 42 | +static struct test_class_priv test_class_priv = {  | 
 | 43 | +	.state = TEST_CLASS_PRIV_INACTIVE,  | 
 | 44 | +};  | 
 | 45 | + | 
 | 46 | +static int test_class_init(struct usbh_class_data *const c_data,  | 
 | 47 | +			   struct usbh_context *const uhs_ctx)  | 
 | 48 | +{  | 
 | 49 | +	struct test_class_priv *priv = c_data->priv;  | 
 | 50 | + | 
 | 51 | +	LOG_DBG("initializing %p, priv value 0x%x", c_data, *priv);  | 
 | 52 | + | 
 | 53 | +	zassert_equal(priv->state, TEST_CLASS_PRIV_INACTIVE,  | 
 | 54 | +		      "Class should be initialized only once");  | 
 | 55 | + | 
 | 56 | +	priv->state = TEST_CLASS_PRIV_IDLE;  | 
 | 57 | + | 
 | 58 | +	return 0;  | 
 | 59 | +}  | 
 | 60 | + | 
 | 61 | +static int test_class_completion_cb(struct usbh_class_data *const c_data,  | 
 | 62 | +				    struct uhc_transfer *const xfer)  | 
 | 63 | +{  | 
 | 64 | +	struct test_class_priv *priv = c_data->priv;  | 
 | 65 | + | 
 | 66 | +	LOG_DBG("completion callback for %p, transfer %p", c_data, xfer);  | 
 | 67 | + | 
 | 68 | +	zassert_equal(priv->state, TEST_CLASS_PRIV_ENABLED);  | 
 | 69 | + | 
 | 70 | +	return -ENOTSUP;  | 
 | 71 | +}  | 
 | 72 | + | 
 | 73 | +static int test_class_probe(struct usbh_class_data *const c_data,  | 
 | 74 | +			    struct usb_device *const udev,  | 
 | 75 | +			    const uint8_t ifnum)  | 
 | 76 | +{  | 
 | 77 | +	struct test_class_priv *priv = c_data->priv;  | 
 | 78 | +	const void *const desc_beg = test_hub_descriptor;  | 
 | 79 | +	const void *const desc_end = test_hub_descriptor + sizeof(test_hub_descriptor);  | 
 | 80 | +	const struct usb_desc_header *desc = desc_beg;  | 
 | 81 | +	const struct usb_if_descriptor *if_desc;  | 
 | 82 | + | 
 | 83 | +	zassert_equal(priv->state, TEST_CLASS_PRIV_IDLE);  | 
 | 84 | + | 
 | 85 | +	desc = usbh_desc_get_by_ifnum(desc, desc_end, ifnum);  | 
 | 86 | +	if (desc == NULL) {  | 
 | 87 | +		return -ENOENT;  | 
 | 88 | +	}  | 
 | 89 | + | 
 | 90 | +	if (desc->bDescriptorType != USB_DESC_INTERFACE) {  | 
 | 91 | +		return -ENOTSUP;  | 
 | 92 | +	}  | 
 | 93 | + | 
 | 94 | +	if_desc = (struct usb_if_descriptor *)desc;  | 
 | 95 | +	if (if_desc->bInterfaceClass != USB_HUB_CLASSCODE ||  | 
 | 96 | +	    if_desc->bInterfaceSubClass != 0x00) {  | 
 | 97 | +		return -ENOTSUP;  | 
 | 98 | +	}  | 
 | 99 | + | 
 | 100 | +	priv->state = TEST_CLASS_PRIV_ENABLED;  | 
 | 101 | + | 
 | 102 | +	return 0;  | 
 | 103 | +}  | 
 | 104 | + | 
 | 105 | +static int test_class_removed(struct usbh_class_data *const c_data)  | 
 | 106 | +{  | 
 | 107 | +	struct test_class_priv *priv = c_data->priv;  | 
 | 108 | + | 
 | 109 | +	zassert_equal(priv->state, TEST_CLASS_PRIV_ENABLED);  | 
 | 110 | + | 
 | 111 | +	priv->state = TEST_CLASS_PRIV_IDLE;  | 
 | 112 | + | 
 | 113 | +	return 0;  | 
 | 114 | +}  | 
 | 115 | + | 
 | 116 | +static int test_class_suspended(struct usbh_class_data *const c_data)  | 
 | 117 | +{  | 
 | 118 | +	struct test_class_priv *priv = c_data->priv;  | 
 | 119 | + | 
 | 120 | +	zassert_equal(priv->state, TEST_CLASS_PRIV_ENABLED);  | 
 | 121 | + | 
 | 122 | +	priv->state = TEST_CLASS_PRIV_SUSPENDED;  | 
 | 123 | + | 
 | 124 | +	return 0;  | 
 | 125 | +}  | 
 | 126 | + | 
 | 127 | +static int test_class_resumed(struct usbh_class_data *const c_data)  | 
 | 128 | +{  | 
 | 129 | +	struct test_class_priv *priv = c_data->priv;  | 
 | 130 | + | 
 | 131 | +	zassert_equal(priv->state, TEST_CLASS_PRIV_SUSPENDED);  | 
 | 132 | + | 
 | 133 | +	priv->state = TEST_CLASS_PRIV_ENABLED;  | 
 | 134 | + | 
 | 135 | +	return 0;  | 
 | 136 | +}  | 
 | 137 | + | 
 | 138 | +static struct usbh_class_api test_class_api = {  | 
 | 139 | +	.init = &test_class_init,  | 
 | 140 | +	.completion_cb = &test_class_completion_cb,  | 
 | 141 | +	.probe = &test_class_probe,  | 
 | 142 | +	.removed = &test_class_removed,  | 
 | 143 | +	.suspended = &test_class_suspended,  | 
 | 144 | +	.resumed = &test_class_resumed,  | 
 | 145 | +};  | 
 | 146 | + | 
 | 147 | +/* Define a class used in the tests */  | 
 | 148 | +USBH_DEFINE_CLASS(test_class, &test_class_api, &test_class_priv);  | 
 | 149 | + | 
 | 150 | +ZTEST(host_class, test_class_fake_device)  | 
 | 151 | +{  | 
 | 152 | +	struct usbh_class_data *c_data = test_class.c_data;  | 
 | 153 | +	struct usb_device *udev = &test_udev;  | 
 | 154 | +	struct test_class_priv *priv = c_data->priv;  | 
 | 155 | +	int ret;  | 
 | 156 | + | 
 | 157 | +	zassert_equal(priv->state, TEST_CLASS_PRIV_IDLE,  | 
 | 158 | +		      "The class should have been initialized by usbh_init()");  | 
 | 159 | + | 
 | 160 | +	ret = usbh_class_probe(c_data, udev, 1);  | 
 | 161 | +	zassert_equal(ret, -ENOENT, "There is no interface 1 so should be rejected");  | 
 | 162 | +	zassert_equal(priv->state, TEST_CLASS_PRIV_IDLE,  | 
 | 163 | +		      "The class should not be enabled if probing failed");  | 
 | 164 | + | 
 | 165 | +	ret = usbh_class_probe(c_data, udev, 0);  | 
 | 166 | +	zassert_ok(ret, "The interface 0 should match in this example class (%s)", strerror(-ret));  | 
 | 167 | +	zassert_equal(priv->state, TEST_CLASS_PRIV_ENABLED,  | 
 | 168 | +		      "The class should be enabled if probing succeeded");  | 
 | 169 | + | 
 | 170 | +	ret = usbh_class_suspended(c_data);  | 
 | 171 | +	zassert_ok(ret, "Susupending the class while it is running should succeed");  | 
 | 172 | +	zassert_equal(priv->state, TEST_CLASS_PRIV_SUSPENDED,  | 
 | 173 | +		      "The class private state should have been updated");  | 
 | 174 | + | 
 | 175 | +	ret = usbh_class_resumed(c_data);  | 
 | 176 | +	zassert_ok(ret, "Resuming the class after suspending should succeed");  | 
 | 177 | +	zassert_equal(priv->state, TEST_CLASS_PRIV_ENABLED,  | 
 | 178 | +		      "The class private state should have been updated");  | 
 | 179 | + | 
 | 180 | +	ret = usbh_class_removed(c_data);  | 
 | 181 | +	zassert_ok(ret, "Removing the class after probing it should succeed");  | 
 | 182 | +	zassert_equal(priv->state,  TEST_CLASS_PRIV_IDLE,  | 
 | 183 | +		      "The class should be back to inactive ");  | 
 | 184 | + | 
 | 185 | +	ret = usbh_class_probe(c_data, udev, 0);  | 
 | 186 | +	zassert_ok(ret, "Probing the class again should succeed");  | 
 | 187 | +	zassert_equal(priv->state,  TEST_CLASS_PRIV_ENABLED,  | 
 | 188 | +		      "The class should be back to active ");  | 
 | 189 | + | 
 | 190 | +}  | 
 | 191 | + | 
 | 192 | +static void *usb_test_enable(void)  | 
 | 193 | +{  | 
 | 194 | +	int ret;  | 
 | 195 | + | 
 | 196 | +	ret = usbh_init(&uhs_ctx);  | 
 | 197 | +	zassert_ok(ret, "Failed to initialize USB host (%s)", strerror(-ret));  | 
 | 198 | + | 
 | 199 | +	ret = usbh_enable(&uhs_ctx);  | 
 | 200 | +	zassert_ok(ret, "Failed to enable USB host (%s)", strerror(-ret));  | 
 | 201 | + | 
 | 202 | +	ret = uhc_bus_reset(uhs_ctx.dev);  | 
 | 203 | +	zassert_ok(ret, "Failed to signal bus reset (%s)", strerror(-ret));  | 
 | 204 | + | 
 | 205 | +	ret = uhc_bus_resume(uhs_ctx.dev);  | 
 | 206 | +	zassert_ok(ret, "Failed to signal bus resume (%s)", strerror(-ret));  | 
 | 207 | + | 
 | 208 | +	ret = uhc_sof_enable(uhs_ctx.dev);  | 
 | 209 | +	zassert_ok(ret, "Failed to enable SoF generator (%s)", strerror(-ret));  | 
 | 210 | + | 
 | 211 | +	LOG_INF("Host controller enabled");  | 
 | 212 | + | 
 | 213 | +	/* Allow the host time to reset the host. */  | 
 | 214 | +	k_msleep(200);  | 
 | 215 | + | 
 | 216 | +	return NULL;  | 
 | 217 | +}  | 
 | 218 | + | 
 | 219 | +static void usb_test_shutdown(void *f)  | 
 | 220 | +{  | 
 | 221 | +	int ret;  | 
 | 222 | + | 
 | 223 | +	ret = usbh_disable(&uhs_ctx);  | 
 | 224 | +	zassert_ok(ret, "Failed to enable host support (%s)", strerror(-ret));  | 
 | 225 | + | 
 | 226 | +	ret = usbh_shutdown(&uhs_ctx);  | 
 | 227 | +	zassert_ok(ret, "Failed to shutdown host support (%s)", strerror(-ret));  | 
 | 228 | + | 
 | 229 | +	LOG_INF("Host controller disabled");  | 
 | 230 | +}  | 
 | 231 | + | 
 | 232 | +ZTEST_SUITE(host_class, NULL, usb_test_enable, NULL, NULL, usb_test_shutdown);  | 
0 commit comments