Development Guide
This document provides a detailed guide for XSTAR development, including code conventions, driver development, command development, testing, and more. For a quick start, see Quick Start; for architecture background, see Architecture Design.
Table of Contents
- Code Conventions
- Driver Development
- Command Development
- Kernel Subsystem Development
- Test Development
- XOS Platform Porting
- Common Development Tasks
- Debugging Tips
Code Conventions
File Naming
- Source files:
kebab-case.c(e.g.,clk-fixed.c,i2c-gpio.c) - Header files:
kebab-case.h(same name as source file)
File Header Comment
Each source file must include the MIT license header:
/*
* driver/clk/clk-fixed.c
*
* Copyright(c) Jianjun Jiang <8192542@qq.com>
*
* 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.
*/
Code Formatting
- Indentation: Use tabs (not spaces)
- Line width: ~120 characters (not strictly enforced, but keep reasonable)
- Braces: Allman style for functions and control structures (opening brace on next line); K&R style for struct/enum definitions and initializer lists (opening brace on same line)
- Comments: Minimize comments, do not proactively add code comments (unless explicitly requested); existing device tree documentation comments and struct field comments in the codebase are for reference only
Naming Conventions
- Functions:
snake_case(e.g.,clk_fixed_probe,register_driver) - Variables:
snake_case(e.g.,pdat,rate,kobj) - Types:
snake_case_tsuffix (e.g.,struct driver_t,enum device_type_t) - Constants:
ALL_CAPS_WITH_UNDERSCORES(e.g.,DEVICE_TYPE_MAX_COUNT,TRUE,FALSE) - Macros:
ALL_CAPS(e.g.,ARRAY_SIZE,container_of,offsetof)
Include Guards
Format: __PATH_TO_FILE_H__, example:
#ifndef __XSTAR_DRIVER_CLK_H__
#define __XSTAR_DRIVER_CLK_H__
/* header file content */
#endif /* __XSTAR_DRIVER_CLK_H__ */
Include Order
#include <xstar.h>
#include <xos/xos.h>
#include <driver/clk/clk.h>
- Local project headers (
#include <xstar.h>or#include <xos/xos.h>) - Driver/kernel headers (e.g.,
#include <driver/clk/clk.h>) - Standard/external headers (if any)
Error Handling
- Boolean functions: return
TRUEon success,FALSEon failure - Pointer functions: return pointer on success,
NULLon failure - Always check return values
- Clean up allocated resources on error paths
static struct device_t * my_probe(struct driver_t * drv, struct dtnode_t * n)
{
struct xxx_pdata_t *pdat;
struct xxx_t *xxx;
pdat = xos_mem_malloc(sizeof(struct xxx_pdata_t));
if(!pdat)
return NULL;
xxx = xos_mem_malloc(sizeof(struct xxx_t));
if(!xxx)
{
xos_mem_free(pdat);
return NULL;
}
/* initialization and registration */
return dev;
}
Type Usage
Use standard types (provided via <xos/xos.h> through <xstarcfg.h>):
uint8_t,uint16_t,uint32_t,uint64_tint8_t,int16_t,int32_t,int64_tsize_t,ssize_tio_addr_tNULL,TRUE,FALSE(fromlibx/xdef.h)
Driver Development
Driver Template
#include <xstar.h>
#include <driver/xxx/xxx.h>
struct xxx_pdata_t {
/* private data */
};
static struct device_t * xxx_probe(struct driver_t * drv, struct dtnode_t * n)
{
struct xxx_pdata_t *pdat;
/* parse device tree properties */
const char *name = dt_read_string(n, "name", NULL);
int value = dt_read_int(n, "value", 0);
/* allocate private data */
pdat = xos_mem_malloc(sizeof(struct xxx_pdata_t));
if(!pdat)
return NULL;
/* initialize hardware */
/* create and register device */
return register_xxx(drv, pdat);
}
static void xxx_remove(struct device_t * dev)
{
struct xxx_pdata_t *pdat = (struct xxx_pdata_t *)dev->priv;
/* release resources */
xos_mem_free(pdat);
}
static void xxx_suspend(struct device_t * dev)
{
}
static void xxx_resume(struct device_t * dev)
{
}
static struct driver_t xxx_driver = {
.name = "xxx-driver",
.probe = xxx_probe,
.remove = xxx_remove,
.suspend = xxx_suspend,
.resume = xxx_resume,
};
static void xxx_driver_init(void)
{
register_driver(&xxx_driver);
}
static void xxx_driver_exit(void)
{
unregister_driver(&xxx_driver);
}
driver_initcall(xxx_driver_init);
driver_exitcall(xxx_driver_exit);
Device Tree Configuration
Add device configuration in romdisk/dtree/default.json:
{
"xxx-driver:0": {
"name": "my-device",
"value": 42
}
}
The device tree key name format is "driver-name:id@address":
driver-name: driver name, must matchdriver_t.nameid: device instance number (optional)address: physical address (optional)
Set "status": "disabled" to skip device probing.
Device Property Reading
| Function | Return Type | Description |
|---|---|---|
dt_read_string(n, name, def) | char * | Read string |
dt_read_int(n, name, def) | int | Read integer |
dt_read_long(n, name, def) | long long | Read long integer |
dt_read_bool(n, name, def) | int | Read boolean |
dt_read_double(n, name, def) | double | Read double-precision float |
dt_read_object(n, name) | struct dtnode_t | Read child object |
Device References
Reference other devices in JSON using the "driver-name:id" format:
{
"led-gpio:0": {
"gpio": "gpiochip0:10",
"active-low": true
}
}
Parse references in code and look up devices:
static struct device_t * xxx_probe(struct driver_t * drv, struct dtnode_t * n)
{
const char *gpio_name = dt_read_string(n, "gpio", NULL);
struct device_t *gpio_dev = search_device(gpio_name, DEVICE_TYPE_GPIOCHIP);
if(!gpio_dev)
return NULL;
/* use gpio_dev */
}
Kbuild File
Create a Kbuild file in the driver directory:
obj-y += core.o
obj-$(CONFIG_DRV_XXX) += xxx-driver.o
Device Types
The system defines 50+ device types (enum device_type_t); drivers must specify the corresponding type when registering a device. Common types:
| Category | Type Enum |
|---|---|
| Clock | DEVICE_TYPE_CLK, DEVICE_TYPE_CLOCKEVENT, DEVICE_TYPE_CLOCKSOURCE |
| GPIO | DEVICE_TYPE_GPIOCHIP, DEVICE_TYPE_IRQCHIP, DEVICE_TYPE_RESETCHIP |
| Communication | DEVICE_TYPE_I2C, DEVICE_TYPE_SPI, DEVICE_TYPE_UART, DEVICE_TYPE_NET |
| Display | DEVICE_TYPE_FRAMEBUFFER, DEVICE_TYPE_G2D, DEVICE_TYPE_CONSOLE |
| Audio | DEVICE_TYPE_AUDIOCAPTURE, DEVICE_TYPE_AUDIOPLAYBACK |
| Input | DEVICE_TYPE_INPUT, DEVICE_TYPE_CAMERA |
| Storage | DEVICE_TYPE_BLOCK, DEVICE_TYPE_NVMEM |
| Output | DEVICE_TYPE_LED, DEVICE_TYPE_PWM, DEVICE_TYPE_SERVO |
See xstar/driver/device.h for the complete list.
Command Development
Command Structure
struct command_t
{
struct list_head_t list;
const char * name;
const char * desc;
void (*usage)(void);
int (*exec)(int argc, char ** argv);
};
Command Template
#include <xstar.h>
static void mycmd_usage(void)
{
shell_printf("Usage: mycmd [options]\n");
shell_printf(" mycmd - do something\n");
shell_printf(" mycmd -h - show help\n");
}
static int mycmd_exec(int argc, char ** argv)
{
shell_printf("My command executed\n");
if(argc > 1)
{
shell_printf("Argument: %s\n", argv[1]);
}
return 0;
}
static struct command_t mycmd = {
.name = "mycmd",
.desc = "My command description",
.usage = mycmd_usage,
.exec = mycmd_exec,
};
static void mycmd_init(void)
{
register_command(&mycmd);
}
static void mycmd_exit(void)
{
unregister_command(&mycmd);
}
command_initcall(mycmd_init);
command_exitcall(mycmd_exit);
Command Usage
# execute command in Shell
mycmd
mycmd arg1 arg2
Kbuild File
obj-$(CONFIG_CMD_MYCMD) += cmd-mycmd.o
The Kconfig option prefix for commands is CONFIG_CMD_*; each command can be individually enabled/disabled. The core framework command.o is always compiled (obj-y).
Kernel Subsystem Development
Subsystem Interface Definition
/* kernel/mysubsystem/my-subsystem.h */
#ifndef __XSTAR_KERNEL_MYSUBSYSTEM_MY_SUBSYSTEM_H__
#define __XSTAR_KERNEL_MYSUBSYSTEM_MY_SUBSYSTEM_H__
#ifdef __cplusplus
extern "C" {
#endif
struct my_subsystem_handle_t;
struct my_subsystem_handle_t * my_subsystem_open(void);
void my_subsystem_close(struct my_subsystem_handle_t *handle);
int my_subsystem_do_something(struct my_subsystem_handle_t *handle, int arg);
#ifdef __cplusplus
}
#endif
#endif /* __XSTAR_KERNEL_MYSUBSYSTEM_MY_SUBSYSTEM_H__ */
Subsystem Implementation
/* kernel/mysubsystem/my-subsystem.c */
#include <xstar.h>
#include <kernel/mysubsystem/my-subsystem.h>
struct my_subsystem_handle_t {
/* private data */
};
struct my_subsystem_handle_t * my_subsystem_open(void)
{
struct my_subsystem_handle_t *handle;
handle = xos_mem_malloc(sizeof(struct my_subsystem_handle_t));
if(!handle)
return NULL;
/* initialize */
return handle;
}
void my_subsystem_close(struct my_subsystem_handle_t *handle)
{
if(handle)
{
/* cleanup */
xos_mem_free(handle);
}
}
int my_subsystem_do_something(struct my_subsystem_handle_t *handle, int arg)
{
if(!handle)
return -1;
/* implement functionality */
return 0;
}
Subsystem Initialization
static void my_subsystem_init(void)
{
/* initialize subsystem */
}
subsys_initcall(my_subsystem_init);
Kbuild File
Kernel subsystems are currently compiled unconditionally; the Kbuild file uses obj-y:
obj-y += my-subsystem.o