adreno系列(二)dts配置与gpu列表

以Find x3 pro设备树文件为例分析:https://github.com/oppo-source/android_kernel_modules_and_devicetree_oppo_sm8350/blob/oppo/sm8350_s_12.1_find_x3_pro/vendor/qcom/proprietary/devicetree/qcom/sm8150-gpu.dtsi

1. sm8150-gpu.dtsi

#define MHZ_TO_KBPS(mhz, w) ((mhz * 1000000 * w) / (1024))

&soc {
	pil_gpu: qcom,kgsl-hyp {
		compatible = "qcom,pil-tz-generic";
		qcom,pas-id = <13>;
		qcom,firmware-name = "a640_zap";
		memory-region = <&pil_gpu_mem>;
	};

    // kgsl-3d0配置
	msm_gpu: qcom,kgsl-3d0@2c00000 {
		label = "kgsl-3d0";
		compatible = "qcom,kgsl-3d0", "qcom,kgsl-3d";
		status = "ok";
		reg = <0x2c00000 0x40000>, <0x2c61000 0x800>,
				<0x6900000 0x44000>, <0x780000 0x6fff>;
		reg-names = "kgsl_3d0_reg_memory", "cx_dbgc",
					"qdss_gfx", "qfprom_memory";
		interrupts = <0 300 IRQ_TYPE_LEVEL_HIGH>;
        // 中断名称
		interrupt-names = "kgsl_3d0_irq";
		qcom,id = <0>;

        // 主版本号:6
        // 副版本号:4
        // patchid: 0
		qcom,chipid = <0x06040000>;

        // 默认频率
		qcom,initial-pwrlevel = <5>;

		qcom,gpu-quirk-secvid-set-once;
		qcom,gpu-quirk-cx-gdsc;

		qcom,idle-timeout = <80>; //msecs
		qcom,no-nap;

		qcom,highest-bank-bit = <15>;

		qcom,min-access-length = <32>;

		qcom,ubwc-mode = <3>;

		qcom,gpu-qdss-stm = <0x161c0000 0x40000>; // base addr, size

		#cooling-cells = <2>;

        // 时钟
		clocks = <&gcc GCC_GPU_CFG_AHB_CLK>,
			<&gpucc GPU_CC_CXO_CLK>,
			<&gcc GCC_DDRSS_GPU_AXI_CLK>,
			<&gcc GCC_GPU_MEMNOC_GFX_CLK>,
			<&gpucc GPU_CC_CX_GMU_CLK>,
			<&gpucc GPU_CC_AHB_CLK>;

		clock-names = "gcc_gpu_ahb", "rbbmtimer_clk",
					"gcc_gpu_axi_clk", "gcc_gpu_memnoc_gfx",
					"gmu_clk", "gpu_cc_ahb";

		qcom,isense-clk-on-level = <1>;

		interconnects = <&gem_noc MASTER_GFX3D &mc_virt SLAVE_EBI1>;
		interconnect-names = "gpu_icc_path";

		qcom,bus-table-ddr =
			<MHZ_TO_KBPS(0, 4)>,    /* index=0  */
			<MHZ_TO_KBPS(100, 4)>,  /* index=1  */
			<MHZ_TO_KBPS(150, 4)>,  /* index=2  */
			<MHZ_TO_KBPS(200, 4)>,  /* index=3  */
			<MHZ_TO_KBPS(300, 4)>,  /* index=4  */
			<MHZ_TO_KBPS(412, 4)>,  /* index=5  */
			<MHZ_TO_KBPS(547, 4)>, /* index=6  */
			<MHZ_TO_KBPS(681, 4)>, /* index=7  */
			<MHZ_TO_KBPS(768, 4)>, /* index=8  */
			<MHZ_TO_KBPS(1017, 4)>, /* index=9  */
			<MHZ_TO_KBPS(1296, 4)>, /* index=10  */
			<MHZ_TO_KBPS(1555, 4)>, /* index=11  */
			<MHZ_TO_KBPS(1804, 4)>; /* index=12  */

		qcom,bus-table-cnoc =
			<0>,   /* Off */
			<100>; /* On */


		/* GDSC regulator names */
		regulator-names = "vddcx", "vdd";
		/* GDSC oxili regulators */
		vddcx-supply = <&gpu_cx_gdsc>;
		vdd-supply = <&gpu_gx_gdsc>;

		qcom,l3-pwrlevels {
			#address-cells = <1>;
			#size-cells = <0>;

			compatible = "qcom,l3-pwrlevels";

			qcom,l3-pwrlevel@0 {
				reg = <0>;
				qcom,l3-freq = <0>;
			};

			qcom,l3-pwrlevel@1 {
				reg = <1>;
				qcom,l3-freq = <864000000>;
			};

			qcom,l3-pwrlevel@2 {
				reg = <2>;
				qcom,l3-freq = <1344000000>;
			};
		};

		/* GPU Mempools */
        // kgsl内存池配置
		qcom,gpu-mempools {
			#address-cells = <1>;
			#size-cells = <0>;
			compatible = "qcom,gpu-mempools";

			/* 4K Page Pool configuration */
			qcom,gpu-mempool@0 {
				reg = <0>;
				qcom,mempool-page-size = <4096>;
				qcom,mempool-reserved = <2048>;
				qcom,mempool-allocate;
			};
			/* 8K Page Pool configuration */
			qcom,gpu-mempool@1 {
				reg = <1>;
				qcom,mempool-page-size = <8192>;
				qcom,mempool-reserved = <1024>;
				qcom,mempool-allocate;
			};
			/* 64K Page Pool configuration */
			qcom,gpu-mempool@2 {
				reg = <2>;
				qcom,mempool-page-size = <65536>;
				qcom,mempool-reserved = <256>;
			};
			/* 1M Page Pool configuration */
			qcom,gpu-mempool@3 {
				reg = <3>;
				qcom,mempool-page-size = <1048576>;
				qcom,mempool-reserved = <32>;
			};
		};

		/* Power levels */
        // kgsl频率配置
		qcom,gpu-pwrlevels {
			#address-cells = <1>;
			#size-cells = <0>;

			compatible = "qcom,gpu-pwrlevels";

			qcom,gpu-pwrlevel@0 {
				reg = <0>;
				qcom,gpu-freq = <600000000>;
				qcom,level = <RPMH_REGULATOR_LEVEL_TURBO>;

				qcom,bus-freq = <12>;
				qcom,bus-min = <10>;
				qcom,bus-max = <12>;
			};

			qcom,gpu-pwrlevel@1 {
				reg = <1>;
				qcom,gpu-freq = <553850000>;
				qcom,level = <RPMH_REGULATOR_LEVEL_NOM_L1>;

				qcom,bus-freq = <10>;
				qcom,bus-min = <9>;
				qcom,bus-max = <11>;
			};

			qcom,gpu-pwrlevel@2 {
				reg = <2>;
				qcom,gpu-freq = <486460000>;
				qcom,level = <RPMH_REGULATOR_LEVEL_NOM>;

				qcom,bus-freq = <9>;
				qcom,bus-min = <8>;
				qcom,bus-max = <10>;
			};

			qcom,gpu-pwrlevel@3 {
				reg = <3>;
				qcom,gpu-freq = <379650000>;
				qcom,level = <RPMH_REGULATOR_LEVEL_SVS_L1>;

				qcom,bus-freq = <8>;
				qcom,bus-min = <7>;
				qcom,bus-max = <9>;
			};

			qcom,gpu-pwrlevel@4 {
				reg = <4>;
				qcom,gpu-freq = <309110000>;
				qcom,level = <RPMH_REGULATOR_LEVEL_SVS>;

				qcom,bus-freq = <5>;
				qcom,bus-min = <5>;
				qcom,bus-max = <7>;
			};

			qcom,gpu-pwrlevel@5 {
				reg = <5>;
				qcom,gpu-freq = <215000000>;
				qcom,level = <RPMH_REGULATOR_LEVEL_LOW_SVS>;

				qcom,bus-freq = <4>;
				qcom,bus-min = <3>;
				qcom,bus-max = <5>;
			};

			qcom,gpu-pwrlevel@6 {
				reg = <6>;
				qcom,gpu-freq = <0>;
				qcom,level = <RPMH_REGULATOR_LEVEL_RETENTION>;

				qcom,bus-freq = <0>;
				qcom,bus-min = <0>;
				qcom,bus-max = <0>;
			};
		};

	};

    // iommu配置
	kgsl_msm_iommu: qcom,kgsl-iommu@2ca0000 {
		compatible = "qcom,kgsl-smmu-v2";

		reg = <0x2ca0000 0x10000>;

		gfx3d_user: gfx3d_user {
			compatible = "qcom,smmu-kgsl-cb";
			label = "gfx3d_user";
			iommus = <&kgsl_smmu 0x0 0x401>;
			qcom,iommu-dma = "disabled";
		};

		gfx3d_secure: gfx3d_secure {
			compatible = "qcom,smmu-kgsl-cb";
			label = "gfx3d_secure";
			iommus = <&kgsl_smmu 0x2 0x400>;
			qcom,iommu-dma = "disabled";
		};
	};

    // gmu配置
	gmu: qcom,gmu@2c6a000 {
		compatible = "qcom,gpu-gmu";

		reg = <0x2c6a000 0x30000>,
			<0xb280000 0x10000>,
			<0xb480000 0x10000>;
		reg-names = "kgsl_gmu_reg",
			"kgsl_gmu_pdc_cfg",
			"kgsl_gmu_pdc_seq";

		interrupts = <0 304 IRQ_TYPE_LEVEL_HIGH>,
			<0 305 IRQ_TYPE_LEVEL_HIGH>;
		interrupt-names = "kgsl_hfi_irq", "kgsl_gmu_irq";

		regulator-names = "vddcx", "vdd";

		iommus = <&kgsl_smmu 0x5 0x400>;
		qcom,iommu-dma = "disabled";

		vddcx-supply = <&gpu_cx_gdsc>;
		vdd-supply = <&gpu_gx_gdsc>;

		clocks = <&gpucc GPU_CC_CX_GMU_CLK>,
			<&gpucc GPU_CC_CXO_CLK>,
			<&gcc GCC_DDRSS_GPU_AXI_CLK>,
			<&gcc GCC_GPU_MEMNOC_GFX_CLK>,
			<&gpucc GPU_CC_AHB_CLK>;

		clock-names = "gmu_clk", "cxo_clk", "axi_clk",
				    "gcc_gpu_memnoc_gfx_clk", "gpu_cc_ahb";

		/* AOP mailbox for sending ACD enable and disable messages */
		mboxes = <&qmp_aop 0>;
		mbox-names = "aop";

	};
};

2. adreno_gpu_core

/**
 * struct adreno_gpu_core - A specific GPU core definition
 */
struct adreno_gpu_core {
	enum adreno_gpurev gpurev;
	unsigned int core, major, minor, patchid;
	/**
	 * @compatible: If specified, use the compatible string to match the
	 * device
	 */
	const char *compatible;
	unsigned long features;
    // Pointer to the GPU family specific functions for this core[见2.1节]
	const struct adreno_gpudev *gpudev;
    // 性能计数器
	const struct adreno_perfcounters *perfcounters;
	size_t gmem_size;
	u32 bus_width;
	/** @snapshot_size: Size of the static snapshot region in bytes */
	u32 snapshot_size;
};

2.1 adreno_gpudev

struct adreno_gpudev {
	/*
	 * These registers are in a different location on different devices,
	 * so define them in the structure and use them as variables.
	 */
	unsigned int *const reg_offsets;
	const struct adreno_ft_perf_counters *ft_perf_counters;
	unsigned int ft_perf_counters_count;

	struct adreno_coresight *coresight[2];

	/* GPU specific function hooks */
	int (*probe)(struct platform_device *pdev, u32 chipid,
		const struct adreno_gpu_core *gpucore);
	void (*snapshot)(struct adreno_device *adreno_dev,
				struct kgsl_snapshot *snapshot);
	irqreturn_t (*irq_handler)(struct adreno_device *adreno_dev);
	int (*init)(struct adreno_device *adreno_dev);
	void (*remove)(struct adreno_device *adreno_dev);
	int (*rb_start)(struct adreno_device *adreno_dev);
	int (*microcode_read)(struct adreno_device *adreno_dev);
	void (*start)(struct adreno_device *adreno_dev);
	bool (*is_sptp_idle)(struct adreno_device *adreno_dev);
	int (*regulator_enable)(struct adreno_device *adreno_dev);
	void (*regulator_disable)(struct adreno_device *adreno_dev);
	void (*pwrlevel_change_settings)(struct adreno_device *adreno_dev,
				unsigned int prelevel, unsigned int postlevel,
				bool post);
	int64_t (*read_throttling_counters)(struct adreno_device *adreno_dev);
	void (*count_throttles)(struct adreno_device *adreno_dev,
					uint64_t adj);
	unsigned int (*preemption_pre_ibsubmit)(
				struct adreno_device *adreno_dev,
				struct adreno_ringbuffer *rb,
				unsigned int *cmds,
				struct kgsl_context *context);
	int (*preemption_yield_enable)(unsigned int *cmds);
	unsigned int (*set_marker)(unsigned int *cmds,
				enum adreno_cp_marker_type type);
	unsigned int (*preemption_post_ibsubmit)(
				struct adreno_device *adreno_dev,
				unsigned int *cmds);
	int (*preemption_init)(struct adreno_device *adreno_dev);
	void (*preemption_schedule)(struct adreno_device *adreno_dev);
	int (*preemption_context_init)(struct kgsl_context *context);
	void (*context_detach)(struct adreno_context *drawctxt);
	void (*clk_set_options)(struct adreno_device *adreno_dev,
				const char *name, struct clk *clk, bool on);
	void (*pre_reset)(struct adreno_device *adreno_dev);
	void (*gpu_keepalive)(struct adreno_device *adreno_dev,
			bool state);
	bool (*hw_isidle)(struct adreno_device *adreno_dev);
	const char *(*iommu_fault_block)(struct kgsl_device *device,
				unsigned int fsynr1);
	int (*reset)(struct kgsl_device *device);
	bool (*sptprac_is_on)(struct adreno_device *adreno_dev);
	unsigned int (*ccu_invalidate)(struct adreno_device *adreno_dev,
				unsigned int *cmds);
	/** @read_alwayson: Return the current value of the alwayson counter */
	u64 (*read_alwayson)(struct adreno_device *adreno_dev);
	/**
	 * @power_ops: Target specific function pointers to power up/down the
	 * gpu
	 */
	const struct adreno_power_ops *power_ops;
	int (*clear_pending_transactions)(struct adreno_device *adreno_dev);
	void (*deassert_gbif_halt)(struct adreno_device *adreno_dev);
	void (*regulator_disable_poll)(struct kgsl_device *device);
};

2.2 adreno_perfcounters

/**
 * adreno_perfcounts: all available perfcounter groups
 * @groups: available groups for this device
 * @group_count: total groups for this device
 */
struct adreno_perfcounters {
    // 性能计数器组
	const struct adreno_perfcount_group *groups;
    // 性能计数器组个数
	unsigned int group_count;
};

2.2.1 adreno_perfcount_group

/**
 * struct adreno_perfcount_group: registers for a hardware group
 * @regs: available registers for this group
 * @reg_count: total registers for this group
 * @name: group name for this group
 */
struct adreno_perfcount_group {
    // 性能计数器寄存器[见2.2.2节]
	struct adreno_perfcount_register *regs;
	unsigned int reg_count;
	const char *name;
	unsigned long flags;
	int (*enable)(struct adreno_device *adreno_dev,
		const struct adreno_perfcount_group *group,
		unsigned int counter, unsigned int countable);
	u64 (*read)(struct adreno_device *adreno_dev,
		const struct adreno_perfcount_group *group,
		unsigned int counter);
};

2.2.2 adreno_perfcount_register

/**
 * struct adreno_perfcount_register: register state
 * @countable: countable the register holds
 * @kernelcount: number of user space users of the register
 * @usercount: number of kernel users of the register
 * @offset: register hardware offset
 * @load_bit: The bit number in LOAD register which corresponds to this counter
 * @select: The countable register offset
 * @value: The 64 bit countable register value
 */
struct adreno_perfcount_register {
	unsigned int countable;
	unsigned int kernelcount;
	unsigned int usercount;
	unsigned int offset;
	unsigned int offset_hi;
	int load_bit;
	unsigned int select;
	uint64_t value;
};

3. adreno_gpulist

// adreno系列的核心定义
static const struct adreno_gpu_core *adreno_gpulist[] = {
	&adreno_gpu_core_a306.base,
	&adreno_gpu_core_a306a.base,
	&adreno_gpu_core_a304.base,
	&adreno_gpu_core_a405,		/* Deprecated */
	&adreno_gpu_core_a418,		/* Deprecated */
	&adreno_gpu_core_a420,		/* Deprecated */
	&adreno_gpu_core_a430,		/* Deprecated */
	&adreno_gpu_core_a530v1,	/* Deprecated */
	&adreno_gpu_core_a530v2.base,
	&adreno_gpu_core_a530v3.base,
	&adreno_gpu_core_a505.base,
	&adreno_gpu_core_a506.base,
	&adreno_gpu_core_a510.base,
	&adreno_gpu_core_a540v1,	/* Deprecated */
	&adreno_gpu_core_a540v2.base,
	&adreno_gpu_core_a512.base,
	&adreno_gpu_core_a508.base,
	&adreno_gpu_core_a630v1,	/* Deprecated */
	&adreno_gpu_core_a630v2.base,
	&adreno_gpu_core_a615.base,
	&adreno_gpu_core_a618.base,
	&adreno_gpu_core_a619.base,
	&adreno_gpu_core_a619_variant.base,
	&adreno_gpu_core_a620.base,
    // 以Adreno640为例进行分析[见3.1节]
	&adreno_gpu_core_a640.base,
	&adreno_gpu_core_a650.base,
	&adreno_gpu_core_a650v2.base,
	&adreno_gpu_core_a660.base,
	&adreno_gpu_core_a660v2.base,
	&adreno_gpu_core_a680.base,
	&adreno_gpu_core_a612.base,
	&adreno_gpu_core_a616.base,
	&adreno_gpu_core_a610.base,
	&adreno_gpu_core_a642.base,
	&adreno_gpu_core_a642l.base,
	&adreno_gpu_core_a702.base,
};

3.1 adreno_gpu_core_a640

// adreno_a6xx_core是6xx系列GPU核心定义
static const struct adreno_a6xx_core adreno_gpu_core_a640 = {
	.base = {
		DEFINE_ADRENO_REV(ADRENO_REV_A640, 6, 4, 0, ANY_ID),
		.features = ADRENO_RPMH | ADRENO_GPMU |
			ADRENO_CONTENT_PROTECTION | ADRENO_IOCOHERENT |
			ADRENO_IFPC | ADRENO_PREEMPTION,
        // a640的adreno_gpudev[见3.2节]
		.gpudev = &adreno_a6xx_gmu_gpudev,
        // a640的性能计数器
		.perfcounters = &adreno_a6xx_legacy_perfcounters,
		.gmem_size = SZ_1M, //Verified 1MB
		.bus_width = 32,
		.snapshot_size = 2 * SZ_1M,
	},
	.prim_fifo_threshold = 0x00200000,
	.gmu_major = 2,
	.gmu_minor = 0,
	.sqefw_name = "a630_sqe.fw",
	.gmufw_name = "a640_gmu.bin",
	.zap_name = "a640_zap",
	.hwcg = a640_hwcg_regs,
	.hwcg_count = ARRAY_SIZE(a640_hwcg_regs),
	.vbif = a640_vbif_regs,
	.vbif_count = ARRAY_SIZE(a640_vbif_regs),
	.hang_detect_cycles = 0xcfffff,
	.protected_regs = a630_protected_regs,
	.disable_tseskip = true,
	.highest_bank_bit = 15,
};

3.2 adreno_a6xx_gmu_gpudev

const struct adreno_gpudev adreno_a6xx_gmu_gpudev = {
	.reg_offsets = a6xx_register_offsets,
	.probe = a6xx_gmu_device_probe,
	.start = a6xx_start,
	.snapshot = a6xx_gmu_snapshot,
	.init = a6xx_init,
	.irq_handler = a6xx_irq_handler,
	.rb_start = a6xx_rb_start,
	.regulator_enable = a6xx_gmu_sptprac_enable,
	.regulator_disable = a6xx_gmu_sptprac_disable,
	.read_throttling_counters = a6xx_read_throttling_counters,
	.microcode_read = a6xx_microcode_read,
	.gpu_keepalive = a6xx_gpu_keepalive,
	.hw_isidle = a6xx_hw_isidle,
	.iommu_fault_block = a6xx_iommu_fault_block,
	.reset = a6xx_gmu_restart,
	.preemption_pre_ibsubmit = a6xx_preemption_pre_ibsubmit,
	.preemption_post_ibsubmit = a6xx_preemption_post_ibsubmit,
	.preemption_init = a6xx_preemption_init,
	.preemption_schedule = a6xx_preemption_schedule,
	.set_marker = a6xx_set_marker,
	.preemption_context_init = a6xx_preemption_context_init,
	.sptprac_is_on = a6xx_gmu_sptprac_is_on,
	.ccu_invalidate = a6xx_ccu_invalidate,
#ifdef CONFIG_QCOM_KGSL_CORESIGHT
	.coresight = {&a6xx_coresight, &a6xx_coresight_cx},
#endif
	.read_alwayson = a6xx_read_alwayson,
	.power_ops = &a6xx_gmu_power_ops,
};

3.3 adreno_a6xx_legacy_perfcounters

/* a610, a612, a616, a618 and a619 do not have the GMU registers.
 * a605, a608, a615, a630, a640 and a680 don't have enough room in the
 * CP_PROTECT registers so the GMU counters are not accessible
 */
const struct adreno_perfcounters adreno_a6xx_legacy_perfcounters = {
	a6xx_legacy_perfcounter_groups,
	ARRAY_SIZE(a6xx_legacy_perfcounter_groups),
};

// a640性能计数器组定义:详情在性能计数器篇中进行介绍
static const struct adreno_perfcount_group
a6xx_legacy_perfcounter_groups [KGSL_PERFCOUNTER_GROUP_MAX] = {
	A6XX_PERFCOUNTER_GROUP(CP, cp,
		a6xx_counter_enable, a6xx_counter_read),
	A6XX_PERFCOUNTER_GROUP_FLAGS(RBBM, rbbm, 0,
		a6xx_counter_enable, a6xx_counter_read),
	A6XX_PERFCOUNTER_GROUP(PC, pc,
		a6xx_counter_enable, a6xx_counter_read),
	A6XX_PERFCOUNTER_GROUP(VFD, vfd,
		a6xx_counter_enable, a6xx_counter_read),
	A6XX_PERFCOUNTER_GROUP(HLSQ, hlsq,
		a6xx_counter_inline_enable, a6xx_counter_read),
	A6XX_PERFCOUNTER_GROUP(VPC, vpc,
		a6xx_counter_enable, a6xx_counter_read),
	A6XX_PERFCOUNTER_GROUP(CCU, ccu,
		a6xx_counter_enable, a6xx_counter_read),
	A6XX_PERFCOUNTER_GROUP(CMP, cmp,
		a6xx_counter_enable, a6xx_counter_read),
	A6XX_PERFCOUNTER_GROUP(TSE, tse,
		a6xx_counter_enable, a6xx_counter_read),
	A6XX_PERFCOUNTER_GROUP(RAS, ras,
		a6xx_counter_enable, a6xx_counter_read),
	A6XX_PERFCOUNTER_GROUP(LRZ, lrz,
		a6xx_counter_enable, a6xx_counter_read),
	A6XX_PERFCOUNTER_GROUP(UCHE, uche,
		a6xx_counter_enable, a6xx_counter_read),
	A6XX_PERFCOUNTER_GROUP(TP, tp,
		a6xx_counter_inline_enable, a6xx_counter_read),
	A6XX_PERFCOUNTER_GROUP(SP, sp,
		a6xx_counter_inline_enable, a6xx_counter_read),
	A6XX_PERFCOUNTER_GROUP(RB, rb,
		a6xx_counter_enable, a6xx_counter_read),
	A6XX_PERFCOUNTER_GROUP(VSC, vsc,
		a6xx_counter_enable, a6xx_counter_read),
	A6XX_PERFCOUNTER_GROUP_FLAGS(VBIF, gbif, 0,
		a6xx_counter_gbif_enable, a6xx_counter_read_norestore),
	A6XX_PERFCOUNTER_GROUP_FLAGS(VBIF_PWR, gbif_pwr,
		ADRENO_PERFCOUNTER_GROUP_FIXED,
		a6xx_counter_gbif_pwr_enable, a6xx_counter_read_norestore),
	A6XX_PERFCOUNTER_GROUP_FLAGS(ALWAYSON, alwayson,
		ADRENO_PERFCOUNTER_GROUP_FIXED,
		a6xx_counter_alwayson_enable, a6xx_counter_alwayson_read),
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值