Linux Phonet 协议族

简介

Phonet 是诺基亚蜂窝调制解调器用于 IPC 和 RPC 的一种数据包协议。通过 Linux Phonet 套接字族,Linux 主机进程可以从/向调制解调器或任何连接到调制解调器的外部设备接收和发送消息。调制解调器负责路由。

Phonet 数据包可以通过各种硬件连接进行交换,具体取决于设备,例如

  • 带有 CDC Phonet 接口的 USB,

  • 红外线,

  • 蓝牙,

  • RS232 串行端口(带有专用的 “FBUS” 行规程),

  • 某些 TI OMAP 处理器的 SSI 总线。

数据包格式

Phonet 数据包具有如下所示的通用头部

struct phonethdr {
  uint8_t  pn_media;  /* Media type (link-layer identifier) */
  uint8_t  pn_rdev;   /* Receiver device ID */
  uint8_t  pn_sdev;   /* Sender device ID */
  uint8_t  pn_res;    /* Resource ID or function */
  uint16_t pn_length; /* Big-endian message byte length (minus 6) */
  uint8_t  pn_robj;   /* Receiver object ID */
  uint8_t  pn_sobj;   /* Sender object ID */
};

在 Linux 上,链路层头部包括 pn_media 字节(见下文)。接下来的 7 个字节是网络层头部的一部分。

设备 ID 是分开的:6 个高位构成设备地址,而 2 个低位用于多路复用,就像 8 位对象标识符一样。 因此,Phonet 可以被视为具有 6 位地址空间和 10 位传输协议的网络层(很像 IP 世界中的端口号)。

调制解调器的地址号始终为零。所有其他设备都有自己的 6 位地址。

网络层

Phonet 套接字地址族映射 Phonet 数据包头

struct sockaddr_pn {
  sa_family_t spn_family;    /* AF_PHONET */
  uint8_t     spn_obj;       /* Object ID */
  uint8_t     spn_dev;       /* Device ID */
  uint8_t     spn_resource;  /* Resource or function */
  uint8_t     spn_zero[...]; /* Padding */
};

资源字段仅在发送和接收时使用;bind() 和 getsockname() 会忽略它。

低级数据报协议

应用程序可以使用 PF_PHONET 系列中的 Phonet 数据报套接字协议发送 Phonet 消息。每个套接字都绑定到 2^10 个可用对象 ID 之一,并且可以与任何其他对等方发送和接收数据包。

struct sockaddr_pn addr = { .spn_family = AF_PHONET, };
ssize_t len;
socklen_t addrlen = sizeof(addr);
int fd;

fd = socket(PF_PHONET, SOCK_DGRAM, 0);
bind(fd, (struct sockaddr *)&addr, sizeof(addr));
/* ... */

sendto(fd, msg, msglen, 0, (struct sockaddr *)&addr, sizeof(addr));
len = recvfrom(fd, buf, sizeof(buf), 0,
               (struct sockaddr *)&addr, &addrlen);

此协议遵循 SOCK_DGRAM 无连接语义。但是,connect() 和 getpeername() 不受支持,因为它们似乎在 Phonet 用法中没有用处(可以很容易地添加)。

资源订阅

一个 Phonet 数据报套接字可以订阅任意数量的 8 位 Phonet 资源,如下所示

uint32_t res = 0xXX;
ioctl(fd, SIOCPNADDRESOURCE, &res);

使用 SIOCPNDELRESOURCE I/O 控制请求或在关闭套接字时,以类似方式取消订阅。

请注意,在任何给定时间,任何给定资源只能订阅一个套接字。如果不是,ioctl() 将返回 EBUSY。

Phonet 管道协议

Phonet 管道协议是一个简单的顺序数据包协议,具有端到端拥塞控制。它使用被动侦听套接字范例。侦听套接字绑定到唯一的空闲对象 ID。每个侦听套接字可以处理多达 255 个并发连接,每个 accept() 的套接字一个。

int lfd, cfd;

lfd = socket(PF_PHONET, SOCK_SEQPACKET, PN_PROTO_PIPE);
listen (lfd, INT_MAX);

/* ... */
cfd = accept(lfd, NULL, NULL);
for (;;)
{
  char buf[...];
  ssize_t len = read(cfd, buf, sizeof(buf));

  /* ... */

  write(cfd, msg, msglen);
}

传统上,连接是通过 “第三方” 应用程序在两个端点之间建立的。这意味着两个端点都是被动的。

从 Linux 内核版本 2.6.39 开始,也可以通过在主动端使用 connect() 直接连接两个端点。这旨在支持更新的诺基亚无线调制解调器 API,例如在 ST-Ericsson U8500 平台中的诺基亚超薄调制解调器中找到的 API

struct sockaddr_spn spn;
int fd;

fd = socket(PF_PHONET, SOCK_SEQPACKET, PN_PROTO_PIPE);
memset(&spn, 0, sizeof(spn));
spn.spn_family = AF_PHONET;
spn.spn_obj = ...;
spn.spn_dev = ...;
spn.spn_resource = 0xD9;
connect(fd, (struct sockaddr *)&spn, sizeof(spn));
/* normal I/O here ... */
close(fd);

管道协议在 SOL_PNPIPE 级别提供两个套接字选项

PNPIPE_ENCAP 接受一个整数值(int),值为

PNPIPE_ENCAP_NONE

套接字正常运行(默认)。

PNPIPE_ENCAP_IP

套接字用作虚拟 IP 接口的后端。这需要 CAP_NET_ADMIN 功能。诺基亚调制解调器上的 GPRS 数据支持可以使用此功能。请注意,在此模式下,无法可靠地 poll() 或从套接字 read()。

PNPIPE_IFINDEX

是一个只读整数值。它包含 PNPIPE_ENCAP 创建的网络接口的接口索引,如果封装关闭,则为零。

PNPIPE_HANDLE

是一个只读整数值。它包含管道的底层标识符(“管道句柄”)。这仅针对已连接或正在连接的套接字描述符定义。

作者

Linux Phonet 最初由 Sakari Ailus 编写。

其他贡献者包括 Mikä Liljeberg、Andras Domokos、Carlos Chinea 和 Rémi Denis-Courmont。

版权所有 © 2008 诺基亚公司。