/* * Copyright (C) 2018 The Android Open Source Project * * 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 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. */ #include #include #include #include #include #include #include #include #include #define LOCAL_TRACE (0) #include #define MAX_PORT_BUF_SIZE 4096 /* max size of per port buffer */ enum test_message_header { TEST_PASSED = 0, TEST_FAILED = 1, TEST_MESSAGE = 2, TEST_MESSAGE_HEADER_COUNT = 3, }; static struct handle* ipc_printf_handle; static struct mutex unittest_lock = MUTEX_INITIAL_VALUE(unittest_lock); static struct handle* unittest_handle_set; static thread_t* unittest_thread; uint64_t get_current_time_ns(void) { return current_time_ns(); } static int send_msg_wait(struct handle* handle, struct ipc_msg_kern* msg) { int ret; uint32_t event; ASSERT(is_mutex_held(&unittest_lock)); ret = ipc_send_msg(handle, msg); if (ret != ERR_NOT_ENOUGH_BUFFER) { return ret; } ret = handle_wait(handle, &event, INFINITE_TIME); if (ret < 0) { return ret; } if (event & IPC_HANDLE_POLL_SEND_UNBLOCKED) { return ipc_send_msg(handle, msg); } if (event & IPC_HANDLE_POLL_MSG) { return ERR_BUSY; } if (event & IPC_HANDLE_POLL_HUP) { return ERR_CHANNEL_CLOSED; } return ret; } /** * unittest_printf - Print a message that gets sent back to the client * @fmt: Format string. * * Print a message that gets sent back to the currently connected client. Should * only be called while the run_test function registered with unittest_add runs. * The length of the formatted string is limited to 254 bytes. * * Return: Formatted string length or (negative) error code. */ int unittest_printf(const char* fmt, ...) { char buf[256]; struct iovec_kern tx_iov = {buf, 1}; struct ipc_msg_kern tx_msg = {1, &tx_iov, 0, NULL}; va_list ap; int ret; int slen; va_start(ap, fmt); /* Format string into buf[1...]. buf[0] contains the message header. */ ret = vsnprintf(buf + 1, sizeof(buf) - 1, fmt, ap); va_end(ap); if (ret < 0) { return ret; } /* * vsnprintf returns the length of the string it would produce if the buffer * was big enough. Compute the actual string length by clamping the return * value to the largest string that can fit in the buffer. */ slen = MIN(ret, (int)sizeof(buf) - 1 - 1); buf[0] = TEST_MESSAGE; tx_iov.iov_len = 1 + slen; mutex_acquire(&unittest_lock); ret = send_msg_wait(ipc_printf_handle, &tx_msg); mutex_release(&unittest_lock); if (ret < 0) { return ret; } return slen; } /** * unittest_loop - Thread function handling all kernel unit-tests * arg: Unused thread argument. * * Wait on handle-set for a client to connect. When a client connects, run the * test function for the port the client connected to then sent the test status * back to the client. The test function can call unittest_printf to send text * back to the client. * * Return: error code is there was an unexpected error. */ static int unittest_loop(void* arg) { int ret; struct handle* chandle; struct handle_ref evt; const uuid_t* unused_uuid_p; struct unittest* test; LTRACEF("waiting for connection\n"); for (;;) { ret = handle_set_wait(unittest_handle_set, &evt, INFINITE_TIME); if (ret < 0) { TRACEF("handle_set_wait failed: %d\n", ret); break; } test = evt.cookie; LTRACEF("got event (ret=%d): ev=%x handle=%p port=%s\n", ret, evt.emask, evt.handle, test->port_name); if (evt.emask & IPC_HANDLE_POLL_READY) { /* get connection request */ ret = ipc_port_accept(evt.handle, &chandle, &unused_uuid_p); LTRACEF("accept returned %d\n", ret); if (ret >= 0) { char tx_buffer[1]; struct iovec_kern tx_iov = { tx_buffer, sizeof(tx_buffer), }; struct ipc_msg_kern tx_msg = {1, &tx_iov, 0, NULL}; /* then run unittest test */ ipc_printf_handle = chandle; tx_buffer[0] = test->run_test(test) ? TEST_PASSED : TEST_FAILED; mutex_acquire(&unittest_lock); ipc_printf_handle = NULL; send_msg_wait(chandle, &tx_msg); mutex_release(&unittest_lock); /* and close it */ handle_close(chandle); } } } return ret; } /** * unittest_add_locked - Internal helper function to add a kernel unit-test * @test: See unittest_add. * * unittest_lock must be locked before calling this. * * Return: See unittest_add. */ static int unittest_add_locked(struct unittest* test) { int ret; struct handle* phandle; ASSERT(is_mutex_held(&unittest_lock)); if (!unittest_handle_set) { unittest_handle_set = handle_set_create(); if (!unittest_handle_set) { ret = ERR_NO_MEMORY; goto err_handle_set_create; } } ret = ipc_port_create(&kernel_uuid, test->port_name, 1, MAX_PORT_BUF_SIZE, IPC_PORT_ALLOW_NS_CONNECT | IPC_PORT_ALLOW_TA_CONNECT, &phandle); if (ret) { goto err_port_create; } ret = ipc_port_publish(phandle); if (ret) { goto err_port_publish; } handle_incref(phandle); test->_href.handle = phandle; test->_href.emask = ~0U; test->_href.cookie = test; ret = handle_set_attach(unittest_handle_set, &test->_href); if (ret < 0) { goto err_handle_set_attach; } LTRACEF("added port %s handle, %p, to handleset %p\n", test->port_name, test->_href.handle, unittest_handle_set); if (!unittest_thread) { unittest_thread = thread_create("unittest", unittest_loop, NULL, HIGH_PRIORITY, DEFAULT_STACK_SIZE); if (!unittest_thread) { ret = ERR_NO_MEMORY; goto err_thread_create; } thread_resume(unittest_thread); } return 0; err_thread_create: handle_set_detach_ref(&test->_href); err_handle_set_attach: handle_decref(phandle); err_port_publish: handle_close(phandle); err_port_create: err_handle_set_create: TRACEF("Failed to add unittest: %d\n", ret); return ret; } /** * unittest_add - Add a kernel unit-test * @test: Test descriptor with port name and callback to start the test when * a client connects to the port. @test is used after unittest_add * returns so it must not be a temporary allocation. * * Creates a port for @test. when the first test is added, create a handle set * and thread that will be shared between all tests. * * Return: 0 if test was added, error-code otherwise. */ int unittest_add(struct unittest* test) { int ret; mutex_acquire(&unittest_lock); ret = unittest_add_locked(test); mutex_release(&unittest_lock); return ret; }