xref: /rk3399_rockchip-uboot/lib/avb/libavb_user/avb_ops_user.c (revision 386fb802ff76c5bdde139e8119446b7cf8b29027)
137a7bc39SJason Zhu /*
237a7bc39SJason Zhu  * Copyright (C) 2017 The Android Open Source Project
337a7bc39SJason Zhu  *
437a7bc39SJason Zhu  * Permission is hereby granted, free of charge, to any person
537a7bc39SJason Zhu  * obtaining a copy of this software and associated documentation
637a7bc39SJason Zhu  * files (the "Software"), to deal in the Software without
737a7bc39SJason Zhu  * restriction, including without limitation the rights to use, copy,
837a7bc39SJason Zhu  * modify, merge, publish, distribute, sublicense, and/or sell copies
937a7bc39SJason Zhu  * of the Software, and to permit persons to whom the Software is
1037a7bc39SJason Zhu  * furnished to do so, subject to the following conditions:
1137a7bc39SJason Zhu  *
1237a7bc39SJason Zhu  * The above copyright notice and this permission notice shall be
1337a7bc39SJason Zhu  * included in all copies or substantial portions of the Software.
1437a7bc39SJason Zhu  *
1537a7bc39SJason Zhu  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
1637a7bc39SJason Zhu  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
1737a7bc39SJason Zhu  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
1837a7bc39SJason Zhu  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
1937a7bc39SJason Zhu  * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
2037a7bc39SJason Zhu  * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
2137a7bc39SJason Zhu  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2237a7bc39SJason Zhu  * SOFTWARE.
2337a7bc39SJason Zhu  */
2437a7bc39SJason Zhu 
2537a7bc39SJason Zhu #include <common.h>
2637a7bc39SJason Zhu #include <image.h>
2737a7bc39SJason Zhu #include <android_image.h>
2837a7bc39SJason Zhu #include <malloc.h>
2937a7bc39SJason Zhu #include <mapmem.h>
3037a7bc39SJason Zhu #include <errno.h>
3137a7bc39SJason Zhu #include <command.h>
3237a7bc39SJason Zhu #include <mmc.h>
3337a7bc39SJason Zhu #include <blk.h>
3437a7bc39SJason Zhu #include <part.h>
3516a62313SJason Zhu #include <stdio.h>
3637a7bc39SJason Zhu #include <android_avb/avb_ops_user.h>
3737a7bc39SJason Zhu #include <android_avb/libavb_ab.h>
3837a7bc39SJason Zhu #include <android_avb/avb_atx_validate.h>
3937a7bc39SJason Zhu #include <android_avb/avb_atx_types.h>
4037a7bc39SJason Zhu #include <optee_include/OpteeClientInterface.h>
4137a7bc39SJason Zhu #include <optee_include/tee_api_defines.h>
4237a7bc39SJason Zhu #include <android_avb/avb_vbmeta_image.h>
4337a7bc39SJason Zhu #include <android_avb/avb_atx_validate.h>
44459bc933SJason Zhu #include <boot_rkimg.h>
4537a7bc39SJason Zhu 
byte_to_block(int64_t * offset,size_t * num_bytes,lbaint_t * offset_blk,lbaint_t * blkcnt)4637a7bc39SJason Zhu static void byte_to_block(int64_t *offset,
4737a7bc39SJason Zhu 			  size_t *num_bytes,
4837a7bc39SJason Zhu 			  lbaint_t *offset_blk,
4937a7bc39SJason Zhu 			  lbaint_t *blkcnt)
5037a7bc39SJason Zhu {
5137a7bc39SJason Zhu 	*offset_blk = (lbaint_t)(*offset / 512);
5237a7bc39SJason Zhu 	if (*num_bytes % 512 == 0) {
537c1937d6SJason Zhu 		if (*offset % 512 == 0)
5437a7bc39SJason Zhu 			*blkcnt = (lbaint_t)(*num_bytes / 512);
557c1937d6SJason Zhu 		else
5637a7bc39SJason Zhu 			*blkcnt = (lbaint_t)(*num_bytes / 512) + 1;
5737a7bc39SJason Zhu 	} else {
5837a7bc39SJason Zhu 		if (*offset % 512 == 0) {
5937a7bc39SJason Zhu 			*blkcnt = (lbaint_t)(*num_bytes / 512) + 1;
6037a7bc39SJason Zhu 		} else {
6137a7bc39SJason Zhu 			if ((*offset % 512) + (*num_bytes % 512) < 512 ||
6237a7bc39SJason Zhu 			    (*offset % 512) + (*num_bytes % 512) == 512) {
6337a7bc39SJason Zhu 				*blkcnt = (lbaint_t)(*num_bytes / 512) + 1;
6437a7bc39SJason Zhu 			} else {
6537a7bc39SJason Zhu 				*blkcnt = (lbaint_t)(*num_bytes / 512) + 2;
6637a7bc39SJason Zhu 			}
6737a7bc39SJason Zhu 		}
6837a7bc39SJason Zhu 	}
6937a7bc39SJason Zhu }
7037a7bc39SJason Zhu 
get_size_of_partition(AvbOps * ops,const char * partition,uint64_t * out_size_in_bytes)7116a62313SJason Zhu static AvbIOResult get_size_of_partition(AvbOps *ops,
7216a62313SJason Zhu 					 const char *partition,
7316a62313SJason Zhu 					 uint64_t *out_size_in_bytes)
7416a62313SJason Zhu {
7516a62313SJason Zhu 	struct blk_desc *dev_desc;
7616a62313SJason Zhu 	disk_partition_t part_info;
7716a62313SJason Zhu 
7816a62313SJason Zhu 	dev_desc = rockchip_get_bootdev();
7916a62313SJason Zhu 	if (!dev_desc) {
8016a62313SJason Zhu 		printf("%s: Could not find device\n", __func__);
8116a62313SJason Zhu 		return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
8216a62313SJason Zhu 	}
8316a62313SJason Zhu 
8428317110SJoseph Chen 	if (part_get_info_by_name(dev_desc, partition, &part_info) < 0)
8516a62313SJason Zhu 		return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
8628317110SJoseph Chen 
8716a62313SJason Zhu 	*out_size_in_bytes = (part_info.size) * 512;
8816a62313SJason Zhu 	return AVB_IO_RESULT_OK;
8916a62313SJason Zhu }
9016a62313SJason Zhu 
read_from_partition(AvbOps * ops,const char * partition,int64_t offset,size_t num_bytes,void * buffer,size_t * out_num_read)9137a7bc39SJason Zhu static AvbIOResult read_from_partition(AvbOps *ops,
9237a7bc39SJason Zhu 				       const char *partition,
9337a7bc39SJason Zhu 				       int64_t offset,
9437a7bc39SJason Zhu 				       size_t num_bytes,
9537a7bc39SJason Zhu 				       void *buffer,
9637a7bc39SJason Zhu 				       size_t *out_num_read)
9737a7bc39SJason Zhu {
9837a7bc39SJason Zhu 	struct blk_desc *dev_desc;
9937a7bc39SJason Zhu 	lbaint_t offset_blk, blkcnt;
10037a7bc39SJason Zhu 	disk_partition_t part_info;
10116a62313SJason Zhu 	uint64_t partition_size;
10216a62313SJason Zhu 
10316a62313SJason Zhu 	if (offset < 0) {
10416a62313SJason Zhu 		if (get_size_of_partition(ops, partition, &partition_size))
10516a62313SJason Zhu 			return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
10616a62313SJason Zhu 
10716a62313SJason Zhu 		if (-offset > partition_size)
10816a62313SJason Zhu 			return AVB_IO_RESULT_ERROR_RANGE_OUTSIDE_PARTITION;
10916a62313SJason Zhu 
11016a62313SJason Zhu 		offset = partition_size - (-offset);
11116a62313SJason Zhu 	}
11237a7bc39SJason Zhu 
11337a7bc39SJason Zhu 	byte_to_block(&offset, &num_bytes, &offset_blk, &blkcnt);
114459bc933SJason Zhu 	dev_desc = rockchip_get_bootdev();
11537a7bc39SJason Zhu 	if (!dev_desc) {
116459bc933SJason Zhu 		printf("%s: Could not find device\n", __func__);
11737a7bc39SJason Zhu 		return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
11837a7bc39SJason Zhu 	}
11937a7bc39SJason Zhu 
12037a7bc39SJason Zhu 	if (part_get_info_by_name(dev_desc, partition, &part_info) < 0) {
12137a7bc39SJason Zhu 		printf("Could not find \"%s\" partition\n", partition);
12237a7bc39SJason Zhu 		return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
12337a7bc39SJason Zhu 	}
12437a7bc39SJason Zhu 
12537a7bc39SJason Zhu 	if ((offset % 512 == 0) && (num_bytes % 512 == 0)) {
1267c1937d6SJason Zhu 		blk_dread(dev_desc, part_info.start + offset_blk,
1277c1937d6SJason Zhu 			  blkcnt, buffer);
12837a7bc39SJason Zhu 		*out_num_read = blkcnt * 512;
12937a7bc39SJason Zhu 	} else {
13037a7bc39SJason Zhu 		char *buffer_temp;
1317c1937d6SJason Zhu 
13237a7bc39SJason Zhu 		buffer_temp = malloc(512 * blkcnt);
1337c1937d6SJason Zhu 		if (!buffer_temp) {
13437a7bc39SJason Zhu 			printf("malloc error!\n");
13537a7bc39SJason Zhu 			return AVB_IO_RESULT_ERROR_OOM;
13637a7bc39SJason Zhu 		}
1377c1937d6SJason Zhu 		blk_dread(dev_desc, part_info.start + offset_blk,
1387c1937d6SJason Zhu 			  blkcnt, buffer_temp);
13937a7bc39SJason Zhu 		memcpy(buffer, buffer_temp + (offset % 512), num_bytes);
14037a7bc39SJason Zhu 		*out_num_read = num_bytes;
14137a7bc39SJason Zhu 		free(buffer_temp);
14237a7bc39SJason Zhu 	}
14337a7bc39SJason Zhu 
14437a7bc39SJason Zhu 	return AVB_IO_RESULT_OK;
14537a7bc39SJason Zhu }
14637a7bc39SJason Zhu 
write_to_partition(AvbOps * ops,const char * partition,int64_t offset,size_t num_bytes,const void * buffer)14737a7bc39SJason Zhu static AvbIOResult write_to_partition(AvbOps *ops,
14837a7bc39SJason Zhu 				      const char *partition,
14937a7bc39SJason Zhu 				      int64_t offset,
15037a7bc39SJason Zhu 				      size_t num_bytes,
15137a7bc39SJason Zhu 				      const void *buffer)
15237a7bc39SJason Zhu {
15337a7bc39SJason Zhu 	struct blk_desc *dev_desc;
15437a7bc39SJason Zhu 	char *buffer_temp;
15537a7bc39SJason Zhu 	disk_partition_t part_info;
15637a7bc39SJason Zhu 	lbaint_t offset_blk, blkcnt;
15737a7bc39SJason Zhu 
15837a7bc39SJason Zhu 	byte_to_block(&offset, &num_bytes, &offset_blk, &blkcnt);
15937a7bc39SJason Zhu 	buffer_temp = malloc(512 * blkcnt);
1607c1937d6SJason Zhu 	if (!buffer_temp) {
16137a7bc39SJason Zhu 		printf("malloc error!\n");
16237a7bc39SJason Zhu 		return AVB_IO_RESULT_ERROR_OOM;
16337a7bc39SJason Zhu 	}
16437a7bc39SJason Zhu 	memset(buffer_temp, 0, 512 * blkcnt);
165459bc933SJason Zhu 	dev_desc = rockchip_get_bootdev();
16637a7bc39SJason Zhu 	if (!dev_desc) {
167459bc933SJason Zhu 		printf("%s: Could not find device\n", __func__);
16837a7bc39SJason Zhu 		return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
16937a7bc39SJason Zhu 	}
17037a7bc39SJason Zhu 
17137a7bc39SJason Zhu 	if (part_get_info_by_name(dev_desc, partition, &part_info) < 0) {
17237a7bc39SJason Zhu 		printf("Could not find \"%s\" partition\n", partition);
17337a7bc39SJason Zhu 		return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
17437a7bc39SJason Zhu 	}
17537a7bc39SJason Zhu 
1767c1937d6SJason Zhu 	if ((offset % 512 != 0) && (num_bytes % 512) != 0)
1777c1937d6SJason Zhu 		blk_dread(dev_desc, part_info.start + offset_blk,
1787c1937d6SJason Zhu 			  blkcnt, buffer_temp);
17937a7bc39SJason Zhu 
18037a7bc39SJason Zhu 	memcpy(buffer_temp, buffer + (offset % 512), num_bytes);
18137a7bc39SJason Zhu 	blk_dwrite(dev_desc, part_info.start + offset_blk, blkcnt, buffer);
18237a7bc39SJason Zhu 	free(buffer_temp);
18337a7bc39SJason Zhu 
18437a7bc39SJason Zhu 	return AVB_IO_RESULT_OK;
18537a7bc39SJason Zhu }
18637a7bc39SJason Zhu 
1877c1937d6SJason Zhu static AvbIOResult
validate_vbmeta_public_key(AvbOps * ops,const uint8_t * public_key_data,size_t public_key_length,const uint8_t * public_key_metadata,size_t public_key_metadata_length,bool * out_is_trusted)1887c1937d6SJason Zhu validate_vbmeta_public_key(AvbOps *ops,
18937a7bc39SJason Zhu 			   const uint8_t *public_key_data,
19037a7bc39SJason Zhu 			   size_t public_key_length,
19137a7bc39SJason Zhu 			   const uint8_t *public_key_metadata,
19237a7bc39SJason Zhu 			   size_t public_key_metadata_length,
19337a7bc39SJason Zhu 			   bool *out_is_trusted)
19437a7bc39SJason Zhu {
195caed6b4fSJoseph Chen /* remain AVB_VBMETA_PUBLIC_KEY_VALIDATE to compatible legacy code */
196caed6b4fSJoseph Chen #if defined(CONFIG_AVB_VBMETA_PUBLIC_KEY_VALIDATE) || \
197caed6b4fSJoseph Chen     defined(AVB_VBMETA_PUBLIC_KEY_VALIDATE)
1987c1937d6SJason Zhu 	if (out_is_trusted) {
19937a7bc39SJason Zhu 		avb_atx_validate_vbmeta_public_key(ops,
20037a7bc39SJason Zhu 						   public_key_data,
20137a7bc39SJason Zhu 						   public_key_length,
20237a7bc39SJason Zhu 						   public_key_metadata,
20337a7bc39SJason Zhu 						   public_key_metadata_length,
20437a7bc39SJason Zhu 						   out_is_trusted);
20537a7bc39SJason Zhu 	}
20637a7bc39SJason Zhu #else
2077c1937d6SJason Zhu 	if (out_is_trusted)
20837a7bc39SJason Zhu 		*out_is_trusted = true;
20937a7bc39SJason Zhu #endif
21037a7bc39SJason Zhu 	return AVB_IO_RESULT_OK;
21137a7bc39SJason Zhu }
21237a7bc39SJason Zhu 
read_rollback_index(AvbOps * ops,size_t rollback_index_location,uint64_t * out_rollback_index)21337a7bc39SJason Zhu static AvbIOResult read_rollback_index(AvbOps *ops,
21437a7bc39SJason Zhu 				       size_t rollback_index_location,
21537a7bc39SJason Zhu 				       uint64_t *out_rollback_index)
21637a7bc39SJason Zhu {
2177c1937d6SJason Zhu 	if (out_rollback_index) {
21837a7bc39SJason Zhu #ifdef CONFIG_OPTEE_CLIENT
21937a7bc39SJason Zhu 		int ret;
2207c1937d6SJason Zhu 
22137a7bc39SJason Zhu 		ret = trusty_read_rollback_index(rollback_index_location,
22237a7bc39SJason Zhu 						 out_rollback_index);
2237c1937d6SJason Zhu 		switch (ret) {
2247c1937d6SJason Zhu 		case TEE_SUCCESS:
2257c1937d6SJason Zhu 			ret = AVB_IO_RESULT_OK;
2267c1937d6SJason Zhu 			break;
2277c1937d6SJason Zhu 		case TEE_ERROR_GENERIC:
2287c1937d6SJason Zhu 		case TEE_ERROR_NO_DATA:
2297c1937d6SJason Zhu 		case TEE_ERROR_ITEM_NOT_FOUND:
23037a7bc39SJason Zhu 			*out_rollback_index = 0;
23137a7bc39SJason Zhu 			ret = trusty_write_rollback_index(rollback_index_location,
23237a7bc39SJason Zhu 							  *out_rollback_index);
2337c1937d6SJason Zhu 			if (ret) {
2347c1937d6SJason Zhu 				printf("%s: init rollback index error\n",
2357c1937d6SJason Zhu 				       __FILE__);
2367c1937d6SJason Zhu 				ret = AVB_IO_RESULT_ERROR_IO;
23737a7bc39SJason Zhu 			} else {
2387c1937d6SJason Zhu 				ret =
2397c1937d6SJason Zhu 				trusty_read_rollback_index(rollback_index_location,
2407c1937d6SJason Zhu 							   out_rollback_index);
2417c1937d6SJason Zhu 				if (ret)
2427c1937d6SJason Zhu 					ret = AVB_IO_RESULT_ERROR_IO;
2437c1937d6SJason Zhu 				else
2447c1937d6SJason Zhu 					ret = AVB_IO_RESULT_OK;
24537a7bc39SJason Zhu 			}
2467c1937d6SJason Zhu 			break;
2477c1937d6SJason Zhu 		default:
2487c1937d6SJason Zhu 			ret = AVB_IO_RESULT_ERROR_IO;
2497c1937d6SJason Zhu 			printf("%s: trusty_read_rollback_index failed",
2507c1937d6SJason Zhu 			       __FILE__);
2517c1937d6SJason Zhu 		}
2527c1937d6SJason Zhu 
2537c1937d6SJason Zhu 		return ret;
254ae205b95SJoseph Chen #else
255ae205b95SJoseph Chen 		*out_rollback_index = 0;
256ae205b95SJoseph Chen 
257ae205b95SJoseph Chen 		return AVB_IO_RESULT_OK;
25837a7bc39SJason Zhu #endif
25937a7bc39SJason Zhu 	}
260ae205b95SJoseph Chen 
26137a7bc39SJason Zhu 	return AVB_IO_RESULT_ERROR_IO;
26237a7bc39SJason Zhu }
26337a7bc39SJason Zhu 
write_rollback_index(AvbOps * ops,size_t rollback_index_location,uint64_t rollback_index)26437a7bc39SJason Zhu static AvbIOResult write_rollback_index(AvbOps *ops,
26537a7bc39SJason Zhu 					size_t rollback_index_location,
26637a7bc39SJason Zhu 					uint64_t rollback_index)
26737a7bc39SJason Zhu {
26837a7bc39SJason Zhu #ifdef CONFIG_OPTEE_CLIENT
2697c1937d6SJason Zhu 	if (trusty_write_rollback_index(rollback_index_location,
2707c1937d6SJason Zhu 					rollback_index)) {
27137a7bc39SJason Zhu 		printf("%s: Fail to write rollback index\n", __FILE__);
27237a7bc39SJason Zhu 		return AVB_IO_RESULT_ERROR_IO;
27337a7bc39SJason Zhu 	}
27437a7bc39SJason Zhu 	return AVB_IO_RESULT_OK;
27537a7bc39SJason Zhu #endif
27637a7bc39SJason Zhu 	return AVB_IO_RESULT_ERROR_IO;
27737a7bc39SJason Zhu }
27837a7bc39SJason Zhu 
read_is_device_unlocked(AvbOps * ops,bool * out_is_unlocked)27937a7bc39SJason Zhu static AvbIOResult read_is_device_unlocked(AvbOps *ops, bool *out_is_unlocked)
28037a7bc39SJason Zhu {
2817c1937d6SJason Zhu 	if (out_is_unlocked) {
28237a7bc39SJason Zhu #ifdef CONFIG_OPTEE_CLIENT
283d9e299e0SJason Zhu 		uint8_t vboot_flag = 0;
28437a7bc39SJason Zhu 		int ret;
28537a7bc39SJason Zhu 
28637a7bc39SJason Zhu 		ret = trusty_read_lock_state((uint8_t *)out_is_unlocked);
2877c1937d6SJason Zhu 		switch (ret) {
2887c1937d6SJason Zhu 		case TEE_SUCCESS:
2897c1937d6SJason Zhu 			ret = AVB_IO_RESULT_OK;
2907c1937d6SJason Zhu 			break;
2917c1937d6SJason Zhu 		case TEE_ERROR_GENERIC:
2927c1937d6SJason Zhu 		case TEE_ERROR_NO_DATA:
2937c1937d6SJason Zhu 		case TEE_ERROR_ITEM_NOT_FOUND:
294d9e299e0SJason Zhu 			if (trusty_read_vbootkey_enable_flag(&vboot_flag)) {
295d9e299e0SJason Zhu 				printf("Can't read vboot flag\n");
296d9e299e0SJason Zhu 				return AVB_IO_RESULT_ERROR_IO;
297d9e299e0SJason Zhu 			}
298d9e299e0SJason Zhu 
299d9e299e0SJason Zhu 			if (vboot_flag)
300d9e299e0SJason Zhu 				*out_is_unlocked = 0;
301d9e299e0SJason Zhu 			else
30237a7bc39SJason Zhu 				*out_is_unlocked = 1;
303d9e299e0SJason Zhu 
30437a7bc39SJason Zhu 			if (trusty_write_lock_state(*out_is_unlocked)) {
30537a7bc39SJason Zhu 				printf("%s: init lock state error\n", __FILE__);
3067c1937d6SJason Zhu 				ret = AVB_IO_RESULT_ERROR_IO;
30737a7bc39SJason Zhu 			} else {
3087c1937d6SJason Zhu 				ret =
3097c1937d6SJason Zhu 				trusty_read_lock_state((uint8_t *)out_is_unlocked);
3107c1937d6SJason Zhu 				if (ret == 0)
3117c1937d6SJason Zhu 					ret = AVB_IO_RESULT_OK;
3127c1937d6SJason Zhu 				else
3137c1937d6SJason Zhu 					ret = AVB_IO_RESULT_ERROR_IO;
31437a7bc39SJason Zhu 			}
3157c1937d6SJason Zhu 			break;
3167c1937d6SJason Zhu 		default:
3177c1937d6SJason Zhu 			ret = AVB_IO_RESULT_ERROR_IO;
3187c1937d6SJason Zhu 			printf("%s: trusty_read_lock_state failed\n", __FILE__);
3197c1937d6SJason Zhu 		}
3207c1937d6SJason Zhu 		return ret;
321ae205b95SJoseph Chen #else
322ae205b95SJoseph Chen 		*out_is_unlocked = 1;
323ae205b95SJoseph Chen 
324ae205b95SJoseph Chen 		return AVB_IO_RESULT_OK;
32537a7bc39SJason Zhu #endif
32637a7bc39SJason Zhu 	}
32737a7bc39SJason Zhu 	return AVB_IO_RESULT_ERROR_IO;
32837a7bc39SJason Zhu }
32937a7bc39SJason Zhu 
write_is_device_unlocked(AvbOps * ops,bool * out_is_unlocked)33037a7bc39SJason Zhu static AvbIOResult write_is_device_unlocked(AvbOps *ops, bool *out_is_unlocked)
33137a7bc39SJason Zhu {
3327c1937d6SJason Zhu 	if (out_is_unlocked) {
33337a7bc39SJason Zhu #ifdef CONFIG_OPTEE_CLIENT
33437a7bc39SJason Zhu 		if (trusty_write_lock_state(*out_is_unlocked)) {
33537a7bc39SJason Zhu 			printf("%s: Fail to write lock state\n", __FILE__);
33637a7bc39SJason Zhu 			return AVB_IO_RESULT_ERROR_IO;
33737a7bc39SJason Zhu 		}
33837a7bc39SJason Zhu 		return AVB_IO_RESULT_OK;
33937a7bc39SJason Zhu #endif
34037a7bc39SJason Zhu 	}
34137a7bc39SJason Zhu 	return AVB_IO_RESULT_ERROR_IO;
34237a7bc39SJason Zhu }
34337a7bc39SJason Zhu 
get_unique_guid_for_partition(AvbOps * ops,const char * partition,char * guid_buf,size_t guid_buf_size)34437a7bc39SJason Zhu static AvbIOResult get_unique_guid_for_partition(AvbOps *ops,
34537a7bc39SJason Zhu 						 const char *partition,
34637a7bc39SJason Zhu 						 char *guid_buf,
34737a7bc39SJason Zhu 						 size_t guid_buf_size)
34837a7bc39SJason Zhu {
349*386fb802SXuhui Lin #if CONFIG_IS_ENABLED(PARTITION_UUIDS)
35037a7bc39SJason Zhu 	struct blk_desc *dev_desc;
35137a7bc39SJason Zhu 	disk_partition_t part_info;
3527c1937d6SJason Zhu 
353459bc933SJason Zhu 	dev_desc = rockchip_get_bootdev();
35437a7bc39SJason Zhu 	if (!dev_desc) {
355459bc933SJason Zhu 		printf("%s: Could not find device\n", __func__);
35637a7bc39SJason Zhu 		return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
35737a7bc39SJason Zhu 	}
35837a7bc39SJason Zhu 
35937a7bc39SJason Zhu 	if (part_get_info_by_name(dev_desc, partition, &part_info) < 0) {
36037a7bc39SJason Zhu 		printf("Could not find \"%s\" partition\n", partition);
36137a7bc39SJason Zhu 		return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
36237a7bc39SJason Zhu 	}
363*386fb802SXuhui Lin 
3647c1937d6SJason Zhu 	if (guid_buf && guid_buf_size > 0)
36537a7bc39SJason Zhu 		memcpy(guid_buf, part_info.uuid, guid_buf_size);
3667c1937d6SJason Zhu 
36737a7bc39SJason Zhu 	return AVB_IO_RESULT_OK;
368*386fb802SXuhui Lin #else
369*386fb802SXuhui Lin 	printf("WARN: Get partition uuid requires CONFIG_PARTITION_UUIDS enabled\n");
370*386fb802SXuhui Lin 	return AVB_IO_RESULT_ERROR_NO_SUCH_VALUE;
371*386fb802SXuhui Lin #endif
37237a7bc39SJason Zhu }
37337a7bc39SJason Zhu 
37437a7bc39SJason Zhu /* read permanent attributes from rpmb */
avb_read_perm_attr(AvbAtxOps * atx_ops,AvbAtxPermanentAttributes * attributes)37537a7bc39SJason Zhu AvbIOResult avb_read_perm_attr(AvbAtxOps *atx_ops,
37637a7bc39SJason Zhu 			       AvbAtxPermanentAttributes *attributes)
37737a7bc39SJason Zhu {
3787c1937d6SJason Zhu 	if (attributes) {
37937a7bc39SJason Zhu #ifdef CONFIG_OPTEE_CLIENT
38037a7bc39SJason Zhu 		trusty_read_permanent_attributes((uint8_t *)attributes,
38137a7bc39SJason Zhu 						 sizeof(struct AvbAtxPermanentAttributes));
38237a7bc39SJason Zhu 		return AVB_IO_RESULT_OK;
38337a7bc39SJason Zhu #endif
38437a7bc39SJason Zhu 	}
38537a7bc39SJason Zhu 
38637a7bc39SJason Zhu 	return -1;
38737a7bc39SJason Zhu }
38837a7bc39SJason Zhu 
38937a7bc39SJason Zhu /*read permanent attributes hash from efuse */
avb_read_perm_attr_hash(AvbAtxOps * atx_ops,uint8_t hash[AVB_SHA256_DIGEST_SIZE])39037a7bc39SJason Zhu AvbIOResult avb_read_perm_attr_hash(AvbAtxOps *atx_ops,
39137a7bc39SJason Zhu 				    uint8_t hash[AVB_SHA256_DIGEST_SIZE])
39237a7bc39SJason Zhu {
39374b485fbSJason Zhu #ifndef CONFIG_ROCKCHIP_PRELOADER_PUB_KEY
39437a7bc39SJason Zhu #ifdef CONFIG_OPTEE_CLIENT
3957c1937d6SJason Zhu 	if (trusty_read_attribute_hash((uint32_t *)hash,
3967c1937d6SJason Zhu 				       AVB_SHA256_DIGEST_SIZE / 4))
39737a7bc39SJason Zhu 		return -1;
39837a7bc39SJason Zhu #else
399647502d6SJason Zhu 	printf("Please open the macro!\n");
40037a7bc39SJason Zhu 	return -1;
40137a7bc39SJason Zhu #endif
40265f0143bSJason Zhu #endif
40337a7bc39SJason Zhu 	return AVB_IO_RESULT_OK;
40437a7bc39SJason Zhu }
40537a7bc39SJason Zhu 
avb_set_key_version(AvbAtxOps * atx_ops,size_t rollback_index_location,uint64_t key_version)40682e713e1SJason Zhu static void avb_set_key_version(AvbAtxOps *atx_ops,
40782e713e1SJason Zhu 				size_t rollback_index_location,
40882e713e1SJason Zhu 				uint64_t key_version)
40982e713e1SJason Zhu {
41082e713e1SJason Zhu #ifdef CONFIG_OPTEE_CLIENT
411ee9d3433SJason Zhu 	uint64_t key_version_temp = 0;
412ee9d3433SJason Zhu 
413ee9d3433SJason Zhu 	if (trusty_read_rollback_index(rollback_index_location, &key_version_temp))
414ee9d3433SJason Zhu 		printf("%s: Fail to read rollback index\n", __FILE__);
415ee9d3433SJason Zhu 	if (key_version_temp == key_version)
416ee9d3433SJason Zhu 		return;
4177c1937d6SJason Zhu 	if (trusty_write_rollback_index(rollback_index_location, key_version))
41882e713e1SJason Zhu 		printf("%s: Fail to write rollback index\n", __FILE__);
41982e713e1SJason Zhu #endif
42082e713e1SJason Zhu }
42182e713e1SJason Zhu 
rk_get_random(AvbAtxOps * atx_ops,size_t num_bytes,uint8_t * output)4228d0db1d9SJason Zhu AvbIOResult rk_get_random(AvbAtxOps *atx_ops,
4238d0db1d9SJason Zhu 			  size_t num_bytes,
4248d0db1d9SJason Zhu 			  uint8_t *output)
4258d0db1d9SJason Zhu {
4268d0db1d9SJason Zhu 	int i;
4278d0db1d9SJason Zhu 	unsigned int seed;
4288d0db1d9SJason Zhu 
4298d0db1d9SJason Zhu 	seed = (unsigned int)get_timer(0);
4308d0db1d9SJason Zhu 	for (i = 0; i < num_bytes; i++) {
4318d0db1d9SJason Zhu 		srand(seed);
4328d0db1d9SJason Zhu 		output[i] = (uint8_t)(rand());
4338d0db1d9SJason Zhu 		seed = (unsigned int)(output[i]);
4348d0db1d9SJason Zhu 	}
4358d0db1d9SJason Zhu 
4368d0db1d9SJason Zhu 	return 0;
4378d0db1d9SJason Zhu }
4388d0db1d9SJason Zhu 
43927e62cd7SJoseph Chen #ifdef CONFIG_ANDROID_BOOT_IMAGE
get_preloaded_partition(AvbOps * ops,const char * partition,size_t num_bytes,uint8_t ** out_pointer,size_t * out_num_bytes_preloaded,int allow_verification_error)44027e62cd7SJoseph Chen static AvbIOResult get_preloaded_partition(AvbOps* ops,
44127e62cd7SJoseph Chen 					   const char* partition,
44227e62cd7SJoseph Chen 					   size_t num_bytes,
44327e62cd7SJoseph Chen 					   uint8_t** out_pointer,
44427e62cd7SJoseph Chen 					   size_t* out_num_bytes_preloaded,
44527e62cd7SJoseph Chen 					   int allow_verification_error)
44627e62cd7SJoseph Chen {
44728317110SJoseph Chen 	struct preloaded_partition *preload_info = NULL;
44828317110SJoseph Chen 	struct AvbOpsData *data = ops->user_data;
44927e62cd7SJoseph Chen 	struct blk_desc *dev_desc;
45028317110SJoseph Chen 	disk_partition_t part_info;
45127e62cd7SJoseph Chen 	ulong load_addr;
45228317110SJoseph Chen 	AvbIOResult ret;
45336c449feSJoseph Chen 	int full_preload = 0;
45427e62cd7SJoseph Chen 
45527e62cd7SJoseph Chen 	dev_desc = rockchip_get_bootdev();
45627e62cd7SJoseph Chen 	if (!dev_desc)
45728317110SJoseph Chen 		return AVB_IO_RESULT_ERROR_IO;
45827e62cd7SJoseph Chen 
45928317110SJoseph Chen 	if (part_get_info_by_name(dev_desc, partition, &part_info) < 0) {
46028317110SJoseph Chen 		printf("Could not find \"%s\" partition\n", partition);
46128317110SJoseph Chen 		return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
46228317110SJoseph Chen 	}
46328317110SJoseph Chen 
464be9d6521SXuhui Lin 	/* Record partition name(either boot or recovery) */
465be9d6521SXuhui Lin 	if (!strncmp(partition, ANDROID_PARTITION_BOOT, 4) ||
466be9d6521SXuhui Lin 	    !strncmp(partition, ANDROID_PARTITION_RECOVERY, 8)) {
467be9d6521SXuhui Lin 		data->boot_partition = strdup(partition);
468be9d6521SXuhui Lin #ifdef CONFIG_ANDROID_AB
469be9d6521SXuhui Lin 		*((char *)data->boot_partition + strlen(partition) - 2) = '\0';
470be9d6521SXuhui Lin #endif
471be9d6521SXuhui Lin 	}
472be9d6521SXuhui Lin 
47328317110SJoseph Chen 	if (!allow_verification_error) {
47428317110SJoseph Chen 		if (!strncmp(partition, ANDROID_PARTITION_BOOT, 4) ||
47528317110SJoseph Chen 		    !strncmp(partition, ANDROID_PARTITION_RECOVERY, 8))
47628317110SJoseph Chen 			preload_info = &data->boot;
47728317110SJoseph Chen 		else if (!strncmp(partition, ANDROID_PARTITION_VENDOR_BOOT, 11))
47828317110SJoseph Chen 			preload_info = &data->vendor_boot;
47928317110SJoseph Chen 		else if (!strncmp(partition, ANDROID_PARTITION_INIT_BOOT, 9))
48028317110SJoseph Chen 			preload_info = &data->init_boot;
48136e836f2SJoseph Chen 		else if (!strncmp(partition, ANDROID_PARTITION_RESOURCE, 8))
48236e836f2SJoseph Chen 			preload_info = &data->resource;
48328317110SJoseph Chen 
48428317110SJoseph Chen 		if (!preload_info) {
48528317110SJoseph Chen 			printf("Error: unknown full load partition '%s'\n", partition);
48628317110SJoseph Chen 			return AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION;
48728317110SJoseph Chen 		}
48828317110SJoseph Chen 
48936c449feSJoseph Chen 		printf("preloaded(s): %sfull image from '%s' at 0x%08lx - 0x%08lx\n",
49036e836f2SJoseph Chen 		       preload_info->size ? "pre-" : "", partition,
49136e836f2SJoseph Chen 		       (ulong)preload_info->addr,
49228317110SJoseph Chen 		       (ulong)preload_info->addr + num_bytes);
49328317110SJoseph Chen 
49428317110SJoseph Chen 		/* If the partition hasn't yet been preloaded, do it now.*/
49528317110SJoseph Chen 		if (preload_info->size == 0) {
49628317110SJoseph Chen 			ret = ops->read_from_partition(ops, partition,
49728317110SJoseph Chen 						       0, num_bytes,
49828317110SJoseph Chen 						       preload_info->addr,
49928317110SJoseph Chen 						       &preload_info->size);
50028317110SJoseph Chen 			if (ret != AVB_IO_RESULT_OK)
50128317110SJoseph Chen 				return ret;
50228317110SJoseph Chen 		}
50328317110SJoseph Chen 		*out_pointer = preload_info->addr;
50428317110SJoseph Chen 		*out_num_bytes_preloaded = preload_info->size;
50528317110SJoseph Chen 		ret = AVB_IO_RESULT_OK;
50628317110SJoseph Chen 	} else {
50728317110SJoseph Chen 		if (!strncmp(partition, ANDROID_PARTITION_INIT_BOOT, 9) ||
50828317110SJoseph Chen 		    !strncmp(partition, ANDROID_PARTITION_VENDOR_BOOT, 11) ||
50928317110SJoseph Chen 		    !strncmp(partition, ANDROID_PARTITION_BOOT, 4) ||
51036e836f2SJoseph Chen 		    !strncmp(partition, ANDROID_PARTITION_RECOVERY, 8) ||
51136e836f2SJoseph Chen 		    !strncmp(partition, ANDROID_PARTITION_RESOURCE, 8)) {
51236c449feSJoseph Chen 			/* If already full preloaded, just use it */
51336c449feSJoseph Chen 			if (!strncmp(partition, ANDROID_PARTITION_BOOT, 4) ||
51436c449feSJoseph Chen 			    !strncmp(partition, ANDROID_PARTITION_RECOVERY, 8)) {
51536c449feSJoseph Chen 				preload_info = &data->boot;
51636c449feSJoseph Chen 				if (preload_info->size) {
51736c449feSJoseph Chen 					*out_pointer = preload_info->addr;
51836c449feSJoseph Chen 					*out_num_bytes_preloaded = num_bytes;
51936c449feSJoseph Chen 					full_preload = 1;
52036c449feSJoseph Chen 				}
52136c449feSJoseph Chen 			}
52236c449feSJoseph Chen 			printf("preloaded: %s image from '%s\n",
52336c449feSJoseph Chen 			       full_preload ? "pre-full" : "distribute", partition);
52428317110SJoseph Chen 		} else {
52528317110SJoseph Chen 			printf("Error: unknown preloaded partition '%s'\n", partition);
52628317110SJoseph Chen 			return AVB_IO_RESULT_ERROR_OOM;
52728317110SJoseph Chen 		}
52828317110SJoseph Chen 
52928317110SJoseph Chen 		/*
53028317110SJoseph Chen 		 * Already preloaded during boot/recovery loading,
53128317110SJoseph Chen 		 * here we just return a dummy buffer.
53228317110SJoseph Chen 		 */
53328317110SJoseph Chen 		if (!strncmp(partition, ANDROID_PARTITION_INIT_BOOT, 9) ||
53436e836f2SJoseph Chen 		    !strncmp(partition, ANDROID_PARTITION_VENDOR_BOOT, 11) ||
53536e836f2SJoseph Chen 		    !strncmp(partition, ANDROID_PARTITION_RESOURCE, 8)) {
53628317110SJoseph Chen 			*out_pointer = (u8 *)avb_malloc(ARCH_DMA_MINALIGN);
53728317110SJoseph Chen 			*out_num_bytes_preloaded = num_bytes; /* return what it expects */
53828317110SJoseph Chen 			return AVB_IO_RESULT_OK;
53928317110SJoseph Chen 		}
54028317110SJoseph Chen 
54136c449feSJoseph Chen 		/* If already full preloaded, there is nothing to do and just return */
54236c449feSJoseph Chen 		if (full_preload)
54336c449feSJoseph Chen 			return AVB_IO_RESULT_OK;
54436c449feSJoseph Chen 
54528317110SJoseph Chen 		/*
54628317110SJoseph Chen 		 * only boot/recovery partition can reach here
54728317110SJoseph Chen 		 * and init/vendor_boot are loaded at this round.
54828317110SJoseph Chen 		 */
54927e62cd7SJoseph Chen 		load_addr = env_get_ulong("kernel_addr_r", 16, 0);
55027e62cd7SJoseph Chen 		if (!load_addr)
5511e9494b5SJoseph Chen 			return AVB_IO_RESULT_ERROR_NO_SUCH_VALUE;
55227e62cd7SJoseph Chen 
55327e62cd7SJoseph Chen 		ret = android_image_load_by_partname(dev_desc, partition, &load_addr);
55427e62cd7SJoseph Chen 		if (!ret) {
55527e62cd7SJoseph Chen 			*out_pointer = (u8 *)load_addr;
55627e62cd7SJoseph Chen 			*out_num_bytes_preloaded = num_bytes; /* return what it expects */
5571e9494b5SJoseph Chen 			ret = AVB_IO_RESULT_OK;
55827e62cd7SJoseph Chen 		} else {
5591e9494b5SJoseph Chen 			ret = AVB_IO_RESULT_ERROR_IO;
56027e62cd7SJoseph Chen 		}
56128317110SJoseph Chen 	}
56227e62cd7SJoseph Chen 
56327e62cd7SJoseph Chen 	return ret;
56427e62cd7SJoseph Chen }
56527e62cd7SJoseph Chen #endif
56627e62cd7SJoseph Chen 
validate_public_key_for_partition(AvbOps * ops,const char * partition,const uint8_t * public_key_data,size_t public_key_length,const uint8_t * public_key_metadata,size_t public_key_metadata_length,bool * out_is_trusted,uint32_t * out_rollback_index_location)56716a62313SJason Zhu AvbIOResult validate_public_key_for_partition(AvbOps *ops,
56816a62313SJason Zhu 					      const char *partition,
56916a62313SJason Zhu 					      const uint8_t *public_key_data,
57016a62313SJason Zhu 					      size_t public_key_length,
57116a62313SJason Zhu 					      const uint8_t *public_key_metadata,
57216a62313SJason Zhu 					      size_t public_key_metadata_length,
57316a62313SJason Zhu 					      bool *out_is_trusted,
57416a62313SJason Zhu 					      uint32_t *out_rollback_index_location)
57516a62313SJason Zhu {
57616a62313SJason Zhu /* remain AVB_VBMETA_PUBLIC_KEY_VALIDATE to compatible legacy code */
57716a62313SJason Zhu #if defined(CONFIG_AVB_VBMETA_PUBLIC_KEY_VALIDATE) || \
57816a62313SJason Zhu     defined(AVB_VBMETA_PUBLIC_KEY_VALIDATE)
57916a62313SJason Zhu 	if (out_is_trusted) {
58016a62313SJason Zhu 		avb_atx_validate_vbmeta_public_key(ops,
58116a62313SJason Zhu 						   public_key_data,
58216a62313SJason Zhu 						   public_key_length,
58316a62313SJason Zhu 						   public_key_metadata,
58416a62313SJason Zhu 						   public_key_metadata_length,
58516a62313SJason Zhu 						   out_is_trusted);
58616a62313SJason Zhu 	}
58716a62313SJason Zhu #else
58816a62313SJason Zhu 	if (out_is_trusted)
58916a62313SJason Zhu 		*out_is_trusted = true;
59016a62313SJason Zhu #endif
59116a62313SJason Zhu 	*out_rollback_index_location = 0;
59216a62313SJason Zhu 	return AVB_IO_RESULT_OK;
59316a62313SJason Zhu }
59416a62313SJason Zhu 
avb_ops_user_new(void)5957c1937d6SJason Zhu AvbOps *avb_ops_user_new(void)
5967c1937d6SJason Zhu {
59748cf108aSJoseph Chen 	AvbOps *ops = NULL;
59848cf108aSJoseph Chen 	struct AvbOpsData *ops_data = NULL;
59937a7bc39SJason Zhu 
60037a7bc39SJason Zhu 	ops = calloc(1, sizeof(AvbOps));
6017c1937d6SJason Zhu 	if (!ops) {
60208f7f19aSJason Zhu 		printf("Error allocating memory for AvbOps.\n");
60337a7bc39SJason Zhu 		goto out;
60437a7bc39SJason Zhu 	}
60537a7bc39SJason Zhu 	ops->ab_ops = calloc(1, sizeof(AvbABOps));
6067c1937d6SJason Zhu 	if (!ops->ab_ops) {
60708f7f19aSJason Zhu 		printf("Error allocating memory for AvbABOps.\n");
60837a7bc39SJason Zhu 		free(ops);
60937a7bc39SJason Zhu 		goto out;
61037a7bc39SJason Zhu 	}
61137a7bc39SJason Zhu 
61237a7bc39SJason Zhu 	ops->atx_ops = calloc(1, sizeof(AvbAtxOps));
6137c1937d6SJason Zhu 	if (!ops->atx_ops) {
61408f7f19aSJason Zhu 		printf("Error allocating memory for AvbAtxOps.\n");
61537a7bc39SJason Zhu 		free(ops->ab_ops);
61637a7bc39SJason Zhu 		free(ops);
61737a7bc39SJason Zhu 		goto out;
61837a7bc39SJason Zhu 	}
61948cf108aSJoseph Chen 
62048cf108aSJoseph Chen 	ops_data = calloc(1, sizeof(struct AvbOpsData));
62148cf108aSJoseph Chen 	if (!ops_data) {
62248cf108aSJoseph Chen 		printf("Error allocating memory for AvbOpsData.\n");
62348cf108aSJoseph Chen 		free(ops->atx_ops);
62448cf108aSJoseph Chen 		free(ops->ab_ops);
62548cf108aSJoseph Chen 		free(ops);
62648cf108aSJoseph Chen 		goto out;
62748cf108aSJoseph Chen 	}
62848cf108aSJoseph Chen 
62937a7bc39SJason Zhu 	ops->ab_ops->ops = ops;
63037a7bc39SJason Zhu 	ops->atx_ops->ops = ops;
63148cf108aSJoseph Chen 	ops_data->ops = ops;
63248cf108aSJoseph Chen 	ops->user_data = ops_data;
63337a7bc39SJason Zhu 
63437a7bc39SJason Zhu 	ops->read_from_partition = read_from_partition;
63537a7bc39SJason Zhu 	ops->write_to_partition = write_to_partition;
63637a7bc39SJason Zhu 	ops->validate_vbmeta_public_key = validate_vbmeta_public_key;
63737a7bc39SJason Zhu 	ops->read_rollback_index = read_rollback_index;
63837a7bc39SJason Zhu 	ops->write_rollback_index = write_rollback_index;
63937a7bc39SJason Zhu 	ops->read_is_device_unlocked = read_is_device_unlocked;
64037a7bc39SJason Zhu 	ops->write_is_device_unlocked = write_is_device_unlocked;
64137a7bc39SJason Zhu 	ops->get_unique_guid_for_partition = get_unique_guid_for_partition;
64237a7bc39SJason Zhu 	ops->get_size_of_partition = get_size_of_partition;
64327e62cd7SJoseph Chen #ifdef CONFIG_ANDROID_BOOT_IMAGE
64427e62cd7SJoseph Chen 	ops->get_preloaded_partition = get_preloaded_partition;
64527e62cd7SJoseph Chen #endif
64616a62313SJason Zhu 	ops->validate_public_key_for_partition = validate_public_key_for_partition;
64737a7bc39SJason Zhu 	ops->ab_ops->read_ab_metadata = avb_ab_data_read;
64837a7bc39SJason Zhu 	ops->ab_ops->write_ab_metadata = avb_ab_data_write;
64937a7bc39SJason Zhu 	ops->atx_ops->read_permanent_attributes = avb_read_perm_attr;
65037a7bc39SJason Zhu 	ops->atx_ops->read_permanent_attributes_hash = avb_read_perm_attr_hash;
65182e713e1SJason Zhu 	ops->atx_ops->set_key_version = avb_set_key_version;
6528d0db1d9SJason Zhu 	ops->atx_ops->get_random = rk_get_random;
65337a7bc39SJason Zhu 
65437a7bc39SJason Zhu 	return ops;
65548cf108aSJoseph Chen out:
65648cf108aSJoseph Chen 	return NULL;
65737a7bc39SJason Zhu }
65837a7bc39SJason Zhu 
avb_ops_user_free(AvbOps * ops)6597c1937d6SJason Zhu void avb_ops_user_free(AvbOps *ops)
6607c1937d6SJason Zhu {
66148cf108aSJoseph Chen 	free(ops->user_data);
66237a7bc39SJason Zhu 	free(ops->ab_ops);
66337a7bc39SJason Zhu 	free(ops->atx_ops);
66437a7bc39SJason Zhu 	free(ops);
66537a7bc39SJason Zhu }
666