未验证 提交 809a8a13 编写于 作者: O openharmony_ci 提交者: Gitee

!19991 翻译完成 18957+18890+19120

Merge pull request !19991 from ester.zhou/TR-18957
# EnterpriseAdminExtensionAbility Development
# EnterpriseAdminExtensionAbility
## Introduction to EnterpriseAdminExtensionAbility
......@@ -8,34 +8,35 @@ EnterpriseAdminExtensionAbility is a mandatory component for Mobile Device Manag
EnterpriseAdminExtensionAbility is applicable only to enterprise administrator applications.
## Observing Activation/Deactivation of a Device Administrator Application and Installation/Removal of an Application
## Observing Activation/Deactivation of a Device Administrator Application and Installation/Uninstallation of an Application
### Overview
**onAdminEnabled**: When an enterprise administrator or employee deploys an MDM application and activates the device administrator application, this callback is invoked to notify the MDM application that the DeviceAdmin permission is activated. The initialization policy of the MDM application can set in **onAdminEnabled**.
**onAdminEnabled**: called when an enterprise administrator or employee deploys an MDM application and enables the DeviceAdmin permission for the application. The MDM application can set the initialization policy in the **onAdminEnabled** callback.
**onAdminDisabled**: When the device administrator application is deactivated, the callback is invoked to notify the MDM application that the DeviceAdmin permission is deactivated.
**onAdminDisabled**: called when the system or employee disables the DeviceAdmin permission to notify the enterprise administrator that the device is no longer managed.
**onBundleAdded**: The enterprise administrator can subscribe to application installation and uninstallation events. When an application is installed on an enterprise device, the MDM application reports the event in this callback to notify the enterprise administrator.
**onBundleAdded**: The enterprise administrator can subscribe to application installation events. When an application is installed on an enterprise device, the MDM application reports the event in this callback to notify the enterprise administrator.
**onBundleRemoved**: When an application is removed from an enterprise device, the MDM application reports the event in this callback to notify the enterprise administrator.
**onBundleRemoved**: The enterprise administrator can subscribe to application uninstallation events. When an application is uninstalled on an enterprise device, the MDM application reports the event in this callback to notify the enterprise administrator.
### Available APIs
| Class | API | Description |
| ------------------------------ | ----------------------------------------- | ---------------------------- |
| EnterpriseAdminExtensionAbility | onAdminEnabled(): void | Called when a device administrator application is activated. |
| EnterpriseAdminExtensionAbility | onAdminDisabled(): void | Called when a device administrator application is deactivated.|
| EnterpriseAdminExtensionAbility | onBundleAdded(bundleName: string): void | Called when an application is installed on a device. |
| EnterpriseAdminExtensionAbility | onBundleRemoved(bundleName: string): void | Called when an application is removed from a device. |
| EnterpriseAdminExtensionAbility | onAdminEnabled(): void | Called when the device administrator application is disabled. |
| EnterpriseAdminExtensionAbility | onAdminDisabled(): void | Called when the device administrator application is enabled.|
| EnterpriseAdminExtensionAbility | onBundleAdded(bundleName: string): void | Called when the MDM application is installed. |
| EnterpriseAdminExtensionAbility | onBundleRemoved(bundleName: string): void | Called when the MDM application is uninstalled. |
### How to Develop
To implement EnterpriseAdminExtensionAbility, you need to activate the device administrator application and create **ExtensionAbility** in the code directory of the device administrator application. The procedure is as follows:
1. In the **ets** directory of the target module, right-click and choose **New > Directory** to create a directory named **EnterpriseExtAbility**.
2. Right-click the **EnterpriseExtAbility** directory, and choose **New > TypeScript File** to create a file named **EnterpriseExtAbility.ts**.
3. Open the **EnterpriseExtAbility.ts** file and import the **EnterpriseAdminExtensionAbility** module. Inherit the **EnterpriseAdminExtensionAbility** module to the custom class and add application notification callbacks, such as **onAdminEnabled()** and **onAdminDisabled()**. When the device administrator application is activated or deactivated, the device administrator can receive notifications.
2. Right-click the **EnterpriseExtAbility** directory and choose **New > TypeScript File** to create a file named **EnterpriseExtAbility.ts**.
3. Open the **EnterpriseExtAbility.ts** file and import the **EnterpriseAdminExtensionAbility** module. Customize a class that inherits from **EnterpriseAdminExtensionAbility** and add the required callbacks, such as **onAdminEnabled()** and **onAdminDisabled()**, When the device administrator application is activated or deactivated, the device administrator can receive notifications.
```ts
import EnterpriseAdminExtensionAbility from '@ohos.enterprise.EnterpriseAdminExtensionAbility';
......
# Driver Development
## Driver Model
The Hardware Driver Foundation (HDF) is designed based on a modular driver model to enable refined driver management and streamline driver development and deployment. The HDF allows the same type of device drivers to be placed in a host. The host manages the start and loading of a group of devices. You can deploy dependent drivers to the same host, and deploy independent drivers to different hosts.
The figure below shows the HDF driver model. A device refers to a physical device. A DeviceNode is a component of a device. A device has at least one DeviceNode. Each DeviceNode can publish a device service. Each DevicdNode has a unique driver to interact with the hardware.
**Figure 1** HDF driver model
![](figures/hdf-driver-model.png)
## How to Develop
The HDF-based driver development involves driver implementation, write of the driver compilation script, and driver configuration. The procedure is as follows:
1. Implement a driver.
Write the driver code and register the driver entry with the HDF.
- Write the driver service code. <br>The following is an example:
```c
#include "hdf_device_desc.h" // Header file that defines the driver development APIs provided by the HDF.
#include "hdf_log.h" // Header file that defines the log APIs provided by the HDF.
#define HDF_LOG_TAG "sample_driver" // Tag contained in logs. If no tag is not specified, the default HDF_TAG is used.
// Bind the service APIs provided by the driver to the HDF.
int32_t HdfSampleDriverBind(struct HdfDeviceObject *deviceObject)
{
HDF_LOGD("Sample driver bind success");
return HDF_SUCCESS;
}
// Initialize the driver service.
int32_t HdfSampleDriverInit(struct HdfDeviceObject *deviceObject)
{
HDF_LOGD("Sample driver Init success");
return HDF_SUCCESS;
}
// Release the driver resources.
void HdfSampleDriverRelease(struct HdfDeviceObject *deviceObject)
{
HDF_LOGD("Sample driver release success");
return;
}
```
- Register the driver entry with the HDF.
```c
// Define a driver entry object. It must be a global variable of the HdfDriverEntry type (defined in hdf_device_desc.h).
struct HdfDriverEntry g_sampleDriverEntry = {
.moduleVersion = 1,
.moduleName = "sample_driver",
.Bind = HdfSampleDriverBind,
.Init = HdfSampleDriverInit,
.Release = HdfSampleDriverRelease,
};
// Call HDF_INIT to register the driver entry with the HDF. When loading the driver, the HDF calls Bind() and then Init(). If Init() fails to be called, the HDF will call Release() to release driver resources and exit the driver model.
HDF_INIT(g_sampleDriverEntry);
```
2. Write the driver compilation script.
- LiteOS
Modify **makefile** and **BUILD.gn** files.
- **Makefile**:
Use the **makefile** template provided by the HDF to compile the driver code.
```c
include $(LITEOSTOPDIR)/../../drivers/hdf_core/adapter/khdf/liteos/lite.mk # (Mandatory) Import the HDF predefined content.
MODULE_NAME := # File to be generated.
LOCAL_INCLUDE: = # Directory of the driver header files.
LOCAL_SRCS : = # Source code files of the driver.
LOCAL_CFLAGS : = # Custom build options.
include $(HDF_DRIVER) # Import the makefile template to complete the build.
```
Add the path of the generated file to **hdf_lite.mk** in the **drivers/hdf_core/adapter/khdf/liteos** directory to link the file to the kernel image. <br>The following is an example:
```c
LITEOS_BASELIB += -lxxx # Static library generated by the link.
LIB_SUBDIRS += # Directory in which makefile is located.
```
- **BUILD.gn**:
Add **BUILD.gn**. The content of **BUILD.gn** is as follows:
```c
import("//build/lite/config/component/lite_component.gni")
import("//drivers/hdf_core/adapter/khdf/liteos/hdf.gni")
module_switch = defined(LOSCFG_DRIVERS_HDF_xxx)
module_name = "xxx"
hdf_driver(module_name) {
sources = [
"xxx/xxx/xxx.c", # Source code to compile.
]
public_configs = [ ":public" ] # Head file configuration of the dependencies.
}
config("public") { # Define the head file configuration of the dependencies.
include_dirs = [
"xxx/xxx/xxx", # Directory of dependency header files.
]
}
```
Add the **BUILD.gn** directory to **/drivers/hdf_core/adapter/khdf/liteos/BUILD.gn**.
```c
group("liteos") {
public_deps = [ ":$module_name" ]
deps = [
"xxx/xxx", # Directory where the BUILD.gn of the driver is located. It is a relative path to /drivers/hdf_core/adapter/khdf/liteos.
]
}
```
- Linux
To define the driver control macro, add the **Kconfig** file to the driver directory **xxx** and add the path of the **Kconfig** file to **drivers/hdf_core/adapter/khdf/linux/Kconfig**.
```c
source "drivers/hdf/khdf/xxx/Kconfig" # Kernel directory to which the HDF module is soft linked.
```
Add the driver directory to **drivers/hdf_core/adapter/khdf/linux/Makefile**.
```c
obj-$(CONFIG_DRIVERS_HDF) += xxx/
```
Add a **Makefile** to the driver directory **xxx** and add code compiling rules of the driver to the **Makefile** file.
```c
obj-y += xxx.o
```
3. Configure the driver.
The HDF Configuration Source (HCS) contains the source code of HDF configuration. For details about the HCS, see [Configuration Management](../driver/driver-hdf-manage.md).
The driver configuration consists of the driver device description defined by the HDF and the private driver configuration.
- (Mandatory) Set driver device information.
The HDF loads a driver based on the driver device description defined by the HDF. Therefore, the driver device description must be added to the **device_info.hcs** file defined by the HDF. <br>The following is an example:
```
root {
device_info {
match_attr = "hdf_manager";
template host { // Host template. If a node (for example, sample_host) uses the default values in this template, the node fields can be omitted.
hostName = "";
priority = 100;
uid = ""; // User ID (UID) of the user-mode process. It is left empty by default. If you do not set the value, this parameter will be set to the value of hostName, which indicates a common user.
gid = ""; // Group ID (GID) of the user-mode process. It is left empty by default. If you do not set the value, this parameter will be set to the value of hostName, which indicates a common user group.
caps = [""]]; // Linux capabilities of the user-mode process. It is left empty by default. Set this parameter based on service requirements.
template device {
template deviceNode {
policy = 0;
priority = 100;
preload = 0;
permission = 0664;
moduleName = "";
serviceName = "";
deviceMatchAttr = "";
}
}
}
sample_host :: host{
hostName = "host0"; // Host name. The host node is used as a container to hold a type of drivers.
priority = 100; // Host startup priority (0-200). A smaller value indicates a higher priority. The default value 100 is recommended. The hosts with the same priority start based on the time when the priority was configured. The host configured first starts first.
caps = ["DAC_OVERRIDE", "DAC_READ_SEARCH"]; // Linux capabilities of a user-mode process.
device_sample :: device { // Sample device node.
device0 :: deviceNode { // DeviceNode of the sample driver.
policy = 1; // Policy for publishing the driver service. For details, see Driver Service Management.
priority = 100; // Driver startup priority (0-200). A smaller value indicates a higher priority. The default value 100 is recommended. The drivers with the same priority start based on the time when the priority was configured. The driver configured first starts first.
preload = 0; // The value 0 means to load the driver by default during the startup of the system.
permission = 0664; // Permission for the DeviceNode created.
moduleName = "sample_driver"; // Driver name. The value must be the same as that of moduleName in the HdfDriverEntry structure.
serviceName = "sample_service"; // Name of the service published by the driver. The service name must be unique.
deviceMatchAttr = "sample_config"; // Keyword for matching the private data of the driver. The value must be the same as that of match_attr in the private data configuration table of the driver.
}
}
}
}
}
```
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**<br/>
>
> - **uid**, **gid**, and **caps** are startup parameters for user-mode drivers only.
>
> - According to the principle of least privilege for processes, **uid** and **gid** do not need to be configured for service modules. In the preceding example, **uid** and **gid** are left empty (granted with the common user rights) for sample_host.
>
> - If you need to set **uid** and **gid** to **system** or **root** due to service requirements, contact security experts for review.
>
> - The process UIDs are configured in **base/startup/init/services/etc/passwd**, and the process GIDs are configured in **base/startup/init/services/etc/group**. For details, see [Adding a System Service User Group]( https://gitee.com/openharmony/startup_init_lite/wikis).
>
> - The **caps** value is in the caps = ["xxx"] format. To configure **CAP_DAC_OVERRIDE**, set this parameter to **caps = ["DAC_OVERRIDE"]**. Do not set it to **caps = ["CAP_DAC_OVERRIDE"]**.
>
> - **preload** specifies the driver loading policy. For details, see [Driver Loading](../driver/driver-hdf-load.md).
- (Optional) Set driver private information.
If the driver has private configuration, add a driver configuration file to set default driver configuration. When loading the driver, the HDF obtains and saves the driver private information in **property** of **HdfDeviceObject**, and passes the information to the driver using **Bind()** and **Init()** (see step 1). <br>The following is an example of the driver configuration:
```
root {
SampleDriverConfig {
sample_version = 1;
sample_bus = "I2C_0";
match_attr = "sample_config"; // The value must be the same as that of deviceMatchAttr in device_info.hcs.
}
}
```
Add the configuration file to the **hdf.hcs** file. <br>The following is an example:
```
#include "device_info/device_info.hcs"
#include "sample/sample_config.hcs"
```
# Driver Loading
## Overview
The HDF loads the drivers that match the configured device list.
## Driver Loading Policies
The HDF supports on-demand loading and sequential loading of drivers. The **preload** field in the configuration file (see [Driver Development](../driver/driver-hdf-development.md)) specifies the loading policy of a driver. The values are as follows:
```c
typedef enum {
DEVICE_PRELOAD_ENABLE = 0,
DEVICE_PRELOAD_ENABLE_STEP2 = 1,
DEVICE_PRELOAD_DISABLE = 2,
DEVICE_PRELOAD_INVALID
} DevicePreload;
```
### On-Demand Loading
- The value **0** (**DEVICE_PRELOAD_ENABLE**) means to load the driver by default during the system boot process.
- The value **1** (**DEVICE_PRELOAD_ENABLE_STEP2**) means to load the driver after a quick start is complete. If the system does not support quick start, the value **1** has the same meaning as **DEVICE_PRELOAD_ENABLE**.
- The value **2** (**DEVICE_PRELOAD_DISABLE**) means to load the driver dynamically. When a user-mode process requests the driver service, the HDF attempts to dynamically load the driver if the driver service is not available. For more details, see [Driver Messaging Mechanism](../driver/driver-hdf-message-management.md).
### Sequential Loading (Default)
The **priority** field (ranging from 0 to 200) in the configuration file determines the loading sequence of a host and a driver. For the drivers in different hosts, the driver with a smaller priority value is loaded first. For the drivers in the same host, the driver with a smaller priority value is loaded first.
### Exception Recovery (User-Mode Driver)
The policies for restoring from a driver service exception are as follows:
- If **preload** is set to **0** (**DEVICE_PRELOAD_ENABLE**) or **1** (**DEVICE_PRELOAD_ENABLE_STEP2**) for the driver service, the startup module starts the host and reloads the service.
- If **preload** is set to **2** (**DEVICE_PRELOAD_DISABLE**), the service module needs to register an HDF service status listener. When receiving a notification on service exit, the service module calls **LoadDevice()** to reload the service.
# Driver Messaging Mechanism
## When to Use
The HDF messaging mechanism implements the interaction between the user-mode applications and kernel-mode drivers.
## Available APIs
The messaging mechanism allows:
1. A user-mode application to send a message to a driver.
2. A user-mode application to receive events reported by a driver.
**Table 1** APIs for the driver messaging mechanism
| API| Description|
| -------- | -------- |
| struct HdfIoService \*HdfIoServiceBind(const char \*serviceName); | Obtains a driver service. After obtaining the driver service, the user-mode application uses **Dispatch()** in the driver service obtained to send messages to the driver.|
| void HdfIoServiceRecycle(struct HdfIoService \*service); | Releases a driver service.|
| int HdfDeviceRegisterEventListener(struct HdfIoService \*target, struct HdfDevEventlistener \*listener); | Registers the method for receiving events reported by the driver.|
| int HdfDeviceSendEvent(struct HdfDeviceObject \*deviceObject, uint32_t id, struct HdfSBuf \*data); | Sends events. |
## How to Develop
1. In the driver configuration file, set **policy** to **2**. For more details, see [Driver Service Management](../driver/driver-hdf-servicemanage.md).
```
device_sample :: Device {
policy = 2;
...
}
```
2. Set permissions for the device node of the driver. By default, the **permission** field is set to **0666**. You can set it based on service requirements.
3. Implement the **Dispatch()** method of **IDeviceIoService**.
```c
// Dispatch() is used to process messages sent from the user-mode application.
int32_t SampleDriverDispatch(struct HdfDeviceIoClient *device, int cmdCode, struct HdfSBuf *data, struct HdfSBuf *reply)
{
HDF_LOGI("sample driver lite A dispatch");
return HDF_SUCCESS;
}
int32_t SampleDriverBind(struct HdfDeviceObject *device)
{
HDF_LOGI("test for lite os sample driver A Open!");
if (device == NULL) {
HDF_LOGE("test for lite os sample driver A Open failed!");
return HDF_FAILURE;
}
static struct ISampleDriverService sampleDriverA = {
.ioService.Dispatch = SampleDriverDispatch,
.ServiceA = SampleDriverServiceA,
.ServiceB = SampleDriverServiceB,
};
device->service = (struct IDeviceIoService *)(&sampleDriverA);
return HDF_SUCCESS;
}
```
4. Define the cmd type in the message processing function.
```c
#define SAMPLE_WRITE_READ 1 // Read and write operation 1
```
5. Enable the user-mode application to obtain a service and send a message to the driver.
```c
int SendMsg(const char *testMsg)
{
if (testMsg == NULL) {
HDF_LOGE("test msg is null");
return HDF_FAILURE;
}
struct HdfIoService *serv = HdfIoServiceBind("sample_driver");
if (serv == NULL) {
HDF_LOGE("fail to get service");
return HDF_FAILURE;
}
struct HdfSBuf *data = HdfSbufObtainDefaultSize();
if (data == NULL) {
HDF_LOGE("fail to obtain sbuf data");
return HDF_FAILURE;
}
struct HdfSBuf *reply = HdfSbufObtainDefaultSize();
if (reply == NULL) {
HDF_LOGE("fail to obtain sbuf reply");
ret = HDF_DEV_ERR_NO_MEMORY;
goto out;
}
if (!HdfSbufWriteString(data, testMsg)) {
HDF_LOGE("fail to write sbuf");
ret = HDF_FAILURE;
goto out;
}
int ret = serv->dispatcher->Dispatch(&serv->object, SAMPLE_WRITE_READ, data, reply);
if (ret != HDF_SUCCESS) {
HDF_LOGE("fail to send service call");
goto out;
}
out:
HdfSbufRecycle(data);
HdfSbbufRecycle(reply);
HdfIoServiceRecycle(serv);
return ret;
}
```
6. Enable the user-mode application to receive messages from the driver.
1. Implement the method for the user-mode application to process the events reported by the driver.
```c
static int OnDevEventReceived(void *priv, uint32_t id, struct HdfSBuf *data)
{
OsalTimespec time;
OsalGetTime(&time);
HDF_LOGI("%{public}s received event at %{public}llu.%{public}llu", (char *)priv, time.sec, time.usec);
const char *string = HdfSbufReadString(data);
if (string == NULL) {
HDF_LOGE("fail to read string in event data");
return HDF_FAILURE;
}
HDF_LOGI("%{public}s: dev event received: %{public}d %{public}s", (char *)priv, id, string);
return HDF_SUCCESS;
}
```
2. Register the method for the user-mode application to receive messages from the driver.
```c
int RegisterListen()
{
struct HdfIoService *serv = HdfIoServiceBind("sample_driver");
if (serv == NULL) {
HDF_LOGE("fail to get service");
return HDF_FAILURE;
}
static struct HdfDevEventlistener listener = {
.callBack = OnDevEventReceived,
.priv ="Service0"
};
if (HdfDeviceRegisterEventListener(serv, &listener) != 0) {
HDF_LOGE("fail to register event listener");
return HDF_FAILURE;
}
......
HdfDeviceUnregisterEventListener(serv, &listener);
HdfIoServiceRecycle(serv);
return HDF_SUCCESS;
}
```
3. Enable the driver to report events.
```c
int32_t SampleDriverDispatch(HdfDeviceIoClient *client, int cmdCode, struct HdfSBuf *data, struct HdfSBuf *reply)
{
// Process the API call.
return HdfDeviceSendEvent(client->device, cmdCode, data);
}
```
# HDF Overview
## Introduction
The Hardware Driver Foundation (HDF) provides driver framework capabilities, such as driver loading, driver service management, driver messaging mechanism, and configuration management, for driver developers. It strives to provide a more precise and efficient driver development environment, where you can perform one-time development for multi-device deployment.
## Driver Loading
The HDF supports the following loading modes:
- On-demand loading
The HDF allows drivers to be loaded by default during the operating system (OS) boot process or dynamically loaded after the OS is started.
- Sequential loading
The HDF allows drivers to be loaded based on their priorities during the OS boot process.
## Driver Service Management
The HDF supports centralized management of driver services. You can obtain a driver service by using the API provided by the HDF.
## Driver Messaging Mechanism
The HDF provides a unified driver messaging mechanism, which allows messages to be exchanged between user-mode applications and kernel-mode drivers.
## Configuration Management
HDF Configuration Source (HCS) provides the source code that describes the HDF configuration in key-value pairs. It decouples the configuration code from driver code, simplifying configuration management.
# HDF Development Example
The following is a HDF-based driver development example.
## Adding Driver Configuration
Add the driver configuration to the HDF configuration file, for example, **vendor/hisilicon/xxx/hdf_config/device_info**.
```
root {
device_info {
match_attr = "hdf_manager";
template host {
hostName = "";
priority = 100;
template device {
template deviceNode {
policy = 0;
priority = 100;
preload = 0;
permission = 0664;
moduleName = "";
serviceName = "";
deviceMatchAttr = "";
}
}
}
sample_host :: host {
hostName = "sample_host";
sample_device :: device {
device0 :: deviceNode {
policy = 2;
priority = 100;
preload = 1;
permission = 0664;
moduleName = "sample_driver";
serviceName = "sample_service";
}
}
}
}
}
```
## Writing the Driver Code
Write the driver code based on the HDF. For more details, see [Driver Development](../driver/driver-hdf-development.md).
```c
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include "hdf_log.h"
#include "hdf_base.h"
#include "hdf_device_desc.h"
#define HDF_LOG_TAG sample_driver
#define SAMPLE_WRITE_READ 123
static int32_t HdfSampleDriverDispatch(
struct HdfDeviceIoClient *client, int id, struct HdfSBuf *data, struct HdfSBuf *reply)
{
HDF_LOGI("%{public}s: received cmd %{public}d", __func__, id);
if (id == SAMPLE_WRITE_READ) {
const char *readData = HdfSbufReadString(data);
if (readData != NULL) {
HDF_LOGE("%{public}s: read data is: %{public}s", __func__, readData);
}
if (!HdfSbufWriteInt32(reply, INT32_MAX)) {
HDF_LOGE("%{public}s: reply int32 fail", __func__);
}
return HdfDeviceSendEvent(client->device, id, data);
}
return HDF_FAILURE;
}
static void HdfSampleDriverRelease(struct HdfDeviceObject *deviceObject)
{
// Release resources.
return;
}
static int HdfSampleDriverBind(struct HdfDeviceObject *deviceObject)
{
if (deviceObject == NULL) {
return HDF_FAILURE;
}
static struct IDeviceIoService testService = {
.Dispatch = HdfSampleDriverDispatch,
};
deviceObject->service = &testService;
return HDF_SUCCESS;
}
static int HdfSampleDriverInit(struct HdfDeviceObject *deviceObject)
{
if (deviceObject == NULL) {
HDF_LOGE("%{public}s::ptr is null!", __func__);
return HDF_FAILURE;
}
HDF_LOGI("Sample driver Init success");
return HDF_SUCCESS;
}
static struct HdfDriverEntry g_sampleDriverEntry = {
.moduleVersion = 1,
.moduleName = "sample_driver",
.Bind = HdfSampleDriverBind,
.Init = HdfSampleDriverInit,
.Release = HdfSampleDriverRelease,
};
HDF_INIT(g_sampleDriverEntry);
```
## Implementing Interaction Between the Application and the Driver
Write the code for interaction between the user-mode application and the driver. Place the code in the **drivers/hdf_core/adapter/uhdf** directory for compilation. For details about **BUILD.gn**, see **drivers/hdf_core/framework/sample/platform/uart/dev/BUILD.gn**.
```c
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include "hdf_log.h"
#include "hdf_sbuf.h"
#include "hdf_io_service_if.h"
#define HDF_LOG_TAG sample_test
#define SAMPLE_SERVICE_NAME "sample_service"
#define SAMPLE_WRITE_READ 123
int g_replyFlag = 0;
static int OnDevEventReceived(void *priv, uint32_t id, struct HdfSBuf *data)
{
const char *string = HdfSbufReadString(data);
if (string == NULL) {
HDF_LOGE("fail to read string in event data");
g_replyFlag = 1;
return HDF_FAILURE;
}
HDF_LOGI("%{public}s: dev event received: %{public}u %{public}s", (char *)priv, id, string);
g_replyFlag = 1;
return HDF_SUCCESS;
}
static int SendEvent(struct HdfIoService *serv, char *eventData)
{
int ret = 0;
struct HdfSBuf *data = HdfSbufObtainDefaultSize();
if (data == NULL) {
HDF_LOGE("fail to obtain sbuf data");
return 1;
}
struct HdfSBuf *reply = HdfSbufObtainDefaultSize();
if (reply == NULL) {
HDF_LOGE("fail to obtain sbuf reply");
ret = HDF_DEV_ERR_NO_MEMORY;
goto out;
}
if (!HdfSbufWriteString(data, eventData)) {
HDF_LOGE("fail to write sbuf");
ret = HDF_FAILURE;
goto out;
}
ret = serv->dispatcher->Dispatch(&serv->object, SAMPLE_WRITE_READ, data, reply);
if (ret != HDF_SUCCESS) {
HDF_LOGE("fail to send service call");
goto out;
}
int replyData = 0;
if (!HdfSbufReadInt32(reply, &replyData)) {
HDF_LOGE("fail to get service call reply");
ret = HDF_ERR_INVALID_OBJECT;
goto out;
}
HDF_LOGI("Get reply is: %{public}d", replyData);
out:
HdfSbufRecycle(data);
HdfSbufRecycle(reply);
return ret;
}
int main()
{
char *sendData = "default event info";
struct HdfIoService *serv = HdfIoServiceBind(SAMPLE_SERVICE_NAME);
if (serv == NULL) {
HDF_LOGE("fail to get service %s", SAMPLE_SERVICE_NAME);
return HDF_FAILURE;
}
static struct HdfDevEventlistener listener = {
.callBack = OnDevEventReceived,
.priv ="Service0"
};
if (HdfDeviceRegisterEventListener(serv, &listener) != HDF_SUCCESS) {
HDF_LOGE("fail to register event listener");
return HDF_FAILURE;
}
if (SendEvent(serv, sendData)) {
HDF_LOGE("fail to send event");
return HDF_FAILURE;
}
while (g_replyFlag == 0) {
sleep(1);
}
if (HdfDeviceUnregisterEventListener(serv, &listener)) {
HDF_LOGE("fail to unregister listener");
return HDF_FAILURE;
}
HdfIoServiceRecycle(serv);
return HDF_SUCCESS;
}
```
> ![icon-note.gif](public_sys-resources/icon-note.gif) **NOTE**<br/>
> The user-mode application uses the message sending API of the HDF, and the compilation of the user-mode application depends on the dynamic libraries **hdf_core** and **osal** provided by the HDF. Therefore, you need to add the following dependencies to the .gn file:
>
> deps = [
>
> "//drivers/hdf_core/adapter/uhdf/manager:hdf_core",
>
> "//drivers/hdf_core/adapter/uhdf/posix:hdf_posix_osal",
>
> ]
# Driver Service Management
Driver services are objects of capabilities provided by HDF driver devices to external systems and are managed by the HDF in a unified manner. Driver service management involves publishing and obtaining driver services.
The HDF uses the **policy** field in the configuration file to define policies for a driver to provide services externally. The values this field are as follows:
```c
typedef enum {
/* The driver does not provide services. */
SERVICE_POLICY_NONE = 0,
/* The driver publishes services only for kernel-mode processes. */
SERVICE_POLICY_PUBLIC = 1,
/* The driver publishes services for both kernel- and user-mode processes. */
SERVICE_POLICY_CAPACITY = 2,
/** The driver services are not published externally but can be subscribed to. */
SERVICE_POLICY_FRIENDLY = 3,
/* The driver private services cannot be published externally or subscribed to. */
SERVICE_POLICY_PRIVATE = 4,
/** Invalid service policy. */
SERVICE_POLICY_INVALID
} ServicePolicy;
```
## When to Use
You can use the driver service management capability of the HDF when the driver needs to provide capabilities via APIs.
## Available APIs
The table below describes the APIs for driver service management.
**Table 1** APIs for driver service management
| API| Description|
| -------- | -------- |
| int32_t (\*Bind)(struct HdfDeviceObject \*deviceObject) | Binds a service API to the HDF. You need to implement the **Bind()** function.|
| const struct HdfObject \*DevSvcManagerClntGetService(const char \*svcName)| Obtains a driver service.|
|int HdfDeviceSubscribeService(struct HdfDeviceObject \*deviceObject, const char \*serviceName, struct SubscriberCallback callback) | Subscribes to a driver service.|
## How to Develop
The development procedure is as follows:
1. Define the service to be published by the driver.
```c
// Define the driver service structure.
struct ISampleDriverService {
struct IDeviceIoService ioService; // The first member must be of the IDeviceIoService type.
int32_t (*ServiceA)(void); // API of the first driver service.
int32_t (*ServiceB)(uint32_t inputCode); // API of the second driver service. You can add more as required.
};
// Implement the driver service APIs.
int32_t SampleDriverServiceA(void)
{
// You need to implement the service logic.
return HDF_SUCCESS;
}
int32_t SampleDriverServiceB(uint32_t inputCode)
{
// You need to implement the service logic.
return HDF_SUCCESS;
}
```
2. Bind the driver service.
Implement the **Bind** pointer function, for example, **SampleDriverBind**, in **HdfDriverEntry** to bind the driver service to the HDF.
```c
int32_t SampleDriverBind(struct HdfDeviceObject *deviceObject)
{
// deviceObject is a pointer to the device object created by the HDF for each driver. The device object holds private device data and service APIs.
if (deviceObject == NULL) {
HDF_LOGE("Sample device object is null!");
return HDF_FAILURE;
}
static struct ISampleDriverService sampleDriverA = {
.ServiceA = SampleDriverServiceA,
.ServiceB = SampleDriverServiceB,
};
deviceObject->service = &sampleDriverA.ioService;
return HDF_SUCCESS;
}
```
3. Obtain the driver service.
The driver service can be obtained by using either of the following methods:
- Using the API provided by the HDF
If the service requester clearly knows the time when the driver is loaded, use the API provided by the HDF to obtain the driver service. The following is an example:
```c
const struct ISampleDriverService *sampleService =
(const struct ISampleDriverService *)DevSvcManagerClntGetService("sample_driver");
if (sampleService == NULL) {
return HDF_FAILURE;
}
sampleService->ServiceA();
sampleService->ServiceB(5);
```
- Using the subscription mechanism
If the service requester is unaware of the time when the driver (in the same host) is loaded, use the subscription mechanism provided by the HDF to subscribe to the service. After the driver is loaded, the HDF publishes the driver service to the subscriber. The implementation is as follows:
```c
// Callback invoked to return the driver service after the subscribed driver is loaded.
// object is the pointer to the private data of the subscriber, and service is the pointer to the subscribed service object.
int32_t TestDriverSubCallBack(struct HdfDeviceObject *deviceObject, const struct HdfObject *service)
{
const struct ISampleDriverService *sampleService =
(const struct ISampleDriverService *)service;
if (sampleService == NULL) {
return HDF_FAILURE;
}
sampleService->ServiceA();
sampleService->ServiceB(5);
}
// Implement the subscription process.
int32_t TestDriverInit(struct HdfDeviceObject *deviceObject)
{
if (deviceObject == NULL) {
HDF_LOGE("Test driver init failed, deviceObject is null!");
return HDF_FAILURE;
}
struct SubscriberCallback callBack;
callBack.deviceObject = deviceObject;
callBack.OnServiceConnected = TestDriverSubCallBack;
int32_t ret = HdfDeviceSubscribeService(deviceObject, "sample_driver", callBack);
if (ret != HDF_SUCCESS) {
HDF_LOGE("Test driver subscribe sample driver failed!");
}
return ret;
}
```
......@@ -27,9 +27,9 @@
- [Distributed Remote Startup](subsys-remote-start.md)
- Graphics
- [Graphics Overview](subsys-graphics-overview.md)
- [Common Component Development](subsys-graphics-common-guide.md)
- [Container Component Development](subsys-graphics-container-guide.md)
- [Development of Layout Container Components](subsys-graphics-layout-guide.md)
- [Common Component Development](subsys-graphics-common-guide.md)
- [Animator Development](subsys-graphics-animation-guide.md)
- Multimedia
- Camera
......@@ -79,6 +79,7 @@
- [Component-based Startup](subsys-boot-init-sub-unit.md)
- [init Run Log Standardization](subsys-boot-init-log.md)
- [Seccomp Policy Development](subsys-boot-init-seccomp.md)
- [DeviceInfo Adaptation](subsys-boot-init-deviceInfo.md)
- [appspawn Module](subsys-boot-appspawn.md)
- [bootstrap Module](subsys-boot-bootstrap.md)
- [FAQs](subsys-boot-faqs.md)
......
......@@ -93,40 +93,62 @@
- [Porting Verification](porting/porting-minichip-verification.md)
- [FAQs](porting/porting-chip-faqs.md)
- Small System SoC Porting Guide
- Small System SoC Porting Guide
- Porting Preparation
- Porting Preparation
- [Before You Start](porting/porting-smallchip-prepare-needs.md)
- [Before You Start](porting/porting-smallchip-prepare-needs.md)
- [Compilation and Building](porting/porting-smallchip-prepare-building.md)
- [Compilation and Building](porting/porting-smallchip-prepare-building.md)
- Kernel Porting
- Kernel Porting
- [LiteOS Cortex-A](porting/porting-smallchip-kernel-a.md)
- [LiteOS Cortex-A](porting/porting-smallchip-kernel-a.md)
- [Linux Kernel](porting/porting-smallchip-kernel-linux.md)
- [Linux Kernel](porting/porting-smallchip-kernel-linux.md)
- Driver Porting
- Driver Porting
- [Porting Overview](porting/porting-smallchip-driver-overview.md)
- [Porting Overview](porting/porting-smallchip-driver-overview.md)
- [Platform Driver Porting](porting/porting-smallchip-driver-plat.md)
- [Platform Driver Porting](porting/porting-smallchip-driver-plat.md)
- [Device Driver Porting](porting/porting-smallchip-driver-oom.md)
- [Device Driver Porting](porting/porting-smallchip-driver-oom.md)
- Standard System SoC Porting Guide
- [Standard System Porting Guide](porting/standard-system-porting-guide.md)
- [A Method for Rapidly Porting the OpenHarmony Linux Kernel](porting/porting-linux-kernel.md)
- Third-Party Library Porting Guide for Mini and Small Systems
- Third-Party Library Porting Guide for Mini and Small Systems
- [Overview](porting/porting-thirdparty-overview.md)
- [Porting a Library Built Using CMake](porting/porting-thirdparty-cmake.md)
- [Porting a Library Built Using Makefile](porting/porting-thirdparty-makefile.md)
- Mini System SoC Porting Cases
- [Mini-System Devices with Screens — Bestechnic SoC Porting Case](porting/porting-bes2600w-on-minisystem-display-demo.md)
- [Combo Solution – ASR Chip Porting Case](porting/porting-asr582x-combo-demo.md)
- [Overview](porting/porting-thirdparty-overview.md)
- [Porting a Library Built Using CMake](porting/porting-thirdparty-cmake.md)
- [Porting a Library Built Using Makefile](porting/porting-thirdparty-makefile.md)
- Mini System SoC Porting Cases
- [Mini-System Devices with Screens — Bestechnic SoC Porting Case](porting/porting-bes2600w-on-minisystem-display-demo.md)
- [Combo Solution – ASR Chip Porting Case](porting/porting-asr582x-combo-demo.md)
- [IoT Solution - Chipsea CST85 Chip Porting Case](porting/porting-cst85f01-combo-demo.md)
- [Mini System STM32F407 SoC Porting Case](porting/porting-stm32f407-on-minisystem-eth.md)
- [Combo Solution – W800 Chip Porting Case](porting/porting-w800-combo-demo.md)
- Small System SoC Porting Cases
- [Small System STM32MP1 SoC Porting Case](porting/porting-stm32mp15xx-on-smallsystem.md)
- Standard System SoC Porting Cases
- [Standard System Solution – Rockchip RK3568 Porting Case](porting/porting-dayu200-on_standard-demo.md)
- [Standard System Solution – Rockchip RK3566 Porting Case](https://gitee.com/openharmony/vendor_kaihong/blob/master/khdvk_3566b/porting-khdvk_3566b-on_standard-demo.md)
- [Standard System Solution – Yangfan Porting Case](porting/porting-yangfan-on_standard-demo.md)
- Subsystem Development
- Kernel
......@@ -155,6 +177,7 @@
- [Exception Debugging](kernel/kernel-mini-memory-exception.md)
- [Trace](kernel/kernel-mini-memory-trace.md)
- [LMS](kernel/kernel-mini-memory-lms.md)
- [Shell](kernel/kernel-mini-debug-shell.md)
- Appendix
- [Kernel Coding Specification](kernel/kernel-mini-appx-code.md)
- [Standard Libraries](kernel/kernel-mini-appx-lib.md)
......@@ -190,6 +213,7 @@
- [Dynamic Loading and Linking](kernel/kernel-small-bundles-linking.md)
- [Virtual Dynamic Shared Object](kernel/kernel-small-bundles-share.md)
- [LiteIPC](kernel/kernel-small-bundles-ipc.md)
- [Container](kernel/kernel-small-bundles-container.md)
- File Systems
- [Virtual File System](kernel/kernel-small-bundles-fs-virtual.md)
- [Supported File Systems](kernel/kernel-small-bundles-fs-support.md)
......@@ -293,14 +317,7 @@
- [Lightweight CPU Isolation](kernel/kernel-standard-sched-cpuisolation.md)
- Drivers
- [Driver Overview](driver/driver-overview-foundation.md)
- HDF
- [HDF Overview](driver/driver-hdf-overview.md)
- [Driver Development](driver/driver-hdf-development.md)
- [Driver Loading](driver/driver-hdf-load.md)
- [Driver Service Management](driver/driver-hdf-servicemanage.md)
- [Driver Message Mechanism Management](driver/driver-hdf-message-management.md)
- [Driver Configuration Management](driver/driver-hdf-manage.md)
- [HDF Development Example](driver/driver-hdf-sample.md)
- [Configuration Management](driver/driver-hdf-manage.md)
- Platform Driver Development
- [ADC](driver/driver-platform-adc-develop.md)
- [DAC](driver/driver-platform-dac-develop.md)
......@@ -428,6 +445,7 @@
- [omponent-based Startup](subsystems/subsys-boot-init-sub-unit.md)
- [init Run Log Standardization](subsystems/subsys-boot-init-log.md)
- [Seccomp Policy Development](subsystems/subsys-boot-init-seccomp.md)
- [DeviceInfo Adaptation](subsystems/subsys-boot-init-deviceInfo.md)
- [appspawn Module](subsystems/subsys-boot-appspawn.md)
- [bootstrap Module](subsystems/subsys-boot-bootstrap.md)
- [FAQs](subsystems/subsys-boot-faqs.md)
......@@ -471,7 +489,9 @@
- [Thermal Policy Customization](subsystems/subsys-thermal_policy.md)
- [Thermal Scene Customization](subsystems/subsys-thermal_scene.md)
- Power Management
- [Power Mode Customization](subsystems/subsys-power-mode-customization.md)
- [Power Mode Customization](subsystems/subsys-power-mode-customization.md)
- [Default Hibernation Behavior Customization](subsystems/subsys-power-default-sleep-behavior-customization.md)
- [Wakeup Source Customization](subsystems/subsys-power-wakeup-source-customization.md)
- Featured Topics
- HPM Part
- [HPM Part Overview](hpm-part/hpm-part-about.md)
......
# Multi-language Runtime Subsystem Changelog
## cl.arkcompiler.1 New Alarms and Existing Alarm Enhancements for LLVM
**Change Impact**
By default, the **-Werror** option is disabled for the OpenHarmony NDK. If you have enabled the **-Werror** option, you are advised to correct the code based on the suggestions in the check result or mask the errors.
**Changes in Key Compilation Check Rules**
| New Check Item| Description| Suggestion|
| --- | --- | --- |
| Wunused-but-set-variable | An alarm is generated when the code contains unused variables (including the ++ operator).| Add the **maybe_unused** attribute when defining variables or use macros to distinguish variables.|
| Wdeprecated-non-prototype | An alarm is generated when a function without a prototype exists in the code.| Add a function prototype and specify the parameters.|
| Wunqualified-std-cast-call | An alarm is generated when **std::move** is incorrectly used in code.| Specify the use case of **std::move** and check the code.|
| Wdeprecated-builtins | An alarm is generated when a deprecated built-in function is used in the code.| Use the substitute function.|
| Warray-parameter | An alarm is generated when a function parameter contains an array that uses inconsistent forms.| Ensure the consistency of the function parameter.|
| Wbitwise-instead-of-logical | An alarm is generated when bitwise OR is used in Boolean operations.| Use logical OR in Boolean operations.|
| Wint-conversion | An alarm is generated when an int variable is converted to a pointer in the code.| Use a new implementation mode in the code.|
| Wdeprecated-declarations | An alarm is generated when a deprecated definition (including functions and variables) is used in code.| Use a new implementation mode in the code.|
| Wnull-pointer-subtraction | An alarm is generated when a null pointer is used in the subtraction operation.| Do not a null pointer in the subtraction operation.|
| Wunused-but-set-parameter | An alarm is generated when a function contains unused parameters.| Delete unused parameters.|
| Warray-bounds | An alarm is generated when out-of-bounds access to an array occurs in the code.| Modify the out-of-bounds access.|
| Wdeprecated-pragma | An alarm is generated when a deprecated macro is used in the code.| Delete the deprecated macro.|
| Wreserved-identifier | An alarm is generated when a variable starting with __ is used in the code.| Check the code to prevent variables starting with underscores (_) from being used externally.|
**Adaptation Guide**
1. For issues in the code that are not detected by LLVM 12, check and update the code.
2. Some old implementations are discarded during LLVM update. Adapt to the new rules and update the code.
3. If you believe a certain type of error can be masked, use the **-Wno-xxx** option to mask the error.
Defective code example
```
void Heap::Resume(TriggerGCType gcType)
{
if (mode_ != HeapMode::SPAWN &&
activeSemiSpace_->AdjustCapacity(inactiveSemiSpace_->GetAllocatedSizeSinceGC())) {
// If activeSpace capacity changes, oldSpace maximumCapacity should change too.
size_t multiple = 2;
// oldSpaceMaxLimit is assigned a value but is not used.
size_t oldSpaceMaxLimit = 0;
if (activeSemiSpace_->GetInitialCapacity() >= inactiveSemiSpace_->GetInitialCapacity()) {
size_t delta = activeSemiSpace_->GetInitialCapacity() - inactiveSemiSpace_->GetInitialCapacity();
oldSpaceMaxLimit = oldSpace_->GetMaximumCapacity() - delta * multiple;
} else {
size_t delta = inactiveSemiSpace_->GetInitialCapacity() - activeSemiSpace_->GetInitialCapacity();
oldSpaceMaxLimit = oldSpace_->GetMaximumCapacity() + delta * multiple;
}
inactiveSemiSpace_->SetInitialCapacity(activeSemiSpace_->GetInitialCapacity());
}
// irrelated code ...
}
```
The oldSpaceMaxLimit variable is not used, and the compiler reports an alarm.
```
../../arkcompiler/ets_runtime/ecmascript/mem/heap.cpp:247:16: error: variable 'oldSpaceMaxLimit' set but not used [-Werror,-Wunused-but-set-variable]
size_t oldSpaceMaxLimit = 0;
```
The error is rectified after the attribute is added.
```
void Heap::Resume(TriggerGCType gcType)
{
if (mode_ != HeapMode::SPAWN &&
activeSemiSpace_->AdjustCapacity(inactiveSemiSpace_->GetAllocatedSizeSinceGC())) {
// If activeSpace capacity changes, oldSpace maximumCapacity should change too.
size_t multiple = 2;
// Add the maybe_unused attribute and declare that the variable oldSpaceMaxLimit may not be used.
[[maybe_unused]] size_t oldSpaceMaxLimit = 0;
if (activeSemiSpace_->GetInitialCapacity() >= inactiveSemiSpace_->GetInitialCapacity()) {
size_t delta = activeSemiSpace_->GetInitialCapacity() - inactiveSemiSpace_->GetInitialCapacity();
oldSpaceMaxLimit = oldSpace_->GetMaximumCapacity() - delta * multiple;
} else {
size_t delta = inactiveSemiSpace_->GetInitialCapacity() - activeSemiSpace_->GetInitialCapacity();
oldSpaceMaxLimit = oldSpace_->GetMaximumCapacity() + delta * multiple;
}
inactiveSemiSpace_->SetInitialCapacity(activeSemiSpace_->GetInitialCapacity());
}
// irrelated code ...
}
```
## cl.arkcompiler.2 LLVM Parsing Format Changed
**Change Impact**
When your code depends on the **version-script** or **-gcc-toolchain** option, parsing may fail if you continue to use the original LLVM 12 configuration file or options.
**Changes in Key Compilation Rules**
1. The symbol representation is changed. In the new version, consecutive greater signs (>) are represented as ">>", which was represented as "> >" in the old version.
2. The -xx options are deprecated. For example, the **-gcc-toolchain** option is replaced by the **--gcc-toolchain** option. (This option has been marked as deprecated in versions later than Clang 3.4 and is officially deprecated in LLVM15.)
**Adaptation Guide**
If two consecutive greater signs (>) exist in the code (without considering the number of spaces in the middle), they will be parsed as "> >" in the older version and ">>" in the new version in version-script (due to mangling differences). In LLVM15, ">>" must be used.
Original configuration file
```
{
global:
extern "C++" {
"google::protobuf::TextFormat::ParseFromString(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, google::protobuf::Message*)";
// In LLVM 12, "> >" can be parsed, but ">>" cannot.
"google::protobuf::TextFormat::PrintToString(google::protobuf::Message const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >*)";
};
local:
*;
}
```
Modified configuration file
```
{
global:
extern "C++" {
"google::protobuf::TextFormat::ParseFromString(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&, google::protobuf::Message*)";
// In LLVM 15, ">>" can be parsed.
"google::protobuf::TextFormat::PrintToString(google::protobuf::Message const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>*)";
};
local:
*;
}
```
## cl.arkcompiler.3 LLVM emu-tls Changed
**Change Impact**
If you use both LLVM 12 and LLVM 15 (this behavior is prohibited) for your code, the emu-tls symbol cannot be found in libc++.so.
**Key Library Dependency Changes**
In LLVM 15, the emu-tls symbol is extracted from the target binary file to libc++.so. That is, the __emutls_get_address attribute changes from an internal symbol to an externally visible symbol, which is included in libc++.so. As a result, the compiled dynamic library may depend on libc++.so.
**Adaptation Guide**
This symbol is also in libclang_rt.builtin.a. If you do not want the compiled dynamic library to depend on libc++.so, statically link the libclang_rt.builtin.a library.
## cl.arkcompiler.4 LLVM Official Release Notes
**Change Impact**
New features are added and internal interfaces are changed (such as IR in LLVM and compiler front-end modification). For details, see the official release notes.
**Key Documents**
https://releases.llvm.org/13.0.0/docs/ReleaseNotes.html
https://releases.llvm.org/14.0.0/docs/ReleaseNotes.html
https://releases.llvm.org/15.0.0/docs/ReleaseNotes.html
**Adaptation Guide**
For details about the updates and adaptation guide, see the official documents.
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册