2017年11月25日 星期六

MQTT sample code using libmosquitto


Sample code for publisher

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <mosquitto.h>

// Server connection parameters
#define MQTT_HOSTNAME "localhost"
#define MQTT_PORT 1883
#define MQTT_TOPIC "/brook"

struct brook_obj {
 unsigned long long published;
 unsigned long long connected;
 unsigned long long disconnected;
 unsigned long long loged;
};

/*
 mosq the mosquitto instance making the callback.
 obj the user data provided in mosquitto_new
 rc the return code of the connection response, one of:
*/
static void connect_cb(struct mosquitto *mosq, void *obj, int rc)
{
 struct brook_obj *bobj = (struct brook_obj*) obj;
 bobj->connected++;
        printf("%s(), connect:%llu, published:%llu, loged:%llu, rc:%d\n",
  __FUNCTION__, bobj->connected, bobj->published, bobj->loged, rc);
}

static void publish_cb(struct mosquitto *mosq, void *obj, int mid)
{
 struct brook_obj *bobj = (struct brook_obj*) obj;
 bobj->published++;
        printf("%s(), connect:%llu, published:%llu, loged:%llu, mid:%d\n",
  __FUNCTION__, bobj->connected, bobj->published, bobj->loged, mid);
}

static void log_cb(struct mosquitto *mosq, void *obj, int level, const char *str)
{
 struct brook_obj *bobj = (struct brook_obj*) obj;
 bobj->loged++;
        printf("%s(), connect:%llu, published:%llu, loged:%llu, level:%d,\n\tstr:%s\n",
  __FUNCTION__, bobj->connected, bobj->published, bobj->loged, level, str);
}

static void disconnect_cb(struct mosquitto *mosq, void *obj, int rc)
{
 struct brook_obj *bobj = (struct brook_obj*) obj;
 bobj->disconnected++;
        printf("%s(), connect:%llu, published:%llu, loged:%llu, disconnected:%llu\n",
  __FUNCTION__, bobj->connected, bobj->published, bobj->loged, bobj->disconnected);
}

/*
 * Start here
 */
int main (int argc, char **argv)
{
 char clientid[24], text[20];
 struct mosquitto *mosq = NULL;
 unsigned long long i;
 int rc, major, minor, rev;
 struct brook_obj brook_obj = {};

 /* Must be called before any other mosquitto functions.
    This function is not thread safe. */
  mosquitto_lib_init();

  rc = mosquitto_lib_version(&major, &minor, &rev);
  fprintf(stdout, "rc:%d, major:%d, minor:%d, rev:%d\n",
  rc, major, minor, rev);

  memset(clientid, 0, sizeof(clientid));
  snprintf(clientid, sizeof(clientid) - 1, "cid-pub-%d", getpid());
  mosq = mosquitto_new(clientid, true, &brook_obj);
 if (!mosq) {
  fprintf (stderr, "new mosq failed \n");
  exit(-1);
 }

  rc = mosquitto_tls_opts_set(mosq, 1, "tlsv1", NULL);
 if (rc) {
  fprintf (stderr, "set tls failed %d\n", rc);
  exit (-1);
 }

  rc = mosquitto_tls_set(mosq, "ca/client/ca.crt", NULL, "ca/client/client.crt", "ca/client/client.key", NULL);
  if (rc) {
  fprintf (stderr, "set tls failed %d\n", rc);
  exit (-1);
  }

   rc = mosquitto_tls_insecure_set(mosq, true);
  if (rc) {
  fprintf (stderr, "set insecure failed %d\n", rc);
  exit (-1);
  }

 // Set callback function
  mosquitto_connect_callback_set(mosq, connect_cb);
  mosquitto_publish_callback_set(mosq, publish_cb);
  mosquitto_log_callback_set(mosq, log_cb);
  mosquitto_disconnect_callback_set(mosq, disconnect_cb);

  rc = mosquitto_connect(mosq, MQTT_HOSTNAME, MQTT_PORT, 60);
  if (rc) {
   fprintf (stderr, "Can't connect to Mosquitto server. %d\n", rc);
   exit (-1);
  }

  for (i = 0; i < 10 ;i++) {
   sprintf(text, "%llu", i);
   printf("send %d, %s\n", (int)strlen(text), text);
   // Publish the message to the topic
  rc = mosquitto_publish(mosq, NULL, MQTT_TOPIC, strlen(text), text, 2, false);
  if (rc) {
   fprintf (stderr, "Can't publish to Mosquitto server\n");
   exit (-1);
  }
  if (!(i % 3)) {
   do {
    rc = mosquitto_loop(mosq, 3, 1);
    fprintf (stdout, "mosquitto_loop %d, published:%llu, i:%llu\n",
      rc, brook_obj.published, i);
   } while(rc == 0 && brook_obj.published < i);
  }
 }

 do {
  rc = mosquitto_loop(mosq, 3, 1);
  fprintf (stdout, "mosquitto_loop %d, published:%llu, i:%llu\n",
    rc, brook_obj.published, i);
 } while(rc == 0 && brook_obj.published != i);

 // Tidy up
 mosquitto_disconnect(mosq);
 mosquitto_destroy(mosq);
 mosquitto_lib_cleanup();

 return 0;
}



Sample code subscriber

#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>

#include <sys/types.h>
#include <unistd.h>

#include <mosquitto.h>

#define MQTT_HOSTNAME "localhost" 
#define MQTT_PORT 1883 
#define MQTT_TOPIC "/brook/#"

struct brook_obj {
 unsigned long long msgs;
 unsigned long long connected;
 unsigned long long disconnected;
 unsigned long long loged;
 unsigned long long subscribed;
 unsigned long long unsubscribed;
};

/*
 mosq the mosquitto instance making the callback.
 obj the user data provided in mosquitto_new
 rc the return code of the connection response, one of:
*/
static void connect_cb(struct mosquitto *mosq, void *obj, int rc)
{
 struct brook_obj *bobj = (struct brook_obj*) obj;
 bobj->connected++;
 printf("%s(), connect:%llu, msgs:%llu, loged:%llu, rc:%d\n",
  __FUNCTION__, bobj->connected, bobj->msgs, bobj->loged, rc);
}

static void log_cb(struct mosquitto *mosq, void *obj, int level, const char *str)
{
 struct brook_obj *bobj = (struct brook_obj*) obj;
 bobj->loged++;
 printf("%s(), connect:%llu, msgs:%llu, loged:%llu, level:%d,\n\tstr:%s\n",
  __FUNCTION__, bobj->connected, bobj->msgs, bobj->loged, level, str);
}

static void disconnect_cb(struct mosquitto *mosq, void *obj, int rc)
{
 struct brook_obj *bobj = (struct brook_obj*) obj;
 bobj->disconnected++;
 printf("%s(), connect:%llu, msgs:%llu, loged:%llu, disconnected:%llu\n",
  __FUNCTION__, bobj->connected, bobj->msgs, bobj->loged, bobj->disconnected);
}

static void message_cb(struct mosquitto *mosq, void *obj, const struct mosquitto_message *message)
{
 struct brook_obj *bobj = (struct brook_obj*) obj;
 bobj->msgs++;
 bool match = 0;
 static unsigned long long i = 0, j = 0;

 printf("%s(), connect:%llu, msgs:%llu, loged:%llu, disconnected:%llu\n",
  __FUNCTION__, bobj->connected, bobj->msgs, bobj->loged, bobj->disconnected);
 printf("%s(), got message mid:%d, '%.*s' for topic '%s'\n",
  __FUNCTION__, message->mid, message->payloadlen, (char*) message->payload, message->topic);

 mosquitto_topic_matches_sub(MQTT_TOPIC, message->topic, &match);
 if (match) {
  printf("got matched topic to %s\n", MQTT_TOPIC);
 }
}

static void unsubscribe_cb(struct mosquitto *mosq, void *obj, int mid)
{
 struct brook_obj *bobj = (struct brook_obj*) obj;
 bobj->unsubscribed++;
 printf("%s(), connect:%llu, msgs:%llu, loged:%llu, disconnected:%llu, unsubscribed:%llu\n",
  __FUNCTION__, bobj->connected, bobj->msgs, bobj->loged, bobj->disconnected,
  bobj->unsubscribed);
}

static void subscribe_cb(struct mosquitto *mosq, void *obj, int mid, int qos_count, const int *granted_qos)
{
 int i;
 struct brook_obj *bobj = (struct brook_obj*) obj;
 bobj->subscribed++;
 printf("%s(), mid:%d, qos_count:%d\n",
  __FUNCTION__, mid, qos_count);
 for (i = 0; i < qos_count; i++) {
  printf("\tQ[%d]:%d\n", i, granted_qos[i]);
 }
 printf("%s(), connect:%llu, msgs:%llu, loged:%llu, disconnected:%llu, subscribed:%llu\n",
  __FUNCTION__, bobj->connected, bobj->msgs, bobj->loged, bobj->disconnected,
  bobj->subscribed);
}

int main(int argc, char *argv[])
{
 char clientid[24];
 struct mosquitto *mosq;
 int rc = 0;
 struct brook_obj brook_obj = {};

 mosquitto_lib_init();

 memset(clientid, 0, sizeof(clientid));
 snprintf(clientid, sizeof(clientid) - 1, "cid-sub-%d", getpid());
 mosq = mosquitto_new(clientid, true, &brook_obj);
 if (!mosq) {
  fprintf (stderr, "new mosq failed\n");
  exit(-1);
 }

 rc = mosquitto_tls_opts_set(mosq, 1, "tlsv1", NULL);
 if (rc) {
  fprintf (stderr, "set tls opt failed %d\n", rc);
  exit(-1);
 }

 rc = mosquitto_tls_insecure_set(mosq, true);
 if (rc) {
  fprintf (stderr, "set insecure failed %d\n", rc);
  exit(-1);
 }

 rc = mosquitto_tls_set(mosq, "ca/client/ca.crt", NULL, "ca/client/client.crt", "ca/client/client.key", NULL);
 if (rc) {
  fprintf (stderr, "set tls failed %d\n", rc);
  exit(-1);
 }

 mosquitto_connect_callback_set(mosq, connect_cb);
 mosquitto_log_callback_set(mosq, log_cb);
 mosquitto_subscribe_callback_set(mosq, subscribe_cb);
 mosquitto_unsubscribe_callback_set(mosq, unsubscribe_cb);
 mosquitto_message_callback_set(mosq, message_cb);
 mosquitto_disconnect_callback_set(mosq, disconnect_cb);

 rc = mosquitto_connect(mosq, MQTT_HOSTNAME, MQTT_PORT, 60);
 if (rc) {
  fprintf(stderr, "mosquitto_connect failed. %d\n", rc);
  exit(-1);
 }

 mosquitto_subscribe(mosq, NULL, MQTT_TOPIC, 1);

 do {
  rc = mosquitto_loop(mosq, 3000, 1);
  fprintf(stdout, "mosquitto_loop %d, msgs%llu\n",
   rc, brook_obj.msgs);
 } while(rc == 0 && brook_obj.msgs < 5);

 mosquitto_disconnect(mosq);
 mosquitto_destroy(mosq);
 mosquitto_lib_cleanup();

 return rc;
}

2017年11月5日 星期日

MQTT


MQ Telemetry Transport (MQTT)網路文章已經很多,也很完善,大家可以參考本文章後面的reference,這裡快速帶過幾個重點:
MQTT是一個輕量級的基於broker的Publish/Subscribe messaging protocol,旨在實現開放,簡單,輕量級和易於實現。
協議的特點包括:
  • 提供一對多的訊息發布
  • 三種QoS
    "At most once" - message丟失或重複可能發生。
    "At least once" - 確保message到達,但可能會發生重複。
    "Exactly once" - 確保message只送達一次。

MQTT Architecture(Publish/Subscribe with broker)

MQTT有三種主要的組成元件,分別為Publisher、Subscriber以及Broker。 Publisher為訊息的來源,它會將訊息(Topic)發送給Broker,而Subscriber向Broker註冊,表示他們想要接收某Topic訊息;因此當有某個Publisher對Broker發送Topic訊息時,只要是有對此Topic註冊的Subscriber,都會收到此則訊息。

Topic wildcards

Topic為UTF-8編碼字串,最長長度為32,767字元,Topic支援階層式命名方式,如:"住家/客廳/溫度",階層的分隔符號為"/",所以前面這個Topic有三層架構,Topic可以透過萬用字元一次訂閱多個主題。但是這些萬用字元只能放在最後一層
"#"為Multi-level wildcard,可以包含零個以上的階層,如有人送出"a/b/c/d",那以下訂閱都可以被match "a/b/c/d","#","a/#","a/b/#","a/b/c/#","+/b/c/#",
"+"為Single-level wildcard,只能包含該層的Topic,如有人送出"a/b/c/d",那以下訂閱都可以被match “a/b/c/d","+/b/c/d","a/+/c/d","a/+/+/d","+/+/+/+",而以下這些事不能match的"a/b/c","b/+/c/d","+/+/+"

Deom the MQTT - mosquitto

請用apt-get install mosquitto mosquitto-clients安裝mosquitto套件
run the broker
brook@vista:~$ mosquitto -v
1511094611: mosquitto version 1.4.8 (build date Mon, 26 Jun 2017 09:31:02 +0100) starting
1511094611: Using default config.
1511094611: Opening ipv4 listen socket on port 1883.
1511094611: Opening ipv6 listen socket on port 1883.
1511094635: New connection from 127.0.0.1 on port 1883.
1511094635: New client connected from 127.0.0.1 as mosqsub/16408-jpr-Verit (c1, k60). 有人連上就會顯示
1511094635: Sending CONNACK to mosqsub/16408-jpr-Verit (0, 0)
1511094635: Received SUBSCRIBE from mosqsub/16408-jpr-Verit
1511094635:     /brook/L1 (QoS 0)
1511094635: mosqsub/16408-jpr-Verit 0 /brook/L1
1511094635: Sending SUBACK to mosqsub/16408-jpr-Verit
1511094659: New connection from 127.0.0.1 on port 1883.
1511094659: New client connected from 127.0.0.1 as mosqpub/16687-jpr-Verit (c1, k60).
1511094659: Sending CONNACK to mosqpub/16687-jpr-Verit (0, 0)
1511094659: Received PUBLISH from mosqpub/16687-jpr-Verit (d0, q0, r0, m0, '/brook/L1', ... (11 bytes))
1511094659: Sending PUBLISH to mosqsub/16408-jpr-Verit (d0, q0, r0, m0, '/brook/L1', ... (11 bytes))
1511094659: Received DISCONNECT from mosqpub/16687-jpr-Verit
1511094659: Client mosqpub/16687-jpr-Verit disconnected.

向Broker註冊topic "/brook/L1"
brook@vista:~$ mosquitto_sub -h localhost -t '/brook/L1'
test for L1有人向Broker推送/brook/L1訊息就會顯示

向Broker推送topic "/brook/L1"的訊息
brook@vista:~$ mosquitto_pub -h localhost -t '/brook/L1' -m 'test for L1'

mosquitto - Broker log notes

brook@vista:~$ mosquitto -v
1511094635: New client connected from 127.0.0.1 as mosqsub/16408-jpr-Verit (c1, k60). 

mosquitto-1.4.8/src/read_handle_server.c
521 if(context->username){
522   _mosquitto_log_printf(NULL, MOSQ_LOG_NOTICE, 
                           "New client connected from %s as %s (c%d, k%d, u'%s').",
                            context->address, client_id, clean_session, 
                            context->keepalive, context->username);
523 }else{
524   _mosquitto_log_printf(NULL, MOSQ_LOG_NOTICE,
                           "New client connected from %s as %s (c%d, k%d).", 
                           context->address, client_id, clean_session,
                           context->keepalive);
525 }

1511094635: Sending CONNACK to mosqsub/16408-jpr-Verit (0, 0)
1511094635: Received SUBSCRIBE from mosqsub/16408-jpr-Verit
1511094635:     /brook/L1 (QoS 0)

man mqtt
QUALITY OF SERVICE
  MQTT defines three levels of Quality of Service (QoS). The QoS defines how hard the 
  broker/client will try to ensure that a message is received. Messages may be sent 
  at any QoS level, and clients may attempt to subscribe to topics at any QoS level.
  This means that the client chooses the maximum QoS it will receive. For example, 
  if a message is published at QoS 2 and a client is subscribed with QoS 0, the 
  message will be delivered to that client with QoS 0. If a second client is also 
  subscribed to the same topic, but with QoS 2, then it will receive the same message 
  but with QoS 2. For a second example, if a client is subscribed with QoS 2 and a 
  message is published on QoS 0, the client will receive it on QoS 0.

  Higher levels of QoS are more reliable, but involve higher latency and have higher bandwidth requirements.
    o 0: The broker/client will deliver the message once, with no confirmation.
    o 1: The broker/client will deliver the message at least once, with confirmation required.
    o 2: The broker/client will deliver the message exactly once by using a four step handshake.

1511094635: mosqsub/16408-jpr-Verit 0 /brook/L1
1511094635: Sending SUBACK to mosqsub/16408-jpr-Verit
1511094659: New connection from 127.0.0.1 on port 1883.
1511094659: New client connected from 127.0.0.1 as mosqpub/16687-jpr-Verit (c1, k60).
1511094659: Sending CONNACK to mosqpub/16687-jpr-Verit (0, 0)
1511094659: Received PUBLISH from mosqpub/16687-jpr-Verit (d0, q0, r0, m0, '/brook/L1', ... (11 bytes))

mosquitto-1.4.8/src/read_handle.c
217 _mosquitto_log_printf(NULL, MOSQ_LOG_DEBUG, 
                          "Received PUBLISH from %s (d%d, q%d, r%d, m%d, '%s', ... (%ld bytes))",
                          context->id, dup, qos, retain, mid, topic, (long)payloadlen);

1511094659: Sending PUBLISH to mosqsub/16408-jpr-Verit (d0, q0, r0, m0, '/brook/L1', ... (11 bytes))

mosquitto-1.4.8/lib/send_mosq.c
156 _mosquitto_log_printf(NULL, MOSQ_LOG_DEBUG, 
                          "Sending PUBLISH to %s (d%d, q%d, r%d, m%d, '%s', ... (%ld bytes))",
                          mosq->id, dup, qos, retain, mid, mapped_topic, (long)payloadlen);

1511094659: Received DISCONNECT from mosqpub/16687-jpr-Verit
1511094659: Client mosqpub/16687-jpr-Verit disconnected.



2017年7月29日 星期六

A pattern for state machine


State machine是很常見的應用/Pattern,這章節會根據下面的圖來實作State machine pattern


enum state {
 STATE_INITIAL = 0,
 STATE_1,
 STATE_2,
 STATE_3,
 STATE_4,
};

enum event {
 E1 = 1,
 E2,
 E3,
 E4,
};
先定義State Machine狀態與Event



struct instance_data {
 enum event evt;
 int data;
};

struct instance {
 enum state cur_state;
 struct instance_data data;
};
定義一個struct來儲存"現在狀態",Event與data



typedef enum state state_func_t(struct instance_data *data);

state_func_t* const state_table[] = {
 [STATE_INITIAL] = in_init,
 [STATE_1] = in_state_1,
 [STATE_2] = in_state_2,
 [STATE_3] = in_state_3,
 [STATE_4] = in_state_4,
};

void run_state(struct instance *inst)
{
 inst->cur_state = state_table[inst->cur_state](&(inst->data));
};

建立一個Table,將每一個state對應的function填入,run_state()會根據目前的State處理該event/data,並回傳下一個狀態



enum state in_state_1(struct instance_data *data)
{
 printf("%s(#%d): got EVT:%d\n", __FUNCTION__, __LINE__, data->evt);
 switch (data->evt) {
 case E1:
  printf("change to S2\n");
  return do_s2();
 case E2:
  printf("change to S3\n");
  return do_s3();
 default:
  printf("keep the same STATE && do nothing\n");
  return STATE_1;
 }
}

enum state in_state_2(struct instance_data *data)
{
 printf("%s(#%d): got EVT:%d\n", __FUNCTION__, __LINE__, data->evt);
 switch (data->evt) {
 case E3:
  printf("change to S3\n");
  return do_s3();
 default:
  printf("keep the same STATE && do s2 again\n");
  return do_s2(); // do s2 again
 }
}

enum state in_state_3(struct instance_data *data)
{
 printf("%s(#%d): got EVT:%d\n", __FUNCTION__, __LINE__, data->evt);
 switch (data->evt) {
 case E2:
  printf("change to S4\n");
  return do_s4();
 default:
  printf("keep the same STATE && do nothing\n");
  return STATE_3;
 }
}

enum state in_state_4(struct instance_data *data)
{
 printf("%s(#%d): got EVT:%d\n", __FUNCTION__, __LINE__, data->evt);
 switch (data->evt) {
 case E1:
  printf("change to S2\n");
  return do_s2();
 case E3:
  printf("change to S1\n");
  return do_s1();
 default:
  printf("keep the same STATE && do again\n");
  return do_s4();
 }
}
定義每一個狀態中的行為



enum state do_s1(void)
{
 printf("%s(#%d)\n", __FUNCTION__, __LINE__);
 return STATE_1;
}

enum state do_s2(void)
{
 printf("%s(#%d)\n", __FUNCTION__, __LINE__);
 return STATE_2;
}

enum state do_s3(void)
{
 printf("%s(#%d)\n", __FUNCTION__, __LINE__);
 return STATE_3;
}

enum state do_s4(void)
{
 printf("%s(#%d)\n", __FUNCTION__, __LINE__);
 return STATE_4;
}

enum state in_init(struct instance_data *data)
{
 printf("%s(#%d): do some init. E:%d\n", __FUNCTION__, __LINE__, data->evt);
 printf("change to S1\n");
 return STATE_1;
}
定義進入每一個狀態要執行的動作



int main( void ) {
 int ch;
 struct instance inst = {STATE_INITIAL, 0};
 while ( 1 ) {
  run_state(&inst);
  // do other program logic, run other state machines, etc
  printf("MENU: E1/E2/E3/E4\n");
  while(((ch = getc(stdin)) == '\n') || (ch <= '0') || (ch > '4'));
  inst.data.evt = ch - '0';
 }
}
main function模擬State Machine收到不同event(1~4)


以下是收到event 1, 2, 3, 4的執行結果





2017年7月8日 星期六

How to send a html email with the bash command "mail"/“sendmail”?


有時我們會需要透過Linux發送一些report mail,這時候我們就可以用mail這個指令
brook@vista:~/kernel$ ls | txt2html| mail --debug-level=7 -s 'show kernel floder' rene3210@gmail.com
mail: sendmail (/usr/sbin/sendmailn
mail: Getting auth info for UID 1003
mail: source=system, name=brook, passwd=x, uid=1003, gid=1000, gecos=,,,, dir=/home/brook, shell=/bin/bash, mailbox=/var/mail/brook, quota=0, change_uid=1
mail: Getting auth info for UID 1003
mail: source=system, name=brook, passwd=x, uid=1003, gid=1000, gecos=,,,, dir=/home/brook, shell=/bin/bash, mailbox=/var/mail/brook, quota=0, change_uid=1
mail: mu_mailer_send_message(): using From: brook@vista
mail: Sending headers...
mail: Sending body...
mail: /usr/sbin/sendmail exited with: 0
mail:

上面這個指令會送出raw data,mail看到的也是顯示一些HTML內容,如
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta name="generator" content="HTML::TextToHTML v2.51"/>
</head>
<body>
<p>AndroidKernel.mk<br/>
<strong>COPYING</strong><br/>
<strong>CREDITS</strong><br/>
Documentation<br/>
Kbuild<br/>
Kconfig<br/>
<strong>MAINTAINERS</strong><br/>
Makefile<br/>
<strong>README</strong><br/>
<strong>REPORTING-BUGS</strong><br/>
android<br/>
arch<br/>
block<br/>
crypto<br/>
drivers<br/>
firmware<br/>
fs<br/>
include<br/>
init<br/>
ipc<br/>
kernel<br/>
lib<br/>
linaro<br/>
mm<br/>
net<br/>
samples<br/>
scripts<br/>
security<br/>
sound<br/>
tools<br/>
usr<br/>
virt</p>

</body>
</html>

如果我們要支援"Multipurpose Internet Mail Extensions (MIME)"只要在subject後面接上"\nContent-Type: text/html"即可
brook@vista:~/kernel$ ls | txt2html| mail --debug-level=7 -s "$(echo -e "show kernel floder\nContent-Type: text/html")" rene3210@gmail.com

也可以用"sendmail"指令來傳送mail
(
echo "From: ${from}";
echo "To: ${to}";
echo "Subject: ${subject}";
echo "Content-Type: text/html";
echo "MIME-Version: 1.0";
echo "";
echo "${message}";
) | sendmail -t

(message=`ls| txt2html`
echo "From: rene3210@gmail.com;"
echo "To: rene3210@gmail.com,rene3210@gov.tw;"
echo "Subject: Quota"
echo "Content-Type: text/html;"
echo "MIME-Version: 1.0;"
echo "${message}";
echo -e"\n\n"
) |sendmail -t


參考資料: How to send a html email with the bash command “sendmail”?



2017年4月30日 星期日

pkg-config


簡介

現在的電腦系統使用許多library package供使用者使用,但是在不同的platform使用這些library package是一件很困難的事,pkg-config收集相關的資訊並且統一的API供開發人員使用。 pkg-config利用".pc"檔的格式,記錄了一些library相關的資訊,提供對應的information給開發人員使用,以ubuntu為例,這些檔案被放置在/usr/lib/x86_64-linux-gnu/pkgconfig、/usr/lib/i386-linux-gnu/pkgconfig與/usr/lib/pkgconfig等目錄,以下為directfb的.pc
brook@vista:~$ cat /usr/lib/x86_64-linux-gnu/pkgconfig/directfb.pc
prefix=/usr
exec_prefix=${prefix}
libdir=${prefix}/lib/x86_64-linux-gnu
includedir=${prefix}/include

Name: DirectFB
Description: Graphics and windowing library for the Linux frame buffer device
Version: 1.2.10
Requires: fusion direct
Libs: -L${libdir} -ldirectfb -lpthread
Libs.private: -L${libdir} -ldl
Cflags: -D_REENTRANT -I${prefix}/include/directfb
brook@vista:~$ pkg-config --list-all
file_name_of_library_or_package          Name_in_pc - Description_in_pc
...
direct                         Direct - DirectFB base development library
...

PC檔內容

.pc包含了事先定義好的variable(key=value)與description(Name: description),description有以下資訊
Name: library的name。
Description: library的簡述。
URL: library的相關URL。
Version: library的版號資訊。
Requires: library所需要的library,可能會包含所需的版號資訊(=, <, >, <= or >=)。
Requires.private: 與 Requires相似,用於static link。
Conflicts: library可能與某個library衝突的資訊,可能會包含衝突的版號資訊(=, <, >, <= or >=)。
Cflags: 使用該library所需的compiler flags。
Libs: 使用該library所需的link flags。
Libs.private: 與Libs相似,用於static link。

pkg-config操作

基本語法為pkg-config [input parameters] [LIBRARIES...]相關input參數可以參考man page
brook@vista:~$ pkg-config --modversion --print-errors directfb
1.2.10
brook@vista:~$ pkg-config --cflags --print-errors directfb
-D_REENTRANT -I/usr/include/directfb
brook@vista:~$ pkg-config --libs --print-errors directfb
-ldirectfb -lpthread -lfusion -ldirect -lpthread
brook@vista:~$ pkg-config --libs --static --print-errors directfb
-ldirectfb -lpthread -ldl -lfusion -ldirect -lpthread -ldl
brook@vista:~$ pkg-config --print-requires --print-errors directfb
fusion
direct
brook@vista:~$ pkg-config --print-requires --print-errors "directfb > 1.3"
Requested 'directfb > 1.3' but version of DirectFB is 1.2.10
brook@vista:~$ pkg-config --exists --print-errors directfb
brook@vista:~$ echo $?
0
brook@vista:~$ pkg-config --exists --print-errors directfbxx
Package directfbxx was not found in the pkg-config search path.
Perhaps you should add the directory containing `directfbxx.pc'
to the PKG_CONFIG_PATH environment variable
No package 'directfbxx' found
brook@vista:~$ echo $?
1

pkg-config實際應用

With GCC
brook@vista:~$ pkg-config --cflags --libs directfb
-D_REENTRANT -I/usr/include/directfb -ldirectfb -lpthread -lfusion -ldirect -lpthread
brook@vista:~$ gcc `pkg-config --cflags --libs directfb` -o myapp myapp.c

With autoconf and automake
configure.ac:
PKG_CHECK_MODULES([DIRECTFB], [directfb])

Makefile.am:
myapp_CFLAGS = $(DIRECTFB_CFLAGS)
myapp_LDADD = $(DIRECTFB_LIBS)

    參考資料:
  1. 簡介 pkg-config 的功能與用法, http://yczhuang.blogspot.tw/2007/04/pkg-config.html
  2. Guide to pkg-config, https://people.freedesktop.org/~dbn/pkg-config-guide.html#writing
  3. pkg-config 使用及建立方法, http://jyhshin.pixnet.net/blog/post/26588033-pkg-config-%E4%BD%BF%E7%94%A8%E5%8F%8A%E5%BB%BA%E7%AB%8B%E6%96%B9%E6%B3%95



2016年12月4日 星期日

gmp-4.1.4 build error "automatic de-ANSI-fication support has been removed"


porting某些package時會出現"automatic de-ANSI-fication support has been removed",這是因為automake1.12之後,就不再支援該功能了,也就是不再支援ansi2knr這個選項了。 而且建議將AM_C_PROTOTYPES改成AC_C_PROTOTYPES的寫法,其範例如下
--- a/Makefile.am
+++ b/Makefile.am
@@ -20,7 +20,7 @@
# MA 02111-1307, USA.
-AUTOMAKE_OPTIONS = gnu no-dependencies $(top_builddir)/ansi2knr
+AUTOMAKE_OPTIONS = gnu no-dependencies

修改configure.in
--- a/configure.in
+++ b/configure.in
@@ -1501,7 +1501,9 @@ echo "      MPN_PATH=\"$path\""
# Automake ansi2knr support.
-AM_C_PROTOTYPES
+AC_C_PROTOTYPES
+AC_HEADER_STDC
+AC_CHECK_HEADERS("string.h")


    參考資料:
  1. https://autotools.io/forwardporting/automake.html




2016年10月9日 星期日

Install Node.js on Openembedded


基本上Node.js在Openembedded上的recipes都已經寫好了,只要clone下來,並且加入IMAGE_INSTALL列表即可
brook@vista:~/projects/poky$ git clone https://github.com/imyller/meta-nodejs.git
Cloning into 'meta-nodejs'...
remote: Counting objects: 1575, done.
remote: Compressing objects: 100% (14/14), done.
remote: Total 1575 (delta 9), reused 0 (delta 0), pack-reused 1561
Receiving objects: 100% (1575/1575), 241.91 KiB | 368.00 KiB/s, done.
Resolving deltas: 100% (846/846), done.
Checking connectivity... done.
brook@vista:~/projects/poky$ . oe-init-build-env
You had no conf/local.conf file. This configuration file has therefore been
created for you with some default values. You may wish to edit it to, for
example, select a different MACHINE (target hardware). See conf/local.conf
for more information as common configuration options are commented.

You had no conf/bblayers.conf file. This configuration file has therefore been
created for you with some default values. To add additional metadata layers
into your configuration please add entries to conf/bblayers.conf.

The Yocto Project has extensive documentation about OE including a reference
manual which can be found at:
    http://yoctoproject.org/documentation

For more information about OpenEmbedded see their website:
    http://www.openembedded.org/


### Shell environment set up for builds. ###

You can now run 'bitbake <target>'

Common targets are:
    core-image-minimal
    core-image-sato
    meta-toolchain
    meta-ide-support

You can also run generated qemu images with a command like 'runqemu qemux86'

brook@vista:~/projects/poky/build$ vim conf/bblayers.conf
...下段說明
brook@vista:~/projects/poky/build$ vim conf/local.conf
...下段說明
brook@vista:~/projects/poky/build$ bitbake core-image-minimal
WARNING: Host distribution "Ubuntu-16.04" has not been validated with this version of the build system; you may possibly experience unexpected failures. It is recommended that you use a tested distribution.
Parsing recipes: 100% |#############################################################################################| Time: 00:00:28
Parsing of 876 .bb files complete (0 cached, 876 parsed). 1316 targets, 49 skipped, 0 masked, 0 errors.
NOTE: Resolving any missing task queue dependencies

Build Configuration:
BB_VERSION        = "1.30.0"
BUILD_SYS         = "x86_64-linux"
NATIVELSBSTRING   = "Ubuntu-16.04"
TARGET_SYS        = "i586-poky-linux"
MACHINE           = "qemux86"
DISTRO            = "poky"
DISTRO_VERSION    = "2.1.1"
TUNE_FEATURES     = "m32 i586"
TARGET_FPU        = ""
meta
meta-poky
meta-yocto-bsp    = "krogoth:8c69f7d56cbd496aa01ba0738675a170826a536b"
meta-nodejs       = "master:848b0defe8eba6e7ffa97b66e4316c17c92be9d4"
...
brook@vista:~/projects/poky/build$ ./tmp/sysroots/x86_64-linux/usr/bin/qemu-system-i386 -nographic -kernel ./tmp/deploy/images/qemux86/bzImage-qemux86.bin -cpu qemu32 -drive file=./tmp/deploy/images/qemux86/core-image-minimal-qemux86.ext4,if=virtio,format=raw -show-cursor -usb -usbdevice tablet -vga vmware -no-reboot -m 256 -append "vga=0 uvesafb.mode_option=640x480-32 root=/dev/vda rw mem=256M ip=192.168.7.2::192.168.7.1:255.255.255.0 oprofile.timer=1 rootfstype=ext4 "

Poky (Yocto Project Reference Distro) 2.1.1 qemux86 /dev/ttyS0

qemux86 login: root
root@qemux86:~# uname -a
Linux qemux86 4.4.11-yocto-standard #1 SMP PREEMPT Sun Oct 9 19:29:24 CST 2016 i686 GNU/Linux
root@qemux86:~# echo 'console.log("hello world");' > node.js
root@qemux86:~# node node.js
hello world


conf/bblayers.conf修改的內容
BBLAYERS ?= " \
  /home/brook/projects/poky/meta \
  /home/brook/projects/poky/meta-poky \
  /home/brook/projects/poky/meta-yocto-bsp \
  ${TOPDIR}/../meta-nodejs \
  "


conf/local.conf修改的內容
# This sets the default machine to be qemux86 if no other machine is selected:
MACHINE ??= "qemux86"
CORE_IMAGE_EXTRA_INSTALL += "nodejs"


    參考資料:
  1. meta-nodejs, Node.js的openembedded recipes
  2. JavaScript for IoT: Blinking LED on Raspberry Pi with Node.js , Node.js的demo影片。
  3. 「Node.js & IoT: Zero to One」 是一本 Node.js 的入門電子書,內容定位為基礎教學,目標是介紹 Node.js 以及 Node.js + IoT 相關技術主題,每個主題都從基本觀念(Zero)開始,介紹到能撰寫簡單的程式為止(One)。不過目前只有介紹JS的基本語法。
  4. Addons教你如何擴充JS,用C/C++寫一個Node.js module。




2016年10月8日 星期六

Using openembedded to build QEMU


這篇文章主要來說明如何使用Openembedded/Krogoth來build出QEMU for ARM。
步驟大概就是:
先下載yp-krogoth之後,source目錄下的oe-init-build-env,接著再編輯conf/local.conf,最後就是執行bitbake core-image-minimal。
我的Krogoth最後一個commit ID是e93596f。
brook@vista:~/projects$ git clone -b krogoth git://git.yoctoproject.org/poky.git yp-krogoth
Cloning into 'yp-krogoth'...
remote: Counting objects: 339411, done.
remote: Compressing objects: 100% (82247/82247), done.
remote: Total 339411 (delta 251486), reused 338934 (delta 251035)
Receiving objects: 100% (339411/339411), 127.65 MiB | 4.49 MiB/s, done.
Resolving deltas: 100% (251486/251486), done.
Checking connectivity... done.
brook@vista:~/projects$ cd yp-krogoth/
brook@vista:~/projects/yp-krogoth$ git log --oneline -1
e93596f binutils: fix AR issue when opkg is unpacking IPKs containing empty entries
brook@vista:~/projects/yp-krogoth$ . oe-init-build-env
You had no conf/local.conf file. This configuration file has therefore been
created for you with some default values. You may wish to edit it to, for
example, select a different MACHINE (target hardware). See conf/local.conf
for more information as common configuration options are commented.

You had no conf/bblayers.conf file. This configuration file has therefore been
created for you with some default values. To add additional metadata layers
into your configuration please add entries to conf/bblayers.conf.

The Yocto Project has extensive documentation about OE including a reference
manual which can be found at:
    http://yoctoproject.org/documentation

For more information about OpenEmbedded see their website:
    http://www.openembedded.org/


### Shell environment set up for builds. ###

You can now run 'bitbake <target>'

Common targets are:
    core-image-minimal
    core-image-sato
    meta-toolchain
    meta-ide-support

You can also run generated qemu images with a command like 'runqemu qemux86'
brook@vista:~/projects/yp-krogoth/build$ vim conf/local.conf
...內容請看一段
brook@vista:~/projects/yp-krogoth/build$ bitbake core-image-minimal
WARNING: Host distribution "Ubuntu-16.04" has not been validated with this version of the build system; you may possibly experience unexpected failures. It is recommended that you use a tested distribution.
Parsing recipes...done.
Parsing of 871 .bb files complete (0 cached, 871 parsed). 1301 targets, 66 skipped, 0 masked, 0 errors.
NOTE: Resolving any missing task queue dependencies

Build Configuration:
BB_VERSION        = "1.30.0"
BUILD_SYS         = "x86_64-linux"
NATIVELSBSTRING   = "Ubuntu-16.04"
TARGET_SYS        = "arm-poky-linux-gnueabi"
MACHINE           = "qemuarm"
DISTRO            = "poky"
DISTRO_VERSION    = "2.1.1"
TUNE_FEATURES     = "arm armv5 thumb dsp"
TARGET_FPU        = "soft"
meta
meta-poky
meta-yocto-bsp    = "krogoth:e93596fe74927e2e2f4dd7f671994ccb9744cff8"

NOTE: Fetching uninative binary shim from http://downloads.yoctoproject.org/releases/uninative/1.0.1/x86_64-nativesdk-libc.tar.bz2;sha256sum=acf1e44a0ac2e855e81da6426197d36358bf7b4e88e552ef933128498c8910f8
NOTE: Preparing RunQueue
NOTE: Executing SetScene Tasks
...略
brook@vista:~/projects/yp-krogoth/build$ ./tmp/sysroots/x86_64-linux/usr/bin/qemu-system-arm -kernel ./tmp/deploy/images/qemuarm/zImage-qemuarm.bin -M versatilepb -drive file=/home/jpr/projects/yp-krogoth/build/tmp/deploy/images/qemuarm/core-image-minimal-qemuarm.ext4,if=virtio,format=raw -no-reboot -show-cursor -usb -usbdevice tablet -m 128 -nographic -append 'root=/dev/vda rw console=ttyAMA0,115200 console=tty ip=192.168.7.2::192.168.7.1:255.255.255.0 mem=128M highres=off rootfstype=ext4 ' 
audio: Could not init `oss' audio driver
ALSA lib ../../alsa-lib-1.1.0/src/confmisc.c:768:(parse_card) cannot find card '0'
...略
qemuarm login: root
root@qemuarm:~# uname -a
Linux qemuarm 4.4.11-yocto-standard #1 PREEMPT Sun Oct 9 11:36:44 CST 2016 armv5tejl GNU/Linux


conf/local.conf相關修改
change MACHINE for "qemux86" to "qemuarm"
MACHINE ?= "qemuarm"
#MACHINE ?= "qemuarm64"
#MACHINE ?= "qemumips"
#MACHINE ?= "qemumips64"
#MACHINE ?= "qemuppc"
#MACHINE ?= "qemux86"
#MACHINE ?= "qemux86-64"
#
# There are also the following hardware board target machines included for
# demonstration purposes:
#
#MACHINE ?= "beaglebone"
#MACHINE ?= "genericx86"
#MACHINE ?= "genericx86-64"
#MACHINE ?= "mpc8315e-rdb"
#MACHINE ?= "edgerouter"
#
# This sets the default machine to be qemux86 if no other machine is selected:
#MACHINE ??= "qemux86"
...
commout out these 2 lines
#PACKAGECONFIG_append_pn-qemu-native = " sdl"
#PACKAGECONFIG_append_pn-nativesdk-qemu = " sdl"



    參考資料:
  1. Yocto Project Quick Start
  2. Yocto Project Build System/Download




2016年10月1日 星期六

QC的partition table大小設定與實際大小對應關係


這裡記錄一下QC的9x40 partition table configure file(common/build/partition_nand.xml)size與實際的大小怎麼換算。 大概就兩類tag,一個tag是size_blks,以eraseblock為單位。另一個是tag是size_kb,所以實際大小還要轉成eraseblock。 如:
/ # cat /proc/mtd
dev:    size   erasesize  name
mtd0: 00280000 00040000 "sbl"
mtd1: 00280000 00040000 "mibib"
mtd2: 00c00000 00040000 "efs2"
mtd3: 00140000 00040000 "tz"
mtd4: 00100000 00040000 "rpm"
mtd5: 00140000 00040000 "aboot"
...

partition table configure file內容
<partition>
    <name length="16" type="string">0:SBL</name>
    <size_blks length="4">0x8</size_blks>
    <pad_blks length="4">0x2</pad_blks>
    ...
</partition>
...
<partition>
    <name length="16" type="string">0:EFS2</name>
    <size_kb length="4">11264</size_kb>
    <pad_kb length="4">1024</pad_kb>
    ....
</partition>
SBL的設定為0x8+0x2個eraseblock,所以/proc/mtd大小為 (8 + 2) * 256 * 1024 = 0x280000。 EFS2的設定為11264+1024 KB,所以/proc/mtd大小為ceiling(11264 / 256)+ceiling(1024 / 256) = 48個eraseblock,所以/proc/mtd大小為48 * 256 * 1024 = 0xC0000。 eraseblock就是上面的erasesize,0x40000 / 1024 = 256kb。


2016年9月17日 星期六

build 32bit app on 64bit Machine -- /usr/bin/ld: skipping incompatible


我將Ubuntu從14.04換到16.04後,系統好像移除了一些package,導致build 32bit的程式會failed。 安裝lib32gcc-4.7-dev之後就解了。
brook@vista:~$ uname -a
Linux vista 4.4.0-36-generic #55-Ubuntu SMP Thu Aug 11 18:01:55 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
brook@vista:~$ cat /etc/issue
Ubuntu 16.04.1 LTS \n \l
brook@vista:~$ gcc -m32 x.c
/usr/bin/ld: skipping incompatible /usr/lib/gcc/x86_64-linux-gnu/4.7/libgcc.a when searching for -lgcc
/usr/bin/ld: cannot find -lgcc
/usr/bin/ld: skipping incompatible /usr/lib/gcc/x86_64-linux-gnu/4.7/libgcc_s.so when searching for -lgcc_s
/usr/bin/ld: cannot find -lgcc_s
collect2: error: ld returned 1 exit status
brook@vista:~$ gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/4.7/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu/Linaro 4.7.4-3ubuntu12' --with-bugurl=file:///usr/share/doc/gcc-4.7/README.Bugs --enable-languages=c,c++,go,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.7 --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.7 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-gnu-unique-object --disable-libmudflap --enable-plugin --with-system-zlib --enable-objc-gc --with-cloog --enable-cloog-backend=ppl --disable-cloog-version-check --disable-ppl-version-check --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 4.7.4 (Ubuntu/Linaro 4.7.4-3ubuntu12)
brook@vista:~$ sudo apt-get install lib32gcc-4.7-dev





2016年4月16日 星期六

修改MAC的hostname


在中文版的 Mac OS裡,系統會自動依照安裝時給的中文姓名翻譯成英文作為主機名稱。對我來說因為常常會用shell底下操作,就超想改hostname,方法如下:
直接編輯"系統偏好設定"/"共享"/"電腦名稱"即可,後面的".local"不用理他。


打開終端機之後就可以看到hostname修改好了。


    參考資料:
  1. 更改 Mac 主機名稱




2016年2月21日 星期日

Linux Kernel(16.1)- Network Device Driver, simple snull.


這一篇藉由LDD(Linux Device Drivers)中的SNULL來了解最基本的Network Device Driver的架構,本章的sample code比原本的SNULL更為簡化,但是Network Topology是相同的,讓interface sn0/sn1可以透過遠方虛擬的remote0/remote1彼此溝通。

最基本的Network Device Driver的寫法就是allocate network device, "struct net_device"並且賦予hook function, "struct net_device_ops",然後將該network device註冊到kernel中,Kernel就可以調用該Network device,最基本的net_device_ops包含
  • ndo_open() and ndo_validate_addr() are called, when the NIC is bring up.
  • ndo_stop() is called, when the NIC is shut down.
  • ndo_start_xmit() is called, when a packet is sent from the NIC.
  • ndo_change_mtu() is called, when the MTU of the NIC is changed.
  • ndo_set_mac_address() is called, when the MAC address of the NIC is changed.
以下是demo code的net_device_ops部分
static const struct net_device_ops nic_netdev_ops = {
    /* Kernel calls ndo_open() and ndo_validate_addr()
     * when you bring up the NIC
     */
    .ndo_open               = nic_open,
    .ndo_validate_addr      = nic_validate_addr,

    /* when you shut down the NIC, kernel call the .ndo_stop() */
    .ndo_stop               = nic_close,

    /* Kernel calls ndo_start_xmit() when it wants to 
     *   transmit a packet. 
     */
    .ndo_start_xmit         = nic_start_xmit,

    /* ndo_change_mtu() is called, when you change MTU */
    .ndo_change_mtu         = nic_change_mtu,

    /* ndo_set_mac_address() is called,
     *   when you change the MAC addr
     */
    .ndo_set_mac_address    = nic_set_mac_addr,
};


我們是模擬ethernet,而ethernet有一些hook function可以用,如下
  • ndo_validate_addr() -> eth_validate_addr().
  • ndo_change_mtu() -> eth_change_mtu().
  • ndo_set_mac_address() -> eth_mac_addr().
在demo code中,ndo_validate_addr()/ndo_change_mtu()/ndo_set_mac_address()我都是將其轉成ethernet的default hook function,我沒直接掛,是因為我想印出訊息來看
static int nic_validate_addr(struct net_device *netdev)
{
    struct nic_priv *priv = netdev_priv(netdev);
    netif_info(priv, drv, netdev, "%s(#%d), priv:%p\n",
                __func__, __LINE__, priv);
    return eth_validate_addr(netdev);
}

static int nic_change_mtu(struct net_device *netdev, int new_mtu)
{
    struct nic_priv *priv = netdev_priv(netdev);
    netif_info(priv, drv, netdev, "%s(#%d), priv:%p\n",
                __func__, __LINE__, priv);
    return eth_change_mtu(netdev, new_mtu);
}

static int nic_set_mac_addr(struct net_device *netdev, void *addr)
{
    struct nic_priv *priv = netdev_priv(netdev);
    netif_info(priv, drv, netdev, "%s(#%d), priv:%p\n",
                __func__, __LINE__, priv);
    return eth_mac_addr(netdev, addr);
}

另外幾個比較重要的function是netif_start_queue()/netif_stop_queue()
  • netif_start_queue()是通知上層,可以將資料送到該網卡,通常放在ndo_open()裡面
  • netif_stop_queue()是通知上層,停止將資料送到該網卡,通常放在ndo_stop()裡面


由於我們沒有真的remote0/remote1可以回應,所以必須設定flag/IFF_NOARP在sn0跟sn1,並且自己要處理L2的header,所以必須在額外掛上"struct header_ops"。

以下為完整的driver,主要code都是印訊息觀察driver的call flow
/* reference ldd3, snull.c */
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>

/* for in_device, in_ifaddr */
#include <linux/inetdevice.h>

MODULE_AUTHOR("Brook");
MODULE_DESCRIPTION("Kernel module for demo");
MODULE_LICENSE("GPL");

#define MAX_ETH_FRAME_SIZE   1792
struct nic_priv {
    /* you can use array to queue more packet */
    unsigned char *tx_buf;
    unsigned int  tx_len;
    u32           msg_enable;
};

static struct net_device *nic_dev[2];
/* netif msg type, defined in netdevice.h
    NETIF_MSG_DRV           = 0x0001,
    NETIF_MSG_PROBE         = 0x0002,
    NETIF_MSG_LINK          = 0x0004,
    NETIF_MSG_TIMER         = 0x0008,
    NETIF_MSG_IFDOWN        = 0x0010,
    NETIF_MSG_IFUP          = 0x0020,
    NETIF_MSG_RX_ERR        = 0x0040,
    NETIF_MSG_TX_ERR        = 0x0080,
    NETIF_MSG_TX_QUEUED     = 0x0100,
    NETIF_MSG_INTR          = 0x0200,
    NETIF_MSG_TX_DONE       = 0x0400,
    NETIF_MSG_RX_STATUS     = 0x0800,
    NETIF_MSG_PKTDATA       = 0x1000,
    NETIF_MSG_HW            = 0x2000,
    NETIF_MSG_WOL           = 0x4000,
*/
#define DEF_MSG_ENABLE 0xffff

static void dump(unsigned char *buf)
{
    unsigned char *p, sbuf[2*(sizeof(struct ethhdr) + sizeof(struct iphdr))];
    int i;
    p = sbuf;

    for(i = 0; i < sizeof(struct ethhdr); i++) {
        p += sprintf(p, "%02X ", buf[i]);
    }
    printk("eth %s\n", sbuf);

    p = sbuf;
    for(i = 0; i < sizeof(struct iphdr); i++) {
        p += sprintf(p, "%02X ", buf[sizeof(struct ethhdr) + i]);
    }
    printk("iph %s\n", sbuf);

    p = sbuf;
    for(i = 0; i < 4; i++) {
        p += sprintf(p, "%02X ", buf[sizeof(struct ethhdr) + sizeof(struct iphdr) + i]);
    }
    printk("payload %s\n", sbuf);
}


static void
nic_rx(struct net_device *netdev, int len, unsigned char *buf)
{
    struct sk_buff *skb;
    struct nic_priv *priv = netdev_priv(netdev);

    netif_info(priv, hw, netdev, "%s(#%d), rx:%d\n",
                __func__, __LINE__, len);
    /*
     * The packet has been retrieved from the transmission
     * medium. Build an skb around it, so upper layers can handle it
     */
    skb = dev_alloc_skb(len + 2);
    if (!skb) {
        netif_err(priv, rx_err, netdev,
                  "%s(#%d), rx: low on mem - packet dropped\n",
                  __func__, __LINE__);
        netdev->stats.rx_dropped++;
        return;
    }
    skb_reserve(skb, 2); /* align IP on 16B boundary */
    memcpy(skb_put(skb, len), buf, len);

    /* Write metadata, and then pass to the receive level */
    skb->dev = netdev;
    skb->protocol = eth_type_trans(skb, netdev);
    skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */
    netdev->stats.rx_packets++;
    netdev->stats.rx_bytes += len;
    netif_rx(skb);
}

static int nic_open(struct net_device *netdev)
{
    struct nic_priv *priv = netdev_priv(netdev);
    netif_info(priv, ifup, netdev, "%s(#%d), priv:%p\n",
                __func__, __LINE__, priv);
    /* may be using DMA */
    priv->tx_buf = kmalloc(MAX_ETH_FRAME_SIZE, GFP_KERNEL);
    if (priv->tx_buf == NULL) {
        netif_info(priv, ifup, netdev, "%s(#%d), cannot alloc tx buf\n",
                    __func__, __LINE__);
        return -ENOMEM;
    }
    netif_start_queue(netdev);
    return 0;
}

static int nic_close(struct net_device *netdev)
{
    struct nic_priv *priv = netdev_priv(netdev);
    netif_info(priv, ifdown, netdev, "%s(#%d), priv:%p\n",
                __func__, __LINE__, priv);
    netif_stop_queue(netdev);
    return 0;
}

static void nic_hw_xmit(struct net_device *netdev)
{
    struct nic_priv *priv = netdev_priv(netdev);
    struct iphdr *iph;
    u32 *saddr, *daddr;
    struct in_device* in_dev;
    struct in_ifaddr* if_info;

    if (priv->tx_len < sizeof(struct ethhdr) + sizeof(struct iphdr)) {
        netif_info(priv, hw, netdev, "%s(#%d), too short\n",
                   __func__, __LINE__);
        return;
    }
    dump(priv->tx_buf);
    iph = (struct iphdr *)(priv->tx_buf + sizeof(struct ethhdr));
    saddr = &iph->saddr;
    daddr = &iph->daddr;

    netif_info(priv, hw, netdev, "%s(#%d), orig, src:%pI4, dst:%pI4, len:%d\n",
                __func__, __LINE__, saddr, daddr, priv->tx_len);

    in_dev = nic_dev[(netdev == nic_dev[0] ? 1 : 0)]->ip_ptr;
    if (in_dev) {
        if_info = in_dev->ifa_list;
        for (if_info = in_dev->ifa_list; if_info; if_info=if_info->ifa_next) {
#if 0
            printk("label:%s, address=%pI4\n",
               if_info->ifa_label, &if_info->ifa_address);
#endif
            *saddr = *daddr = if_info->ifa_address;
            ((u8 *)saddr)[3]++;
            netif_info(priv, hw, netdev, "%s(#%d), new, src:%pI4, dst:%pI4\n",
                        __func__, __LINE__, saddr, daddr);
            break;
        }
        if (!if_info) {
            /* drop packet */
            netdev->stats.tx_dropped++;
            netif_info(priv, hw, netdev, "%s(#%d), drop packet\n",
                        __func__, __LINE__);
            return;
        }
    }

    iph->check = 0;         /* and rebuild the checksum (ip needs it) */
    iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl);

    netdev->stats.tx_packets++;
    netdev->stats.tx_bytes += priv->tx_len;

    nic_rx(nic_dev[(netdev == nic_dev[0] ? 1 : 0)], priv->tx_len, priv->tx_buf);
}

static netdev_tx_t nic_start_xmit(struct sk_buff *skb,
                                  struct net_device *netdev)
{
    struct nic_priv *priv = netdev_priv(netdev);
    netif_info(priv, drv, netdev, "%s(#%d), orig, src:%pI4, dst:%pI4\n",
                __func__, __LINE__, &(ip_hdr(skb)->saddr), &(ip_hdr(skb)->daddr));
    priv->tx_len = skb->len;
    if (likely(priv->tx_len < MAX_ETH_FRAME_SIZE)) {
        if (priv->tx_len < ETH_ZLEN) {
            memset(priv->tx_buf, 0, ETH_ZLEN);
            priv->tx_len = ETH_ZLEN;
        }
        skb_copy_and_csum_dev(skb, priv->tx_buf);
        dev_kfree_skb_any(skb);
    } else {
        dev_kfree_skb_any(skb);
        netdev->stats.tx_dropped++;
        return NETDEV_TX_OK;
    }

    nic_hw_xmit(netdev);
    return NETDEV_TX_OK;
}

static int nic_validate_addr(struct net_device *netdev)
{
    struct nic_priv *priv = netdev_priv(netdev);
    netif_info(priv, drv, netdev, "%s(#%d), priv:%p\n",
                __func__, __LINE__, priv);
    return eth_validate_addr(netdev);
}

static int nic_change_mtu(struct net_device *netdev, int new_mtu)
{
    struct nic_priv *priv = netdev_priv(netdev);
    netif_info(priv, drv, netdev, "%s(#%d), priv:%p\n",
                __func__, __LINE__, priv);
    return eth_change_mtu(netdev, new_mtu);
}

static int nic_set_mac_addr(struct net_device *netdev, void *addr)
{
    struct nic_priv *priv = netdev_priv(netdev);
    netif_info(priv, drv, netdev, "%s(#%d), priv:%p\n",
                __func__, __LINE__, priv);
    return eth_mac_addr(netdev, addr);
}

/*
 * This function is called to fill up an eth header, since arp is not
 * available on the interface
 */
int snull_header(struct sk_buff *skb, struct net_device *netdev,
                unsigned short type, const void *daddr, const void *saddr,
                unsigned len)
{
    struct nic_priv *priv = netdev_priv(netdev);
    struct ethhdr *eth = (struct ethhdr *)skb_push(skb, ETH_HLEN);
    struct net_device *dst_netdev;

    netif_info(priv, drv, netdev, "%s(#%d)\n",
                __func__, __LINE__);
    dst_netdev = nic_dev[(netdev == nic_dev[0] ? 1 : 0)];
    eth->h_proto = htons(type);
    memcpy(eth->h_source, saddr ? saddr : netdev->dev_addr, netdev->addr_len);
    memcpy(eth->h_dest, dst_netdev->dev_addr, dst_netdev->addr_len);
    return (netdev->hard_header_len);
}

static const struct header_ops snull_header_ops = {
        .create  = snull_header,
};

static const struct net_device_ops nic_netdev_ops = {
    /* Kernel calls ndo_open() and ndo_validate_addr()
     * when you bring up the NIC
     */
    .ndo_open               = nic_open,
    .ndo_validate_addr      = nic_validate_addr,

    /* when you shut down the NIC, kernel call the .ndo_stop() */
    .ndo_stop               = nic_close,

    /* Kernel calls ndo_start_xmit() when it wants to 
     *   transmit a packet. 
     */
    .ndo_start_xmit         = nic_start_xmit,

    /* ndo_change_mtu() is called, when you change MTU */
    .ndo_change_mtu         = nic_change_mtu,

    /* ndo_set_mac_address() is called,
     *   when you change the MAC addr
     */
    .ndo_set_mac_address    = nic_set_mac_addr,
};

static struct net_device* nic_alloc_netdev(void)
{
    struct net_device *netdev;

    netdev = alloc_etherdev(sizeof(struct nic_priv));
    if (!netdev) {
        pr_err("%s(#%d): alloc dev failed",
               __func__, __LINE__);
        return NULL;
    }
    eth_hw_addr_random(netdev);
    netdev->netdev_ops = &nic_netdev_ops;

    /* keep the default flags, just add NOARP */
    netdev->flags |= IFF_NOARP;

    /* There are no explicit users, so this is 
     *     now equivalent to NETIF_F_HW_CSUM. */
    netdev->features |= NETIF_F_HW_CSUM;

    netdev->header_ops = &snull_header_ops;

    return netdev;
}

static int __init brook_init(void)
{
    int ret;
    struct nic_priv *priv;

    nic_dev[0] = nic_alloc_netdev();
    if (!nic_dev[0]) {
        pr_err("%s(#%d): alloc netdev[0] failed", __func__, __LINE__);
        return -ENOMEM;
    }

    nic_dev[1] = nic_alloc_netdev();
    if (!nic_dev[1]) {
        pr_err("%s(#%d): alloc netdev[1] failed", __func__, __LINE__);
        ret = -ENOMEM;
        goto alloc_2nd_failed;
    }

    ret = register_netdev(nic_dev[0]);
    if (ret) {
        pr_err("%s(#%d): reg net driver failed. ret:%d",
               __func__, __LINE__, ret);
        goto reg1_failed;
    }

    ret = register_netdev(nic_dev[1]);
    if (ret) {
        pr_err("%s(#%d): reg net driver failed. ret:%d",
               __func__, __LINE__, ret);
        goto reg2_failed;
    }

    priv = netdev_priv(nic_dev[0]);
    priv->msg_enable = DEF_MSG_ENABLE;
    priv = netdev_priv(nic_dev[1]);
    priv->msg_enable = DEF_MSG_ENABLE;
    return 0;

reg2_failed:
    unregister_netdev(nic_dev[0]);
reg1_failed:
    free_netdev(nic_dev[1]);
alloc_2nd_failed:
    free_netdev(nic_dev[0]);
    return ret;
}
module_init(brook_init);

static void __exit brook_exit(void)
{
    int i;
    pr_info("%s(#%d): remove module", __func__, __LINE__);
    for (i = 0; i < ARRAY_SIZE(nic_dev); i++) {
        unregister_netdev(nic_dev[i]);
        free_netdev(nic_dev[i]);
    }
}
module_exit(brook_exit);





參考資料:
  1. Linux Device Drivers, Third Edition, Chapter 17: Network Drivers, https://lwn.net/Kernel/LDD3/,
  2. Linux Networking and Network Devices APIs, https://www.kernel.org/doc/htmldocs/networking/index.html




2016年1月3日 星期日

OpenEmbedded - Writing a recipes/package inherit cmake


基本上inherit cmake跟inherit autotools是差不多的,其實cmake class裡面也是有inherit autotool,比較大的差異是,source file在cmake下是放在${PN}目錄下,即package name下,檔案結構如下:
brook@vista:~/oe-core$ tree meta/recipes-support/brook-cmake
meta/recipes-support/brook-cmake
|-- brook-cmake
|   `-- brook-cmake.patch
`-- brook-cmake_1.0.bb
1 directory, 2 files


brook-cmake.patch就是放置CMakeLists.txt、main.c跟LICENSE,其內容如下:

CMakeLists.txt

cmake_minimum_required (VERSION 2.6)
project(brook-cmake-demo)
add_executable(brook-cmake main.c)
install(TARGETS brook-cmake DESTINATION bin)


main.c

#include <stdio.h>

int main(int argc, char *argv[])
{
    printf("hello, cmake\n");
    return 0;
}


brook-cmake_1.0.bb

DESCRIPTION = "Brook CMake Project"
LICENSE = "GPLv2"
LIC_FILES_CHKSUM = "file://LICENSE;md5=5ff2bd2dd80c7cc542a6b8de3ee4ff87"

SRC_URI = "file://brook-cmake.patch \
"

inherit cmake

EXTRA_OEMAKE = "-Wall"


下面這裡是多個版本的寫法

其實寫法都一樣,只是patch file的目錄要包含版本號,檔案結構如下:
brook@vista:~/oe-core/meta/recipes-support$ tree brook-cmake
brook-cmake
|-- brook-cmake-1.0
|   `-- brook-cmake.patch
|-- brook-cmake-2.0
|   `-- brook-cmake.patch
|-- brook-cmake_1.0.bb
`-- brook-cmake_2.0.bb


BB檔與patch的內容跟上面都是一樣的
brook@vista:~/oe-core$ cat meta/recipes-support/brook-cmake/brook-cmake_1.0.bb
DESCRIPTION = "Brook CMake Project"
LICENSE = "GPLv2"
LIC_FILES_CHKSUM = "file://LICENSE;md5=7e4bf6f59b6da8e57ed5425a10a7310b"

SRC_URI = "file://brook-cmake.patch \
"

inherit cmake

BBCLASSEXTEND = "native"

EXTRA_OEMAKE = "-Wall"

brook@vista:~/oe-core$ cat meta/recipes-support/brook-cmake/brook-cmake_2.0.bb
DESCRIPTION = "Brook CMake Project"
LICENSE = "GPLv2"
LIC_FILES_CHKSUM = "file://LICENSE;md5=7e4bf6f59b6da8e57ed5425a10a7310b"

SRC_URI = "file://brook-cmake.patch \
"

inherit cmake

BBCLASSEXTEND = "native"

EXTRA_OEMAKE = "-Wall"

brook@vista:~/oe-core$ cat meta/recipes-support/brook-cmake/brook-cmake-1.0/brook-cmake.patch
From 037c40faba39a11e54f282c3eab5e47a4cf1c7ca Mon Sep 17 00:00:00 2001
From: Brook <rene3210 at gmail.com>
Date: Tue, 27 Sep 2016 20:42:11 +0800
Subject: [PATCH] brook oe demo code

---
 CMakeLists.txt | 5 +++++
 LICENSE        | 1 +
 main.c         | 8 ++++++++
 3 files changed, 14 insertions(+)
 create mode 100644 CMakeLists.txt
 create mode 100644 LICENSE
 create mode 100644 main.c

diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..ee4d6d9
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,5 @@
+cmake_minimum_required (VERSION 2.6)
+project(brook-cmake-demo)
+add_executable(brook-cmake main.c)
+install(TARGETS brook-cmake DESTINATION bin)
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..0664320
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1 @@
+"GPLv2"
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..d089aad
--- /dev/null
+++ b/main.c
@@ -0,0 +1,8 @@
+#include 
+
+int main(int argc, char *argv[])
+{
+    printf("hello, cmake 1.0\n");
+    return 0;
+}
+
--
2.7.4

brook@vista:~/oe-core$ cat meta/recipes-support/brook-cmake/brook-cmake-2.0/brook-cmake.patch
From 037c40faba39a11e54f282c3eab5e47a4cf1c7ca Mon Sep 17 00:00:00 2001
From: Brook <rene3210 at gmail.com>
Date: Tue, 27 Sep 2016 20:42:11 +0800
Subject: [PATCH] brook oe demo code

---
 CMakeLists.txt | 5 +++++
 LICENSE        | 1 +
 main.c         | 8 ++++++++
 3 files changed, 14 insertions(+)
 create mode 100644 CMakeLists.txt
 create mode 100644 LICENSE
 create mode 100644 main.c

diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..ee4d6d9
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,5 @@
+cmake_minimum_required (VERSION 2.6)
+project(brook-cmake-demo)
+add_executable(brook-cmake main.c)
+install(TARGETS brook-cmake DESTINATION bin)
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..0664320
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1 @@
+"GPLv2"
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..d089aad
--- /dev/null
+++ b/main.c
@@ -0,0 +1,8 @@
+#include 
+
+int main(int argc, char *argv[])
+{
+    printf("hello, cmake 2.0\n");
+    return 0;
+}
+
--
2.7.4


如果沒有特別指定版本的話,會使用最新的,可以透過設定PREFERRED_VERSION_package_name設定特定版本,如
brook@vista:~/oe-core$ grep -H PREFERRED_VERSION build/conf/local.conf
build/conf/local.conf:PREFERRED_VERSION_brook-cmake = "1.0"
相關資訊可以參考Bitbake User Manual/2.3. Preferences and Providers

    參考資料:
  1. CMake 入門




BitBake User Manual - CH3.3. Sharing Functionality


BitBake 有metadata sharing的機制,基本上分為兩類,include file(.inc)跟class file(.bbclass),你可以使用include或require來include .inc檔案,其行為就是會將.inc內容插入include那行的位置中。
include跟require相似,差異是,如果檔案不存在include不會error,但是requre就會,如:
include foo_no_exist.inc
require foo_need_exist.inc

你可以使用iherit來使用class file,使用這指令會去尋找對應的class來使用,如:
inherit autotools

如果每個recipes都需要inherit的話,可以在.conf使用INHERIT指令達成這目的,如:
INHERIT += sanity

example:meta/conf/sanity.conf
# Sanity checks for common user misconfigurations
#
# See sanity.bbclass
#
# Expert users can confirm their sanity with "touch conf/sanity.conf"
BB_MIN_VERSION = "1.27.1"

SANITY_ABIFILE = "${TMPDIR}/abi_version"

SANITY_VERSION ?= "1"
LOCALCONF_VERSION  ?= "1"
LAYER_CONF_VERSION ?= "6"
SITE_CONF_VERSION  ?= "1"

INHERIT += "sanity"


example:meta/classes/sanity.bbclass


    參考資料:
  1. BitBake User Manual




熱門文章