How To Specify Your Custom BSP
We previously established that a CPU vendor’s BSP (Board Support Package) provides a solid foundation for your embedded project. With BSP customization now hopefully solidified as a requirement, let’s delve into the key question: what elements are crucial to include, and conversely, what can and must be excluded? Here is a step-by-step process that may help you tidy up your custom BSP.
This is the third article of the BSP series.
- Previous article: Why You Should Customize Your Embedded System’s BSP.
This article is biased towards Linux systems and the Yocto framework but should apply to any development and execution environments.
0. Know your requirements
The importance of ground zero can’t be understated: Before delving into the specifics of BSP customization, it’s crucial to establish a clear understanding of your project’s requirements. This upfront planning minimizes improvisation and ensures you’re building on a solid foundation.
To achieve this clear understanding, you’ll first need to define your application’s needs. This means clearly identifying the functionalities and features your embedded application will require. Systematically walking through the features list helps you determine the necessary hardware components and software functionalities you’ll need from the BSP. Think of this process as an inventory of functions, not as a data flow.
When the features list is established, separate BSP vs. application logic. It’s crucial to distinguish between the functionalities that belong to the BSP, such as device drivers or commodity services (embedded web server, time sync, etc), and those specific to your business application’s logic. This separation ensures you’re customizing the BSP for the right purposes without including unnecessary elements.
By following the above steps, you’ll gain a well-defined roadmap for customizing your BSP. This reduces the risk of getting stuck due to underthinking the overall architecture from the beginning and ensures you’re including all and only the essential components contributing to your application’s value.
1. Define the boot strategy
Before customizing the core functionalities of the BSP, it’s vital to address features of the boot process for your embedded system, and especially its security implications.
The boot requirements outline the specific steps and protocols the system must follow to successfully load and execute the operating system. This includes defining the authorized boot media, authentication methods for the system image, and secure boot procedures.
Implementing a secure boot process is paramount. This involves establishing a root of trust, which is a hardware or software component responsible for verifying the authenticity and integrity of the boot loader and operating system image before execution. This helps prevent unauthorized code from being loaded onto your system.
Specifying authorized boot media is crucial. This could be internal storage (e.g., eMMC, SSD), removable media (e.g., SD card) or even network (e.g. tftp). Recoursing to non-internal media is an appealing option during debug. However, it’s probably not desirable in field deployment, unless you know exactly what you’re doing and you take extra security measures to prevent booting a rogue image.
Before launching the boot process, the system must authenticate the boot image’s integrity. This typically involves cryptographic techniques like digital signatures to ensure the image hasn’t been tampered with during storage or transfer. Many different flavors exist and must be specified early.
Some systems allow firmware upgrades to be performed at the boot stage. This can be helpful for applying security patches or bug fixes when the system does not run. However, it’s crucial to ensure these upgrades are also authenticated to prevent installing malicious firmware.
In case of unsuccessful firmware updates or security breaches, having a debricking solution in place is essential. This is a recovery mechanism that allows you to restore the system to a known good state, even if the primary boot process is compromised.
Last but not least, most bootloaders come with a wide variety of commands and features that you probably don’t need. Some more advanced bootloaders even permit accessing filesystems, scripting complex commands, or sourcing code that’s stored in environment variables. Those features are convenient and fun to use during development but can rapidly turn into a cybersecurity nightmare. All commands should be reviewed for usefulness (U-Boot: help), and all environment variables should be scrutinized (U-Boot: printenv) and pruned if not strictly necessary.
By carefully considering these boot requirements and prioritizing security, you can ensure your embedded system starts up securely and with a minimized risk of vulnerability.
2. Configure the operating system
Modern embedded system operating systems, like Linux, FreeRTOS or Zephyr, offer a vast array of configuration options. While this flexibility empowers you to tailor the OS to your specific needs, it can also feel overwhelming. There’s no one-size-fits-all solution and the optimal configuration will vary depending on your unique project requirements.
Here’s a practical approach to guide you through the definition of your OS configuration.
- Leverage centralized configuration tools. Most operating systems centralize configuration options within dedicated tools or directories. For example, Linux and Zephyr utilize kernel configuration tools like
make menuconfig
, while FreeRTOS relies on preprocessor macros within a dedicated directory of the C source code. Familiarize yourself with these tools and how to access the configuration options they provide. - Embrace Informed Decision-Making. The key to efficient configuration lies in understanding each option’s impact and making informed decisions. While a comprehensive review of every single option might seem daunting, especially for a complex OS like Linux, it’s the most effective approach. Here’s what this entails:
- Walk through each and every configuration option. Yes, that’s a lot, but most of them offer one obvious best choice for your application. If something is unclear, carry on with the next lines.
- Review the documentation. The operating system’s documentation is your best friend. Consult the documentation to understand the purpose and implications of each configuration option. Most tools will also provide basic help information within their interfaces. Good for a quick reminder.
- Seek community resources. Don’t hesitate to leverage online communities and forums dedicated to your chosen operating system. Experienced users and developers can be invaluable resources for understanding specific configuration options and best practices.
- Leave room for iteration. Don’t forget that we are at the specification stage, not the actual configuration. In some cases there might not be one single best option. Constantly evaluate your project’s specific needs throughout the configuration process by asking yourself: “Do I truly need this feature enabled?” Disabling unnecessary features reduces the OS footprint, improves boot times, and minimizes potential security vulnerabilities. Don’t be afraid to relax the spec and let the developers experiment and refine the configuration.
3. List the device drivers
Device drivers act as interpreters between the operating system and the various hardware components connected to your embedded system. Selecting and configuring the appropriate driver set is crucial for ensuring proper functionality and communication between the software and hardware.
It’s important to note that the process of selecting device drivers can vary depending on the operating system you’ve chosen for your embedded system:
Kernel-Level Driver Selection: In some operating systems, like Linux, the initial selection of device drivers is often tightly coupled with the operating system configuration process. Tools like
make menuconfig
might present a list of available device drivers for various hardware components. You’ll need to enable the drivers corresponding to the hardware elements present in your system.Separate Driver Management: Other operating systems, like FreeRTOS, might handle device driver loading and management independently of the core OS configuration. In such cases, you’ll likely need to integrate the necessary drivers directly into your application code or utilize a separate driver management framework.
Here’s a practical approach to expedite the process of identifying the required device drivers:
Review the Electronic Schematics: Your system’s electronic schematics provide a comprehensive overview of all the hardware components connected to the CPU. This includes elements like sensors, actuators, communication interfaces, and memory devices.
Map Schematics to Drivers: Carefully examine each hardware component listed in the schematics. For each device, there should be a corresponding device driver enabled within your operating system configuration or integrated into your application code (depending on your chosen OS). This ensures the OS recognizes and can interact with the hardware.
Remove Unnecessary Drivers: Conversely, if the schematics don’t depict a specific hardware component, any associated device driver should be disabled or removed from your configuration. Including unused drivers can increase the overall system footprint and potentially introduce security vulnerabilities.
♦ Pro Tip ♦ Walk through these steps early in the hardware design process in close collaboration with the hardware team. Choosing hardware components that have readily available drivers for your selected operating system is highly incentivized. Developing a custom device driver from scratch can be a costly and time-consuming endeavor. By involving the hardware team early on, you can ensure compatibility between chosen components and readily available drivers, streamlining the development process.
By following these steps and leveraging your system’s schematics, you can streamline the process of selecting and configuring the essential device drivers for your embedded system. This ensures efficient communication between the software and hardware, laying the foundation for successful system operation.
4. Specify the filesystems
While not all embedded systems require them, filesystems play a critical role in managing data storage and retrieval for those that do. If your application necessitates a filesystem, careful planning is crucial. Here’s a breakdown of the key considerations for each filesystem you include:
Physical Device and Storage Technology: Identify the physical device that will house the filesystem. Common options include:
- Internal Storage: Embedded systems often utilize internal storage like:
- eMMC (embedded MultiMedia Card): Offers high capacity and good performance, suitable for read-write operations.
- NOR Flash: Provides fast read speeds but limited write endurance, ideal for storing bootloaders and frequently accessed code.
- NAND Flash: Offers high density and lower cost, suitable for bulk data storage, but with slower write speeds and wear considerations.
- External Storage: Removable media like SD cards can be used for additional storage or for easier updates.
- RAM: A portion of system RAM can be allocated for a RAM-disk filesystem, ideal for temporary data or frequently accessed files due to its exceptional speed.
- Internal Storage: Embedded systems often utilize internal storage like:
Filesystem Type: Choosing the right filesystem type depends on the storage technology and usage patterns:
- Read-Only Filesystems: For bootloaders or critical configuration files that shouldn’t be modified, read-only filesystems like cramfs (Compressed Read-Only Memory Filesystem) are ideal due to their small footprint and fast loading times.
- Read-Write Filesystems: For data storage with frequent updates, journaling filesystems like ext4 (Fourth Extended Filesystem) offer robust data integrity and error recovery capabilities.
- Network Filesystems: Network File System (NFS) allows accessing files stored on a remote server, useful for centralized data management.
- Windows-Compatible Filesystems: If interoperability with Windows machines is required, consider filesystems like FAT32 or exFAT.
Storage Allocation: Allocate sufficient storage space for each filesystem, but avoid filling it completely. Leaving some free space allows the filesystem’s protection mechanisms, like wear leveling for flash memory, to function effectively. This prevents data corruption and unexpected behavior.
Partitioning: For devices with large storage capacities, partitioning allows you to create logical divisions within the physical storage space. Each partition can have its own filesystem type, catering to specific needs. This can be beneficial for separating frequently accessed data from rarely used files or organizing the boot process.
By carefully considering these factors for each filesystem you include in your BSP, you can ensure efficient data management, reliable storage, and optimal performance for your embedded system.
5. Detail the network layout
The Board Support Package (BSP) plays a crucial role in establishing a robust foundation for your embedded system’s network functionality. It typically includes pre-configured network drivers and basic network settings, allowing your application to seamlessly interact with the network environment. However, it’s important to distinguish between the BSP’s role and your application’s responsibilities.
BSP Sets the Stage, Application Utilizes the Network
Unless your application is specifically designed for network management purposes, it’s generally recommended to avoid directly manipulating network settings. Instead, your application should leverage the network infrastructure provided by the BSP. This promotes a cleaner separation of concerns and simplifies application development.
The BSP establishes the groundwork by providing:
- Network Drivers: Necessary drivers for the physical network interfaces on your embedded system (e.g., Ethernet controllers, Wi-Fi modules).
- Basic Network Configuration: This might include an IP addressing strategy (fixed IP, DHCP), default routes, and potentially some basic security settings.
- Network Communication APIs: Your application interacts with the network using these functionalities provided by the BSP. These APIs typically handle tasks like sending and receiving data packets, utilizing network protocols (TCP/IP, UDP), and accessing network resources.
By relying on the pre-configured network environment, your application can focus on its core logic and business functionalities. Here’s a breakdown of the details you’ll need to define for the network layout, working collaboratively with the BSP team.
Network Interface Details
This section outlines the key elements to consider when specifying your network configuration for all network interfaces, both physical and logical:
List All Interfaces: Identify and list all network interfaces available on your embedded system. This includes:
- Physical Interfaces: These are the physical ports on your device, such as Ethernet ports, Wi-Fi modules, or cellular network interfaces.
- Logical Interfaces: In some cases, you might create logical interfaces by combining multiple physical interfaces for redundancy or increased bandwidth (e.g., bonding).
Low-Level Configuration (BSP Responsibility): While the specific details might be handled by the BSP, it’s important to understand these settings:
- Data Rates: Specify the supported data rates for each physical interface (e.g., 100Mbps, 1Gbps for Ethernet).
- Autonegotiation: Indicate whether autonegotiation of parameters (like speed and duplex mode) is enabled or disabled on the interface. This allows the device to automatically negotiate connection parameters with other network devices. However, most industrial applications require a deterministic environment and forbid autonegotiation.
IP Addressing Strategy: Choose the method for assigning an IP address to your device, working with the BSP team to ensure compatibility with the network environment:
- Fixed IP: Manually assign a static IP address, subnet mask, and default gateway. This is suitable for scenarios where the device IP needs to be predictable and persistent.
- DHCP: Utilize a DHCP server on your network to dynamically assign an IP address, subnet mask, and default gateway to your device. This simplifies configuration but may require a DHCP server on your network.
- Other Options: Depending on your specific network environment, you might consider alternative addressing schemes like Link-Local Addressing.
IP Details: Specify the following information relevant to your chosen IP addressing strategy:
- IP Address (or Range): Define the specific IP address assigned to the interface (for static IP) or the range of addresses your DHCP server can assign (for DHCP). In some cases, an interface might have multiple IP addresses assigned for different purposes.
- Subnet Mask: Specify the subnet mask that defines the network portion of the IP address and identifies devices on the same local network segment.
- Default Gateway: Define the IP address of the default gateway router that your device will use to route packets to other networks.
Routing (if not automatic): By default, most operating systems can be configured to handle routing automatically. However, if you have specific routing requirements, you might need to configure static routes in collaboration with the BSP team. This involves defining the destination network, subnet mask, and the next hop gateway for specific traffic.
Fallback Address (Optional): In some scenarios, you might configure a fallback IP address for redundancy. This allows the device to obtain a secondary IP address if the primary method fails.
Security Guidelines (Especially for Wireless Interfaces): Implement appropriate security measures to protect your network communication, especially for wireless interfaces. This might involve:
- WPA/WPA2/WPA3 Encryption: Configure strong encryption standards for Wi-Fi communication to protect data from eavesdropping.
- Access Control: Implement access control lists (ACLs) to restrict unauthorized devices from connecting to your network
Meticulously defining and documenting your network layout ensures efficient communication and streamlined network operation for your embedded system. This will minimize configuration errors and troubleshooting efforts in the long run.
By adhering to this approach, you can leverage the expertise embedded within the BSP for robust network configuration, allowing your application to focus on its specific tasks and contribute to a well-designed and efficient embedded system.
6. Define the system services
System services are essential building blocks of a well-functioning embedded system. They represent software programs that run in the background, performing specific tasks crucial for system operation. Effectively specifying these services is vital for ensuring a reliable and efficient system.
Services: The Backbone of Modern Embedded Systems
In modern embedded system software architectures, the concept of services has taken center stage. The core principle is that everything can be considered a service, including the application itself. This modular approach promotes code reusability, simplifies development, and facilitates independent scaling of functionalities. In practice, most system services can be handled by the BSP, especially the more generic ones, see examples below.
Independent Operation Through Processes and Tasks
One of the key characteristics of system services is their ability to operate concurrently. They typically run in separate processes or tasks, allowing them to execute independently without interfering with each other. This enables efficient resource utilization and responsiveness within the system.
Specifying Service Behavior: Start-up, Execution, and Termination
To ensure proper system operation, it’s crucial to specify various aspects of a service’s behavior:
- Start-up Order: Define the order in which services are launched during system boot. This ensures that services with dependencies are started in the correct sequence. E.g., a web server is started only when the network is online and the database service runs.
- Termination Behavior: Specify how a service should behave when it terminates unexpectedly. Options might include:
- One-Shot Execution: The service runs only once and exits.
- Respawn: The service automatically restarts if it crashes or exits unexpectedly. This is often used for critical services.
- Manual Restart: The service requires manual intervention to be restarted.
Specifying System Services in Linux with systemd
Popular embedded Linux systems often leverage tools like systemd
for efficient service management. systemd
allows you to define service configuration files that specify details like:
- Service Name and Description: A clear identifier and description of the service.
- User and Group: The user and group under which the service should run.
- Dependencies: Define any services that this service relies on and must be started before it.
- Start-up Type: Specify the behavior at boot (automatic, manual, etc.) and restart behavior upon failure.
- Execution Commands: Define the commands or scripts to be executed to start, stop, and manage the service.
By carefully specifying these service parameters, you can ensure a well-coordinated and reliable service ecosystem within your embedded system.
Examples of Generic System Services
Here are a few examples of generic system services commonly found in embedded systems:
- Network Services: Services like DHCP client, web server, or SSH server facilitate network communication and remote access.
- Device Management: Services might manage hardware components like sensors, actuators, or communication interfaces.
- Logging Services: Services handle logging system events and messages for debugging and monitoring purposes.
- Application Service: The core application logic itself can be treated as a service, interacting with other services and the system.
Remember, a well-defined set of system services forms the foundation for a robust and efficient embedded system, enabling modularity, independent operation, and coordinated system functionality.
7. List the user libraries
The Board Support Package (BSP) provides the foundation for your embedded system’s software environment. This includes essential user libraries that your application can leverage. Here’s a breakdown of how to specify the user libraries that should be included within your BSP.
Essential Components
The BSP should include the following user libraries:
- BSP Libraries: Libraries developed specifically for the BSP to interact with the hardware or provide low-level functionalities. These libraries are essential for the proper functioning of the BSP itself.
- Core System Libraries: Core system libraries like
libc
(standard C library) andlibpthread
(POSIX threads) provide fundamental functionalities for application development. These libraries are typically pre-selected by the chosen operating system and BSP development environment.
Optional Application Libraries
In some cases, your application might rely on additional user libraries that are not part of the core system or BSP functionalities. These application libraries should be included in the BSP if they are pre-existing and not developed specifically for your business application. Here are considerations for including these applications libraries:
- Statically Linked Libraries: If the libraries are small and essential for the application’s core functionality, consider statically linking them into your application. This eliminates the need for separate library files at runtime and simplifies deployment.
- Dynamic Libraries: For larger libraries or those with potential versioning conflicts, consider including them dynamically within the BSP. This allows for easier updates and avoids bloating the application binary size. The BSP would need to ensure the necessary libraries are available at runtime for the application to function correctly.
Choosing Between Static and Dynamic Libraries
In a Linux environment, the decision between static and dynamic libraries depends on several factors:
- Application Size: Statically linking libraries increases the size of your application binary. If memory is a significant constraint, consider dynamic libraries.
- Memory Management: Dynamic libraries require additional memory overhead at runtime to manage loading and linking. If memory usage is critical, static linking might be preferable.
- Library Updates: Dynamic libraries allow for easier updates without recompiling the application. This is beneficial for libraries that might evolve over time.
- Deployment Complexity: Statically linked applications are simpler to deploy as they don’t rely on separate library files. However, dynamic libraries can simplify updates in the field.
Finding the Right Balance
The optimal approach often involves a combination of static and dynamic linking. The BSP can provide core system libraries statically for a smaller footprint, while offering the option for applications to dynamically link with larger or more specialized libraries.
It’s crucial to carefully evaluate your application’s needs and resource constraints to determine the most appropriate library inclusion strategy for your BSP. Working collaboratively with the BSP development team can ensure a well-balanced and efficient user library selection process.
8. Consider extra debug features
… and specify them!
While they might add complexity and potential security concerns, debug features are critical components for efficient development and troubleshooting of your embedded system. Considering them from the outset as essential elements of the BSP ensures a robust foundation for the development process.
Balancing Functionality with Resource Constraints
It’s true that debug features can introduce additional burden on the system by:
- Increased Resource Consumption: Debuggers often require additional memory and processing power, which might be limited on resource-constrained embedded systems.
- Cybersecurity Concerns: Leaving debug features enabled in production environments can introduce security vulnerabilities. Debuggers might expose sensitive information or provide unauthorized access points.
Dual Target Compilation Framework: A Strategic Approach
To mitigate these concerns, consider implementing a dual target compilation framework within your BSP development environment. This framework allows you to build separate targets for:
- Production Target: This target is optimized for resource efficiency and security. It excludes debug features and functionalities, resulting in a lean and secure production image.
- Debug Target: This target incorporates all the necessary debug features and functionalities. It’s intended for development and testing purposes only. By using the debug target during development, you can leverage comprehensive debugging capabilities to identify and resolve issues effectively. Once development is complete, the production target optimized for performance and security is deployed to the final embedded system.
Essential Debug Features for Your BSP
Here’s a list of some valuable debug features to consider integrating into your BSP’s debug target:
- BSP Integration Tools: Tools like
cukinia
or similar target-native test suites provide a user-friendly interface to ensure the BSP contains the features it needs to. - Operating System Debug Features:
- Kernel Debugging: Features like kernel printk statements, oops messages, and kernel symbols allow for in-depth debugging of the operating system kernel. Tools like
gdb
(GNU debugger) can be used to step through kernel code and analyze system behavior. - File System Debugging: Tools like
debugfs
(Linux) provide functionalities to inspect and manipulate the file system during debugging, aiding in filesystem-related issue resolution.
- Kernel Debugging: Features like kernel printk statements, oops messages, and kernel symbols allow for in-depth debugging of the operating system kernel. Tools like
- Extended Logging: Configure comprehensive logging mechanisms within your application and the BSP to capture detailed system events and messages during debug sessions. This additional logging information can provide valuable insights into system behavior.
- JTAG Debugging: Hardware debugging using a JTAG probe allows for low-level access to the processor and memory for advanced debugging scenarios, possibly in real time. This is especially useful on microcontrollers running bare metal or on top of an RTOS like FreeRTOS.
Remember:
Security is paramount. Debug features should be disabled and appropriate access controls implemented before deploying the production target to the final embedded system.
By incorporating these debug features within your BSP’s debug target and adhering to a secure development workflow, you can establish a solid foundation for efficient development, troubleshooting, and ultimately, a successful embedded system project.
Security: Weaving into the Fabric of Embedded Systems
While you might expect a dedicated chapter on cybersecurity, separating security concerns from core development fosters the misconception that it’s an afterthought. In reality, cybersecurity is a fundamental aspect of embedded system design, not a post-process applied with a layer of security patches. Too many projects fall victim to this disjointed approach, leaving the final system vulnerable.
Security by Design: A Holistic Approach
Effective cybersecurity demands a holistic approach where security considerations are woven into every step of the project specification. This starts at the BSP level and ensures a robust and secure system by design, not by haphazardly plugging holes after the fact. Here’s why this integrated approach is crucial:
- Security is not an add-on: Treating security as a separate layer after development is akin to building a house on a foundation of sand. A security-centric approach builds a solid foundation from the outset.
- Early identification, early mitigation: Integrating security considerations throughout the specification process allows you to identify and mitigate potential threats early in the development cycle, saving time and resources compared to fixing vulnerabilities later.
- A cohesive defense: By considering security during each development stage, you create a cohesive defense system where all elements work together seamlessly to protect your embedded system.
Wrap-up
Successfully navigating the intricacies of embedded system development requires careful planning, a well-defined configuration strategy, and a robust foundation provided by the Board Support Package (BSP). This guide has equipped you with the knowledge to effectively specify your OS configuration, define a secure network layout, and select appropriate user libraries for your BSP.
We understand that navigating the complexities of embedded system development can be a challenge. That’s where Embedded Expertise comes in. Our team possesses the expertise to guide you in specifying the optimal BSP for your chosen hardware and operating system, ensuring compatibility and efficient resource utilization.
By partnering with us, you gain access to a team of specialists who can guide you through every step of the development journey, from initial concept to final deployment.