How to Write a USB driver in linux

Source: Internet
Author: User

From: http://hyt19860117.love.blog.163.com/blog/static/127773729201042435553852/

With the improvement of people's living standards, more and more USB devices are used, but Linux still does not fully Plug and Play in hardware configuration. how to configure and use Linux is also
This article is intended to help you understand how to develop a USB driver to facilitate the configuration and use of USB devices.
Drivers of USB devices can also be used to further learn the general architecture of USB drivers and then compile drivers for special USB devices.

Basic knowledge about USB
USB
Is the English Universal Serial
Bus stands for Universal Serial Bus. USB was initially designed to replace many different low-speed buses (including parallel, serial, and keyboard connections) that connect different types of bus with a single type
. The development of USB has gone beyond these low-speed connection methods. It now supports almost all devices that can connect to PCs. The latest USB specification has been revised theoretically as high as Mbps
High-speed connection. The Linux kernel supports two main types of USB Drivers: drivers on the host system and drivers on the device. From the host's perspective, a common host is a PC.
The driver of the USB device in the host system controls the USB device to be inserted, while the driver of the USB device controls how the device communicates with the host as a USB device. This article describes how to run
How does a USB system run on a PC. We will also use the USB driver framework program as an example to describe in detail. We will not discuss the driver program of the USB device here.

USB driver Basics
Before writing the USB driver, let's first look at the structure of the written USB driver in the kernel, such:

 
USB drivers exist between different kernel subsystems and USB hardware controllers.
The USB driver provides an interface for accessing and controlling USB hardware, without having to consider the various types of USB hardware controllers that the system currently has. USB is a very complex configuration
The Linux Kernel provides us with a subsystem called USB core to handle most of the complexity. USB devices include configuration and interfaces.
(Interface) and the endpoint, the USB device is bound to the interface, rather than the entire USB device. As shown in:

 
The most basic form of USB communication is through the terminal (USB endpoint interrupt, batch, and other time, control
The USB endpoint can only transmit data to one direction, from the host to the device or from the device to the host. The endpoint can be seen as a one-way pipe (pipe ). So we can recognize
Is: the device usually has one or more configurations, and the configuration often has one or more interfaces. The interface usually has one or more settings, the interface does not have or has more than one endpoint. Driver Driver
The program object is registered in the USB subsystem. Later, use the manufacturer and device identification to determine whether the hardware has been installed. The USB core uses a list (a list containing the manufacturer ID and device ID ).
Struct) to determine which driver should be used for a device. The hot plug-in script uses it to determine which driver should be automatically loaded when a specific device is inserted into the system.
The above briefly describes the basic theory of the driver. before writing a device driver, we also need to understand the following two concepts: modules and device files.
Module
Block: it is a program running in the kernel space. It is actually a target object file and cannot be run independently without links. However, it can be loaded into the system and run as part of the kernel, this allows you to dynamically expand the kernel.
Yes. The main use of the module is to implement the device driver. In Linux, a hardware driver can be directly loaded into the kernel code. When the kernel is started, the hardware is driven.
Backup. The other is to compile a. ko file in the module mode (. o is used as the module file in the kernel below 2.4. We will refer to the 2.6 kernel, the same as below ). Load the application as needed
Run in the kernel space. Therefore, a hardware driver usually refers to a driver module.
Device File: for a device, it can have a corresponding secret under/dev
A device node. This node exists as a file, but it is not a file in the general sense. It is a device file. More specifically, it is a device node. This node is created using the mknod command, where
The primary device number and secondary device number are specified. The primary device number indicates a certain type of device, which generally corresponds to a specific driver. The secondary device number generally distinguishes different attributes, such as different usage methods, different locations, and different
Operation. This device number is obtained from the/proc/devices file. Therefore, the device node is in the directory only when the driver is in the kernel. The master of this device number (especially the master device number ).
The role is to declare the driver used by the device. Drivers correspond to device numbers one by one. When you open a device file, the operating system knows the driver corresponding to the device. For
Hardware and Linux are driven in this way: first, we must provide a. ko driver module File. To use this driver, first load it. We can use insmod
Xxx. ko, then the driver will be registered with the system based on its own type (character device type or block device type, for example, the mouse is a character device and the hard disk is a block device, after successful registration, the system will report a master configuration.
The primary device ID is the unique identifier of the system. The driver creates a device file typically stored in the/dev directory based on the master device number. When we want to access this hardware, we can
Files are executed using open, read, write, close, and other commands. The driver receives the corresponding read and write operations and operates according to the corresponding functions in its module.
.

USB driver practices
Now
After understanding the above theory, we can write the driver program. If you have a good basic skill and have written the hardware driver in linux, the USB hardware driver is similar to the pci_driver, then write
The USB driver is relatively simple. If you only have a general understanding of the linux hardware driver, it doesn't matter, because there is a framework program in the linux kernel source code that can be borrowed.
Framework Program in/usr/src /~ (Your kernel version, the same as below)/drivers/usb, file name is usb-skeleton.c. Write a USB driver most
There are four things to do: the device to be supported by the driver, the USB driver to be registered, the detection and disconnection, the submission and control of urb (USB request block) (of course, you can also use urb for data transmission, below I
).
Driver supported devices: There is a struct usb_device_id, which provides a list of USB devices supported by the driver of different types, for a driver that only controls a specific USB device, the struct usb_device_id table is defined:
/* List of devices supported by the driver */
Static struct usb_device_id skel_table [] = {
{USB_DEVICE (USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID )},
{}/* Termination entry */
};
MODULE_DEVICE_TABLE (usb, skel_table );
Pair
In the PC driver, MODULE_DEVICE_TABLE is required, and usb must be the first value of the macro, while USB_SKEL_VENDOR_ID and
USB_SKEL_PRODUCT_ID is the manufacturer and product ID of this special device. in the program, we change the defined value to our USB, for example:
/* Define the ID number of the manufacturer and product */
# Define USB_SKEL_VENDOR_ID 0x1234
# Define USB_SKEL_PRODUCT_ID 0x2345
You can run the lsusb command to insert the USB device to the host first. You can also view the manufacturer's USB device manual. Running lsusb on my machine is like this:
Bus 004 Device 001: ID :0000
Bus 003 Device 002: ID 1234: 2345 Abc Corp.
Bus 002 Device 001: ID :0000
Bus 001 Device 001: ID :0000
After obtaining these two values, define them in the program.
Register the USB driver:Institute
Some USB Drivers must create a struct.
Usb_driver. This struct must be filled in by the USB driver, including many callback functions and variables, which describe the USB driver to the USB core code. Create a valid
Struct usb_driver struct, you only need to initialize five fields, in the Framework Program is like this:
Static struct usb_driver skel_driver = {
. Owner = THIS_MODULE,
. Name = "skeleton ",
. Probe = skel_probe,
. Disconnect = skel_disconnect,
. Id_table = skel_table,
};
Struct module * owner: pointer to the module owner of the driver. The USB core uses it to correctly reference and count the USB driver so that it will not be detached at an inappropriate time. This variable should be set to the THIS_MODULE macro.
Const char * name: pointer to the driver name. It must be unique among all USB drivers in the kernel and is usually set to the same name as the driver module name.
Int
(* Probe) (struct usb_interface * intf, const struct usb_device_id
* Id): This is the pointer to the test function in the USB driver. This function is used when the USB core considers that it has an interface (usb_interface) that can be processed by the driver.
Called.
Void (disconnect) (struct usb_interface * intf): pointer to the disconnect function in the USB driver) this function is called by the USB core when it is removed from the system or when the driver is detaching from the USB core.
Const struct usb_device_id * id_table: pointer to the ID device table, which contains a list of USB devices supported by the driver. If this variable is not set, the probe callback function in the USB driver will not be called.
In
There are several other callback functions in this struct that are not very common and will not be described here. Use struct usb_driver
The pointer is the parameter's usb_register_driver function call to put struct
The USB driver is registered with the USB core. This is generally done in the module initialization code of the USB driver:
Static int _ init usb_skel_init (void)
{
Int result;

/* Register the driver to the USB subsystem */
Result = usb_register (& skel_driver );
If (result)
Err ("usb_register failed. Error number % d", result );

Return result;
}
When the USB driver is about to be detached, you need to remove struct usb_driver from the kernel. Call usb_deregister_driver to complete this operation. When a call occurs, any USB interface bound to the driver will be disconnected and the function will be called:
Static void _ exit usb_skel_exit (void)
{
/* Log out the driver from the subsystem */
Usb_deregister (& skel_driver );
}
Probe and disconnect:When
When a device is installed and the USB core determines that the driver should be processed, the probe function is called, the probe function checks the device information passed to it, and determines whether the driver is really suitable for the device. When the driver
For some reason, the disconnect function is called when the device should not be controlled. It can do some cleanup work. In the probe callback function, the USB driver initializes any local struct that may be used to control the USB device.
Save any device-related information to a local struct. The following is the source code of the probe function.
/* Set the endpoint information */
/* Use only the first batch IN and batch OUT endpoints */
Iface_desc = interface-> cur_altsetting;
For (I = 0; I <iface_desc-> desc. bNumEndpoints; ++ I ){
Endpoint = & iface_desc-> endpoint [I]. desc;

If (! Dev-> bulk_in_endpointAddr &&
(Endpoint-> bEndpointAddress & USB_DIR_IN )&&
(Endpoint-> bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
= USB_ENDPOINT_XFER_BULK )){
/* Find a batch IN endpoint */
Buffer_size = endpoint-> wMaxPacketSize;
Dev-> bulk_in_size = buffer_size;
Dev-> bulk_in_endpointAddr = endpoint-> bEndpointAddress;
Dev-> bulk_in_buffer = kmalloc (buffer_size, GFP_KERNEL );
If (! Dev-> bulk_in_buffer ){
Err ("cocould not allocate bulk_in_buffer ");
Goto error;
}
}

If (! Dev-> bulk_out_endpointAddr &&
! (Endpoint-> bEndpointAddress & USB_DIR_IN )&&
(Endpoint-> bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
= USB_ENDPOINT_XFER_BULK )){
/* Locate a batch OUT endpoint */
Dev-> bulk_out_endpointAddr = endpoint-> bEndpointAddress;
}
}
If (! (Dev-> bulk_in_endpointAddr & dev-> bulk_out_endpointAddr )){
Err ("cocould not find both bulk-in and bulk-out endpoints ");
Goto error;
}
In the probe function, this loop first accesses each endpoint existing in this interface and gives this endpoint a local pointer for future access:
For (I = 0; I <iface_desc-> desc. bNumEndpoints; ++ I ){
Endpoint = & iface_desc-> endpoint [I]. desc;
In
After a round of detection, we have an endpoint. If we haven't found a batch IN-type endpoint, we can check whether this endpoint direction is IN. This can be done by checking whether USB_DIR_IN is included IN
The bEndpointAddress endpoint variable is determined. If yes, we are checking whether the endpoint type is batch.
USB_ENDPOINT_XFERTYPE_MASK bit mask to take the value of the bmAttributes variable, and then test whether it matches
USB_ENDPOINT_XFER_BULK value match:
If (! Dev-> bulk_out_endpointAddr &&
! (Endpoint-> bEndpointAddress & USB_DIR_IN )&&
(Endpoint-> bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
= USB_ENDPOINT_XFER_BULK ))
If all these probes pass, the driver will know that it has found the correct endpoint type, you can save the endpoint information to a local struct for later communication with the endpoint:
/* Locate an IN-Batch endpoint */
Buffer_size = endpoint-> wMaxPacketSize;
Dev-> bulk_in_size = buffer_size;
Dev-> bulk_in_endpointAddr = endpoint-> bEndpointAddress;
Dev-> bulk_in_buffer = kmalloc (buffer_size, GFP_KERNEL );
If (! Dev-> bulk_in_buffer ){
Err ("cocould not allocate bulk_in_buffer ");
Goto error;
}
Because the USB driver needs to obtain the local data structure associated with the interface later in the lifecycle of the device, it calls the usb_set_intfdata function and saves it to the struct usb_interface structure for later access
/* Save the Data Pointer to this interface device */
Usb_set_intfdata (interface, dev );
We will call the usb_set_intfdata function later to obtain data. After all this is done, the USB driver must call the usb_register_dev function in the probe function to register the device to the USB core:
/* Register the device to the USB core */
Retval = usb_register_dev (interface, & skel_class );
If (retval ){
/* In some cases, the driver cannot be registered */
Err ("Not able to get a minor for this device .");
Usb_set_intfdata (interface, NULL );
Goto error;
}
When
When a USB device is disconnected, all resources associated with the device should be cleared as much as possible, if you have already called the registration function in the probe function to assign a configuration for the USB device
The USB _ deregister_dev function must be called to return the device number to the USB core. Called before getting from the interface in the disconnected Function
Any data set by usb_set_intfdata is also very important. Set struct
The data pointer in the usb_interface struct is NULL to prevent improper access to the data.
Each interface is tested once in the probe function.
When writing a USB driver, we only need to set the first endpoint, and Other endpoints will automatically complete the test. In the probe function, we should note that the structure struct is used in the kernel.
Usb_host_endpoint is used to describe the USB endpoint.
The structure of usb_endpoint_descriptor contains the real endpoint information, struct
The usb_endpoint_descriptor struct contains all the USB-specific data. In this struct, we need to consider the following fields:
BEndpointAddress: this is a specific USB address. It can be used in combination with USB_DIR_IN and USB_DIR_OUT to determine whether the data of this endpoint is transmitted to the device or the host.
BmAttributes:
This is the type of the endpoint. This value can be used in combination with the bit mask USB_ENDPOINT_XFERTYPE_MASK to determine whether the type of this endpoint is
USB_ENDPOINT_XFER_ISOC (), USB_ENDPOINT_XFER_BULK (batch ),
Which of the following is USB_ENDPOINT_XFER_INT.
WMaxPacketSize: the maximum number of bytes that the endpoint can process at a time. The driver can send data that exceeds this value to the endpoint. In actual transmission, if the data volume exceeds this value, it is split.
BInterval: This value takes effect only when the endpoint type is the interrupt type. It is the interval between the end point interrupt requests, in milliseconds.
Submit and control urb: When the driver has data to be sent to a USB device (in most cases, in the write function of the driver), allocate an urb to transmit the data to the device:
/* Create an urb and allocate a cache to it */
Urb = usb_alloc_urb (0, GFP_KERNEL );
If (! Urb ){
Retval =-ENOMEM;
Goto error;
}
After urb is allocated successfully, you need to create a DMA buffer to efficiently send data to the device. The data transmitted to the driver must be copied to this buffer:
Buf = usb_buffer_alloc (dev-> udev, count, GFP_KERNEL, & urb-> transfer_dma );
If (! Buf ){
Retval =-ENOMEM;
Goto error;
}

If (copy_from_user (buf, user_buffer, count )){
Retval =-EFAULT;
Goto error;
}
After data is correctly copied from the user space to a local buffer, urb must be correctly initialized before it can be submitted to the USB core:
/* Initialize urb */
Usb_fill_bulk_urb (urb, dev-> udev,
Usb_sndbulkpipe (dev-> udev, dev-> bulk_out_endpointAddr ),
Buf, count, skel_write_bulk_callback, dev );
Urb-> transfer_flags | = URB_NO_TRANSFER_DMA_MAP;
Then urb can be submitted to the USB core for transmission to the device:
/* Send data from the batch OUT port */
Retval = usb_submit_urb (urb, GFP_KERNEL );
If (retval ){
Err ("% s-failed submitting write urb, error % d", _ FUNCTION __, retval );
Goto error;
}
After urb is successfully transferred to a USB device, the urb callback function is called by the USB core. In our example, we initialize urb to point it to the skel_write_bulk_callback function. The following is the function:
Static void skel_write_bulk_callback (struct urb * urb, struct pt_regs * regs)
{
Struct usb_skel * dev;

Dev = (struct usb_skel *) urb-> context;

If (urb-> status &&
! (Urb-> status =-ENOENT |
Urb-> status =-ECONNRESET |
Urb-> status =-eshudown )){
Dbg ("% s-nonzero write bulk status terminated ed: % d ",
_ FUNCTION __, urb-> status );
}

/* Release the allocated buffer */
Usb_buffer_free (urb-> dev, urb-> transfer_buffer_length,
Urb-> transfer_buffer, urb-> transfer_dma );
}
Sometimes the USB driver only needs to send or receive some simple data, and the driver does not need urb for data transmission. This involves two simple interface functions: usb_bulk_msg and usb_control_msg, in this USB frame program, read operations are such an application:
/* Block batch reads to obtain data from devices */
Retval = usb_bulk_msg (dev-> udev,
Usb_rcvbulkpipe (dev-> udev, dev-> bulk_in_endpointAddr ),
Dev-> bulk_in_buffer,
Min (dev-> bulk_in_size, count ),
& Count, HZ * 10 );

/* If the read is successful, copy it to the user space */
If (! Retval ){
If (copy_to_user (buffer, dev-> bulk_in_buffer, count ))
Retval =-EFAULT;
Else
Retval = count;
}
The usb_bulk_msg interface function is defined as follows:
Int usb_bulk_msg (struct usb_device * usb_dev, unsigned int pipe,
Void * data, int len, int * actual_length, int timeout );
The parameters are as follows:
Struct usb_device * usb_dev: pointer to the target USB device sent by batch messages.
Unsigned int pipe: The specific endpoint of the target USB device sent by batch message. This value is created by calling usb_sndbulkpipe or usb_rcvbulkpipe.
Void * data: if it is an OUT endpoint, It is a pointer to the data to be sent to the device. If it is an IN endpoint, It is a pointer to the location where the data read from the device should be stored.
Int len: the buffer size specified by the data parameter.
Int * actual_length: pointer to the location where the actual number of transmitted bytes is stored. Whether the data is transmitted to the device or received from the device depends on the direction of the endpoint.
Int timeout: the timeout time in the unit of Jiffies. If the value is 0, this function waits until the message ends.
If the interface function is successfully called, the return value is 0. Otherwise, a negative error value is returned.
The usb_control_msg interface function is defined as follows:
Int
Usb_control_msg (struct usb_device * dev, unsigned int pipe ,__ u8
Request ,__ u8requesttype ,__ 2010value ,__ exam index, void * data ,__
Size, int timeout)
In addition to allowing the driver to send and receive USB control messages, the operation of the usb_control_msg function is similar to that of the usb_bulk_msg function. Its parameters are different from those of usb_bulk_msg:
Struct usb_device * dev: pointer to the target USB device that controls the message sent.
Unsigned int pipe: controls the specific endpoint of the target USB device sent by the message. This value is created by calling usb_sndctrlpipe or usb_rcvctrlpipe.
_ U8 request: control the USB request value of the message.
_ U8 requesttype: control the USB request type value of the message.
_ Value: controls the USB message value of a message.
_ Index: controls the USB message index value of a message.
Void * data: if it is an OUT endpoint, it refers to the pointer to the data that will be sent to the device. If it is an IN endpoint, It is a pointer to the location where the data read from the device should be stored.
_ Size: the buffer size specified by the data parameter.
Int timeout: the timeout time in Jiffies units. If it is 0, this function will wait until the message ends.
If this interface function is successfully called, the number of bytes transmitted to or read from the device is returned. If it fails, a negative error value is returned.
Neither of these two interface functions can be called in an interrupt context or when a spin lock is held. Similarly, this function cannot be canceled by any other functions. Exercise caution when using this function.
We want to write a driver for an unknown USB device. We only need to make a slight modification to this framework program. We have already said that we want to modify the ID number of the manufacturer and product, change 0xfff0 to an unknown usb id.
# Define USB_SKEL_VENDOR_ID 0xfff0
# Define USB_SKEL_PRODUCT_ID 0xfff0
Also
There is a way to write the interface endpoint type to be tested IN the probe function. IN this framework program, only the IN and OUT endpoints (USB_ENDPOINT_XFER_BULK) are detected.
In this case, use a mask (USB_ENDPOINT_XFERTYPE_MASK) to test other endpoint types. The driver will perform a test on each interface of the USB device,
After the probe is successful, the driver is bound to this interface. Then there is the urb initialization problem. If you only write a simple USB driver, you don't have to worry about it. The Framework Program is enough.
Here we will briefly introduce three auxiliary functions for initializing urb:
Usb_fill_int_urb: its function prototype is as follows:
Void usb_fill_int_urb (struct urb * urb, struct usb_device * dev,
Unsigned int pipe, void * transfer_buff,
Int buffer_length, usb_complete_t complete,
Void * context, int interval );
This function is used to correctly initialize the urb that will be sent to the interrupt endpoint of the USB device.
Usb_fill_bulk_urb: its function prototype is as follows:
Void usb_fill_bulk_urb (struct urb * urb, struct usb_device * dev,
Unsigned int pipe, void * transfer_buffer,
Int buffer_length, usb_complete_t complete)
This function is used to correctly initialize the batch urb endpoint.
Usb_fill_control_urb: its function prototype is as follows:
Void
Usb_fill_control_urb (struct urb * urb, struct usb_device * dev, unsigned
Int pipe, unsigned char * setup_packet, void * transfer_buffer, int
Buffer_length, usb_complete_t complete, void * context );
This function is used to properly initialize and control the urb endpoint.
There is also a URL for initialization and so on. It has no initialization function yet, so they must be manually initialized in the driver before being committed to the USB core, refer to/usr/src/~ under the kernel source code tree /~ The konicawc. c file under/drivers/usb/media.

Compilation, configuration, and use of driver modules
Now our driver has been roughly written, and then we can compile it into a module in linux to insert the driver module into the kernel and run it. The compiled Makefile file can be written as follows:
Ifneq ($ (KERNELRELEASE ),)
Obj-m: = xxx. o
Else
KERNELDIR? =/Lib/modules/$ (shell uname-r)/build
PWD: = $ (shell pwd)
Default:
$ (MAKE)-C $ (KERNELDIR) M = $ (PWD) modules
Endif
Clean:
Rm-rf *. mod. **. o *. ko. *. ko. *. tmp *. *. mod. o. *. *. o .*
Its
Xxx is the file name of the source file. in linux, run make to generate the driver module (xxx. ko. Use insmod After generating the driver module
Xxx. ko can be inserted into the kernel and run. With lsmod, you can see the modules you have inserted into the kernel, or use the rmmod command in the system.
Xxx uninstalls the module. If the compiled driver module is copied to/lib/modules /~ /Kernel/drivers/usb/, and then depmod,
When you insert a USB device, the system will automatically load the driver module for you. Of course, this must be supported by hotplug; after the driver module is loaded successfully, the device file is generated under/dev /.
If you run the command cat/proc/bus/usb/devices, we can see that the driver has been bound to the interface:
T: Bus = 03 lev= 01 Prnt = 01 Port = 01 Cnt = 01 Dev # = 2 Spd = 12 MxCh = 0
D: Ver = 1.10 Cls = 02 (comm.) Sub = 00 Prot = 00 MxPS = 8 # Cfgs = 1
P: Vendor = 1234 ProdID = 2345 Rev = 1.10
C: * # Ifs = 1 Cfg # = 1 xcode = c0 MxPwr = 0mA
I: If # = 1 Alt = 0 # EPs = 2 Cls = 0a (data) Sub = 00 Prot = 00 Driver = test_usb_driver/* our Driver */
E: Ad = 01 (O) xcode = 02 (Bulk) MxPS = 64 Ivl = 0 ms
E: Ad = 82 (I) xcode = 02 (Bulk) MxPS = 64 Ivl = 0 ms
The framework program generates a device file skel0 (which can be freely modified). Now, you can open, read, write, and close the device file.

Conclusion
Surface
For the emergence of new USB devices, someone must constantly write new drivers so that these devices can work normally in linux. In this sense, writing the driver itself is a non-
This article is a very meaningful work. It helps developers who are interested in writing drivers to learn more about the Design of USB drivers, so as to attract more people to join this article.
Team. Linux not only provides us with a top-quality operating system, but also provides us with the opportunity to participate in its future development process, from which we can get endless happiness!

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.