23_实验二十二_移植网卡驱动

实验二十二 移植网卡驱动------四处齐动,把 MAE0621A 装进内核

对应课件:《第5章 移植Linux内核》5.4 节步骤 6(Slide 52~56)

系列说明 :本系列基于华清远见 FS-MP1A(STM32MP157A)开发板,对应课件《第5章 移植Linux内核》。为什么第 5 章要专门移植网卡?因为后面的开发、调试全靠网络挂载根文件系统 (改一次文件不用烧盘),网线就是生命线。eMMC 那篇(实验二十一)驱动现成、只补设备树;本篇不一样------驱动代码也要自己放 (资料包里的 maxio.c/phy_device.c,后者还要覆盖内核同名文件),加上 Makefile、Kconfig、menuconfig、设备树,四处齐动,是第 5 章改动最多的一篇,也是最像"真实工作"的一篇。前置:实验二十一(eMMC 节点已通,重编译流程已熟)。

一、改动全景:四个文件,各管一件事

# 改哪里 干什么 性质
1 drivers/net/phy/ 目录 放入 maxio.c、phy_device.c 两个驱动源文件 加代码
2 drivers/net/phy/Makefile 加一行编译项,让 .c 被编译成 .o 构建入口
3 drivers/net/phy/Kconfig 加一个配置项,让 menuconfig 里出现它的开关 配置入口
4 arch/arm/boot/dts/stm32mp15xx-fsmp1x.dtsi 加 &ethernet0 节点,告诉内核网卡接在哪 硬件描述

四者的关系:没有 1,2 无米下锅;没有 2,1 编不进;没有 3,menuconfig 里找不到开关(也就打不开 1、2 的总闸);没有 4,驱动编进去了也不知道网卡在哪根总线上。少一处就白跑一次编译。

先弄清驱动角色 :我们板子的以太网是"MAC + PHY"两级结构------MAC 控制器(ethernet0,ST 芯片内置,内核自带 stmmac 驱动,设备树里声明即可)+ PHY 芯片 MAE0621A (板子上一颗独立小芯片,需要专门驱动)。第 3 章 F-5 给 U-Boot 换过它的驱动(u-boot-网卡-MAE0621A驱动.rar,放的是 phy.c/maxio.c/dwc_eth_qos.c),本篇换的是内核版 驱动(kernel-网卡驱动.zip,放的是 phy_device.c/maxio.c)------课件 Slide 52 特别提醒:"注意不是用于 u-boot 的驱动"。两个包里虽然都叫 maxio.c,内容也各是各的(编译体系都不同),两套代码、两个世界,别拿错包。

二、实验环境(实际)

项目 实际值
操作位置 Ubuntu 虚拟机,实验二十/二十一那棵源码树
素材 kernel-网卡驱动.zip(18,708 字节,md5 d75517d6942d13263c1d9a165aff043b)------结构是两层目录:kernel-网卡驱动/MAE0621A驱动/ 下两个文件:maxio.c(6,654 字节)、phy_device.c(64,547 字节)
板子 本篇不开

开工自检(10 秒) :git status --short 里只剩实验二十一的预期改动(dtsi/.config 类,干净更好);ls drivers/net/phy/ 能看到几十个 .c 文件、Makefile/Kconfig 在列。

三、课件 ↔ 步骤对应表

课件 Slide 内容 对应步骤
52 为什么移植网卡;从出厂源码包取 MAE0621A 的 phy_device.c/maxio.c 放入 drivers/net/phy 步骤 1
53 phy/Makefile 加 obj-$(CONFIG_MAXIO_PHY) += maxio.o 步骤 2
54 phy/Kconfig 加 config MAXIO_PHY 配置项 步骤 3
55 menuconfig:Device Drivers → Network device support → PHY → 勾 <*> MAXIO PHYS 步骤 4
56 dtsi 末尾加 &ethernet0 节点 + 重编译 make -j4 uImage dtbs LOADADDR=0xC2000040 步骤 5~6

本篇动作 → 后面谁用 → 现在含糊的后果

本篇动作 后面哪一篇要用 现在含糊的后果
内核里装好 MAE0621A + ethernet0 节点 实验二十三点火后内核日志应出现 PHY 报上名来;第 6 章用 NFS 挂根文件系统时全靠这块网卡 网卡没通,第 6 章的 NFS 根起不来,整个"网络调试"玩法作废
Makefile/Kconfig/menuconfig 三件套的改法 第 7 章起写自己的驱动时一模一样的三件套(加代码→Makefile→Kconfig→menuconfig) 只会抄不会懂,后面每加一个驱动都要现查
"四处齐动"的完整性检查习惯 以后任何"内核不支持某硬件"的移植都这么拆解 漏一处就白编一次(十几分钟起步),还查不出原因

本篇需要的资料:kernel-网卡驱动.zip(见上表)。注意与第 3 章 F-5 的 U-Boot 驱动包区分(见"先弄清驱动角色"一段)。

四、实验步骤

步骤 1:放入驱动源文件(Slide 52)

把 kernel-网卡驱动.zip 拷进虚拟机共享目录,然后在源码树里解包------注意 zip 里套了两层目录 (kernel-网卡驱动/MAE0621A驱动/),用 unzip -j 直接拍平到目标目录最省事:

bash 复制代码
cd <共享目录>/stm32mp1-openstlinux-5.4-dunfell-mp1-20-06-24/sources/arm-ostl-linux-gnueabi/linux-stm32mp-5.4.31-r0/linux-5.4.31/drivers/net/phy/      # 内核源码顶层之下的 phy 目录(新开终端就要重新 cd)
unzip -j -o <共享目录>/kernel-网卡驱动.zip -d .   # zip 路径按你的共享目录改

就地验证(三条):

bash 复制代码
ls -l maxio.c phy_device.c
# maxio.c 6,654 字节;phy_device.c 64,547 字节(内核自带那份是 64,147 字节------被覆盖的就是它)
cp phy_device.c /tmp/phy_device.c.orig        # 覆盖已经发生,留一份内核自带的原版备查

phy_device.c 名字撞车、必须覆盖,为什么? 内核自带的 drivers/net/phy/phy_device.c 是 MDIO 总线扫 PHY 的通用代码。华清改过三个地方(拿 /tmp/phy_device.c.orig 与 zip 版 diff 一看便知):

① 读 PHY ID 之前先往 MII_PHYSID2 寄存器写一次 0 ------注释原话 "An MDIO connects to multiple PHYs requiring write before read":MAE0621A 的脾气是先写后读才肯报 ID ,不写就探测不到。这段代码包在 #ifdef CONFIG_MAXIO_PHY 里------步骤 3 要定义的那个配置开关,管的不只是 maxio.c 编不编译,还管这段"怪脾气补丁"编不编译 ,三处改动咬合就在这;

② 读 ID 失败时返回 -ENODEV 而不是 -EIO------注释 "returning -ENODEV doesn't stop bus scanning",让 MDIO 总线把所有地址扫完而不是半途而废;

③ 一处设备生命周期小修正(device_initialize 时机提前)。

所以覆盖不是可选项,是课件方案的必需环节;.orig 备份的价值是"以后启动异常时能 diff 出厂商到底动了什么"。

图:实测------步骤 1 全程:cd 进 drivers/net/phy/(绿箭头"前往 phy 目录");unzip -j -o <共享目录>/kernel-网卡驱动.zip -d . 把共享目录里的网卡驱动包直接解压进当前目录 (inflating: ./maxio.c、inflating: ./phy_device.c 两行 = 两个文件各就各位);ls -l 对账 6,654 / 64,547 字节 (7月7日 2022 = 素材包自带的时间戳);cp phy_device.c /tmp/phy_device.c.orig 完成备份(cp 成功一声不吭)。

步骤 2:Makefile 加编译项(Slide 53)

步骤 1 把你带进了 drivers/net/phy/ 深处,先退回内核源码根目录 ------从这里到顶层正好三级(phy → net → drivers → 顶层),.. 写三次:

bash 复制代码
cd ../../..
pwd      # 末段应是 linux-5.4.31 ------ 回到内核源码根目录

此后直到本篇结束都在根目录里操作(grep/nano/make 的相对路径全部以它为基准,别再往下钻)。然后打开 Makefile:

bash 复制代码
nano drivers/net/phy/Makefile

Ctrl+W 搜 XGENE 回车定位,在第 44 行 obj-$(CONFIG_MDIO_XGENE) += mdio-xgene.o 附近加一行:

makefile 复制代码
obj-$(CONFIG_MAXIO_PHY)         += maxio.o

改完 Ctrl+O 回车保存(这台 Ubuntu 的 nano 是 4.8,底栏就是 ^O 写入;新版 nano 的 Ctrl+S 未默认绑定,按了可能没反应------以 ^O 写入 为准)、Ctrl+X 退出。改好的样子:

图:实测(顶替课件 Slide 53)------nano 4.8 里改好的样子:绿框即新增的 obj-$(CONFIG_MAXIO_PHY) += maxio.o,插在 obj-$(CONFIG_MDIO_XGENE) += mdio-xgene.o 之后,绿箭头指向它;底部"已写入 97 行" = 保存成功。

就地验证:

bash 复制代码
grep -n "MAXIO" drivers/net/phy/Makefile     # 应打出新加的一行

这行的意思:如果 .config 里 CONFIG_MAXIO_PHY=y,就把 maxio.c 编进内核(=m 则编成模块)。CONFIG_MAXIO_PHY 这个名字是我们下一步在 Kconfig 里自己定义的------Makefile 与 Kconfig 靠这个名字对上号,拼错一个字母就断链。

步骤 3:Kconfig 加配置项(Slide 54)

打开 Kconfig:

bash 复制代码
nano drivers/net/phy/Kconfig

Ctrl+W 搜 RENESAS 回车定位(保存退出键同上),在 config REALTEK_PHY(第 452 行)与 config RENESAS_PHY(第 457 行)附近加:

kconfig 复制代码
config MAXIO_PHY
	tristate "MAXIO PHYS"
	---help---
	  Supports the Maxio MAEXXXX PHY.

图:实测(顶替课件 Slide 54)------nano 4.8 里改好的样子:绿框即新增的 config MAXIO_PHY 三行(tristate 与 ---help--- 用 Tab 缩进),插在 config REALTEK_PHY 之后、config RENESAS_PHY 之前,与正文指引的位置一致;标题栏"已更改"表示截图时还没保存。config 后面的 MAXIO_PHY 就是步骤 2 Makefile 引用的那个名字,也是步骤 1 里 phy_device.c 那段 #ifdef 认的名字。

步骤 4:menuconfig 勾选(Slide 55)

bash 复制代码
make menuconfig

路径(三层子菜单,逐层进):

复制代码
Device Drivers --->
    [*] Network device support --->
        -*- PHY Device support and infrastructure --->
            <*> MAXIO PHYS        ← 新出现的项,勾成 <*>(编进内核)

按键逐步走(menuconfig 三招在实验二十一步骤 2 演示过,这里按本篇路径再走一遍):

  1. 进第一层 :方向键把光标移到 Device Drivers ---> 行,按回车;
  2. 进第二层 :方向键移到 Network device support --->(确认它已是 [*]),按回车;
  3. 进第三层 :方向键移到 PHY Device support and infrastructure --->(-*- 状态),按回车;
  4. 勾选 :方向键移到 < > MAXIO PHYS 行,按空格键 ------[ ] 变 <*> 即勾上(或直接按 Y ,效果相同;多按一次空格会切回 [ ],按错了就再按回来);
  5. 退出并保存 :连按两次 Esc 逐层退出(也可以每层选 <Exit>),回到最外层再退时会问 Do you wish to save your new configuration?------方向键选 Yes 回车。不保存 = 白勾。

图:课件 Slide 55------PHY 设备菜单实拍:<*> Realtek PHYs(已选)、标红新增的 <*> MAXIO PHYS(已选) 、< > Driver for Renesas PHYs(未选)------勾完之后你的屏幕就是这一页的样子。
这次是真的要勾 :与实验二十一的 SDMMC(default y 天生已开)不同,MAXIO_PHY 是我们自己在 Kconfig 里新定义的选项,默认不开 ------menuconfig 里不勾,前两步全白搭。路径前两级显示 [*]/-*- 是因为 MAXIO_PHY 依赖它们(menuconfig 里按 / 搜 MAXIO 能看到依赖链),依赖不满足时这一项根本不出现。

就地验证:

bash 复制代码
grep MAXIO .config      # 应打出 CONFIG_MAXIO_PHY=y

步骤 5:设备树加 ethernet0 节点(Slide 56)

打开设备树文件(实验二十一改过的同一个文件):

bash 复制代码
nano arch/arm/boot/dts/stm32mp15xx-fsmp1x.dtsi

Ctrl+W 搜 vrefbuf 回车------跳到文件末尾区域,在文件末尾 (&vrefbuf 段之后)添加:

c 复制代码
/*网卡*/
&ethernet0 {
    status = "okay";
    pinctrl-0 = <&ethernet0_rgmii_pins_a>;
    pinctrl-1 = <&ethernet0_rgmii_pins_sleep_a>;
    pinctrl-names = "default", "sleep";
    phy-mode = "rgmii-id";
    max-speed = <1000>;
    phy-handle = <&phy0>;

    mdio0 {
        #address-cells = <1>;
        #size-cells = <0>;
        compatible = "snps,dwmac-mdio";
        phy0: ethernet-phy@0 {
            reg = <0>;
        };
    };
};

改完 Ctrl+O 回车保存、Ctrl+X 退出,然后对照课件图检查一遍插入效果:

图:课件 Slide 56------stm32mp15xx-fsmp1x.dtsi 文件末尾(课件作者文件里第 194~212 行)的新增内容截图,与我们抄的一致。

图:实测(对照上图课件)------nano 4.8 里改好的样子:绿框即新增的 /*网卡*/ &ethernet0 { ... } 整段,插在 &vrefbuf 段之后、文件末尾,绿箭头指向它。phy0: ethernet-phy@0 { reg = <0>; } 里的 0 就是 PHY 的 MDIO 地址(F-5 在 U-Boot 侧见过的同一个概念)。

逐行读关键属性(对照实验十六内核日志里 stm32-dwmac 5800a000.ethernet 那一串):

属性 意思
phy-mode = "rgmii-id" MAC 与 PHY 之间用 RGMII 千兆接口;-id 表示时钟延迟在数据线内嵌、由接收端补------硬件接法决定的写法,照抄即可
max-speed = <1000> 最高千兆(实际跑多快由 PHY 自协商决定,实验十三见过全过程)
phy-handle = <&phy0> + mdio0 里的 ethernet-phy@0 { reg = <0>; } MAC 通过 MDIO 总线找 PHY,PHY 地址 0 ------与 F-5 在 U-Boot 里那次同源(地址由板上接线决定,两边看到的是同一颗芯片);snps,dwmac-mdio 是 stmmac 内置 MDIO 总线的标准 compatible
ethernet0_rgmii_pins_a 等引脚组 定义在 SoC 级 stm32mp15-pinctrl.dtsi(0020 补丁带来的,实验二十一的五个 sdmmc2 引脚组也在那个文件),照抄引用即可

改完 git diff 过一眼。

步骤 6:重编译(Slide 56 末)

bash 复制代码
make -j4 uImage dtbs LOADADDR=0xC2000040

增量编译,几分钟(OOM 就 -j2)。验证:两个产物时间戳更新、新 uImage 字节数记下;git diff --stat 查看本篇改动------注意 maxio.c 是新文件(未跟踪),不会出现在 git diff --stat 里 (实测只列出 4 项:fsmp1x.dtsi、Kconfig、Makefile、phy_device.c),新增文件要用 git status --short 看。

验收预告(实验二十三) :用这对新文件点火,内核日志应出现 stmmac 探测 5800a000.ethernet、PHY 枚举(MAE0621A 报上名来)------之后 ping 通 Ubuntu、第 6 章 NFS 挂根才有戏。

步骤 7:git 收官提交(把实验二十一 + 本篇一起提交)

实验二十一(eMMC)与本篇(网卡)改的是同一个 fsmp1x.dtsi、同属"第 5 章驱动移植"一组改动,两篇一起提交一条 (实验二十一篇尾有说明)。提交前 git status --short 应能看到:maxio.c(新增)、phy_device.c(修改)、phy/Makefile、phy/Kconfig、fsmp1x.dtsi(含实验二十一的 sdmmc2 与本篇的 ethernet0 两段)与 .config(被 .gitignore 忽略、不入列)。显式列出提交(不用 git add -A,实验十一的教训):

bash 复制代码
git add drivers/net/phy/maxio.c drivers/net/phy/phy_device.c drivers/net/phy/Makefile drivers/net/phy/Kconfig arch/arm/boot/dts/stm32mp15xx-fsmp1x.dtsi
git commit -m "FS-MP1A eMMC 与网卡驱动(实验二十一/二十二)"
git log --oneline | head -3     # 顶部应是这条,其下是实验二十的提交
git status --short              # 应无输出 = 工作区干净

提交后版本串的 -dirty 消失(g<新哈希> 仍在);照例增量重编一次 make uImage dtbs LOADADDR=0xC2000040,实验二十三点火对账以最新字节数为准。

实际执行结果(2026-09-26 实测):

图:实测------步骤 6 重编译收尾与步骤 7 提交全程:重编译打出 Image Name: Linux-5.4.31-g381ee3922-dirty(版本串已从实验二十的 g77fcad789 前进到 g381ee3922 ,-dirty = 实验二十一/二十二改动当时未提交)、Data Size: 7315136、Load Address/Entry Point 均 c2000040,绿箭头"重编译成功"指 Kernel: arch/arm/boot/uImage is ready;随后 git diff --stat 显示 4 files changed, 41 insertions(+), 6 deletions(-)(maxio.c 是未跟踪文件不在此列 );git add 五文件、git commit 打出 [WORKING 8dd24eeb2] FS-MP1A eMMC 与网卡驱动(实验二十一/二十二)、5 files changed, 294 insertions(+), 6 deletions(-) (create mode ×1 = maxio.c 首次入库);git log --oneline 三层叠加 = 8dd24eeb2(实验二十一/二十二)→ 381ee3922(实验二十)→ 77fcad789(new kernel 基线);收尾 git status --short 无输出 = 工作区干净。

五、注意事项

  1. phy_device.c 覆盖内核自带同名文件 ------按课件做,但先留 .orig 备份(步骤 1 已带命令);三处改动的出处见步骤 1 的解读框。
  2. Makefile 的 CONFIG_MAXIO_PHY 与 Kconfig 的 config MAXIO_PHY 必须一字不差 (含大小写),这是一对"暗号";phy_device.c 里那段 #ifdef CONFIG_MAXIO_PHY 认的也是它------三处咬合,一处拼错全盘哑火。
  3. Kconfig 的缩进用 Tab :tristate/---help--- 行前的制表符是语法的一部分,用空格可能报 Kconfig 解析警告。
  4. menuconfig 退出选 Save ,grep MAXIO .config 复核 =y。
  5. dtsi 的 ethernet0 节点加在文件末尾 ,别插进别的节点中间;#address-cells/#size-cells 两行是 mdio0 子总线的编址声明,照抄即可。
  6. 重编译必须带 LOADADDR(实验二十/二十一同款注意事项)。
  7. 别拿成 U-Boot 版驱动包:文件名相近(都有 maxio.c),但 U-Boot 版三件套(phy.c/maxio.c/dwc_eth_qos.c)属于 U-Boot 的驱动体系------编译报头文件/函数不存在时,第一反应核对 md5 与文件清单。

六、验证点一览

验证点 命令 通过的样子 在哪一步敲
驱动文件到位 ls -l maxio.c phy_device.c(phy 目录里) 6,654 / 64,547 字节各一份 步骤 1
原版已备份 ls -l /tmp/phy_device.c.orig 64,147 字节(内核自带那份) 步骤 1
Makefile 编译项 grep -n "MAXIO" drivers/net/phy/Makefile obj-$(CONFIG_MAXIO_PHY) += maxio.o 一行 步骤 2
Kconfig 配置项 grep -n -A1 "MAXIO" drivers/net/phy/Kconfig config MAXIO_PHY + tristate "MAXIO PHYS" 步骤 3
配置已勾 grep MAXIO .config CONFIG_MAXIO_PHY=y 步骤 4
设备树节点 grep -A 18 "网卡" arch/arm/boot/dts/stm32mp15xx-fsmp1x.dtsi ethernet0 节点整段,在文件末尾 步骤 5
重编完成 ls -l arch/arm/boot/uImage arch/arm/boot/dts/stm32mp157a-fsmp1a.dtb 实测通过(2026-09-26) :时间戳 21:53/23:54 更新;uImage 头 Data Size: 7315136 步骤 6
改动范围清白 git diff --stat + git status --short diff 4 项(未跟踪的 maxio.c 不进 diff)+ status 可见 maxio.c;提交后两者都干净 步骤 6~7
git 收官提交 `git log --oneline head -3` 实测通过(2026-09-26) :8dd24eeb2 → 381ee3922 → 77fcad789 三层叠加

不达标时的排查:

现象 先查什么
menuconfig 里找不到 MAXIO PHYS Kconfig 的 config MAXIO_PHY 拼写、缩进是不是 Tab、文件保存没有;前两级依赖(Network device support / PHY infrastructure)是否开着
勾了但 .config 里是 # CONFIG_MAXIO_PHY is not set 依赖不满足------menuconfig 里按 / 搜 MAXIO 看依赖链
编译报 maxio.c 里的头文件/函数错误 源文件拿错包(拿成 U-Boot 版了?)------核对 md5 与文件名
编译报 phy_maxio_init 一类符号缺失 maxio.c 没拷到位、或 Makefile 那行没加------ls 与 grep 两把尺子各过一遍
覆盖没生效(PHY 仍探测不到) diff /tmp/phy_device.c.orig drivers/net/phy/phy_device.c 应能看到步骤 1 解读框里的三处差异;看不到 = 拷贝被原版盖回去了
点火后网卡仍不通 (实验二十三验收)先看内核日志有没有 stmmac/PHY 枚举行,再回本篇查设备树

七、实验完成标志

  • maxio.c(6,654 字节)、phy_device.c(64,547 字节)已放入 drivers/net/phy/,内核原版已备份 /tmp/phy_device.c.orig(步骤 1 实测,截图入档)
  • drivers/net/phy/Makefile 已加 obj-$(CONFIG_MAXIO_PHY) += maxio.o,grep 打出第 45 行(步骤 2 实测,截图入档)
  • drivers/net/phy/Kconfig 已加 config MAXIO_PHY 三行(Tab 缩进、插在 REALTEK 与 RENESAS 之间),grep 可见(步骤 3 实测,截图入档)
  • menuconfig 已勾 <*> MAXIO PHYS,.config 里 CONFIG_MAXIO_PHY=y(步骤 4)
  • stm32mp15xx-fsmp1x.dtsi 末尾已加 &ethernet0 节点(步骤 5 实测,截图入档)
  • make -j4 uImage dtbs LOADADDR=0xC2000040 重编译成功:uImage 头 Data Size: 7315136、版本串 Linux-5.4.31-g381ee3922-dirty(步骤 6 实测,截图入档)
  • git 收官提交完成:哈希 8dd24eeb2 (5 files changed, 294 insertions(+), 6 deletions(-)),git log 三层叠加、工作区干净(步骤 7 实测,截图入档)

八、下一步:运行内核

两块短板都补齐了:eMMC 认得出、网卡通了。下一篇(实验二十三,第 5 章收官)用我们自己编的 uImage 与 dtb 点火:先复现"无根文件系统"的老结局(顺便看清两块盘的盘号),再用 bootargs 挂上出厂根文件系统走进 Linux 命令行------第 3 章以来"bootm 找不到内核"与"内核找不到根"两大悬案,至此全部了结。

相关推荐
水饺编程1 小时前
第1章:开发环境搭建,在 Windows 中安装 Git
linux·c语言·汇编·git·ubuntu
0+1111 小时前
Linux --进程间关系和守护进程
linux·运维·服务器
AIgorithmGEEK1 小时前
[Linux]HTTP 应用层协议全解(下篇)
linux·网络·网络协议·http·cookie·无连接·无状态
名誉寒冰1 小时前
我是怎么理解 eBPF、AppArmor 和 Seccomp 的?
linux·网络·内核
H.莓飛1 小时前
【Linux】命令行参数、环境变量与程序地址空间
linux·c语言·chrome·后端·centos
‎ദ്ദിᵔ.˛.ᵔ₎2 小时前
linux 进程间通信
linux·网络
Ruiery2 小时前
Linux 6.6内核 CPU 深度解析(八):percpu 变量 — 每个 CPU 一份数据,无锁访问
linux·运维·服务器
91刘仁德2 小时前
传输层协议深度拆解:端口寻址、UDP 报文与 TCP 可靠性机制全解析
linux·网络·c++·网络协议·tcp/ip·udp·c
AIgorithmGEEK2 小时前
[Linux]HTTP 应用层协议全解(中篇)
linux·网络·网络协议·http