What is meant by kernel programming in linux

Thread Starter

microcontroller60

Joined Oct 1, 2019
43
I have been reading kernel programming in Linux. Linux is an operating system developed by a programming language. Raspberry Pi is a development board with ARM chip on which Linux operating runs As I know that if I want to interface a sensor with a Raspberry Pi board, then I write a c program for it that read the status of the sensor.

The kernel is the part of operating system Which is made from programming language So overall if I have to interface a device with Pi, then I have to write a program for it. I don't understand kernel programming anywhere when I'm interfacing a device with Pi

Can anyone help me What is meant by kernel programming in Linux when I am working on embedded project
 

MrChips

Joined Oct 2, 2009
23,965
You are confusing "kernel", "programming", and "programming language".

Linux is an operating system developed by a programming language.
Linux is an operating system. Yes.

...developed by a programming language.
Redundant. All computer systems are developed by a programming language,

All computer systems and portions of computer systems are developed by programming using a programming language.

The kernel is programmed using a programming language.
The kernel is the inner core of the operating system. Its function is to link the computer hardware to the application.

1580394645601.png

https://simple.wikipedia.org/wiki/Kernel_(computer_science)
 

402DF855

Joined Feb 9, 2013
271
I think in Linux drivers are referred to as modules. If I were going to write a Linux driver I'd probably look for a suitable example showing how to build, install, and interface a "module".
 

bertus

Joined Apr 5, 2008
21,454
Hello,

The the older kernels you had to compile the drivers against the used kernel.
In the newer kernels they use the kernel-modules.

Bertus
 

Thread Starter

microcontroller60

Joined Oct 1, 2019
43
You are confusing "kernel", "programming", and "programming language".
I know a simple process, if I want to build an embedded system, then I need related hardware. After that I have to choose an operating system on which I will write code for embedded system. It can be Windows or Linux suppose I have a Linux. After that I installed a suitable IDE on which to write the code. In this way I can program any microcontroller.

I built a complete embedded system but I never used kernel programming. If you are working on a project then what does kernel programming mean regarding project?
 

MrChips

Joined Oct 2, 2009
23,965
Are you still confused?
I build embedded systems as a profession. I do not use kernel.
What exactly is your question?
 
The NDT requires a kernel patch. See https://github.com/ndt-project/ndt/wiki/NDTArchitecture

I helped design a kernel patch for an DEC OS. It was a real-time time-sharing system. We wanted to add a feature called "scope rubouts". Typically, you had VT100 CRT terminals which were 24 rows x 80 columns. The OS did not know how to handle tabs and rubouts correctly. We patched the OS to do so. We had the complete source files to do so.

You had to "generate" and then install the OS based on questions.
 

nsaspook

Joined Aug 27, 2009
8,532
What is the importance of kernel programming in an embedded system? When kernel programming is required in an embedded system ?
When you need a kernel level capability (something a user level program can't provide in a protected mode system) not provided by the current OS.

For example the OS provides a SPI controller kernel driver to actually talk to external slave devices but you need protected hardware access to program a multi-processor near real time interface protocol driver that talks to SPI but provides the normal file-system API so any user program can use the SPI capability in a (mainly) transparent manner with file streams instead of peek/poking SPI bytes using the SPI kernel driver directly.
http://events17.linuxfoundation.org/sites/events/files/slides/Groking the Linux SPI Subsystem.pdf

Using MCP3008 ADCs with Raspberry Pi
https://jumpnowtek.com/rpi/Using-mcp3008-ADCs-with-Raspberry-Pis.html
 
Last edited:

MrChips

Joined Oct 2, 2009
23,965
What is the importance of kernel programming in an embedded system? When kernel programming is required in an embedded system ?
It is still not clear what you mean by "kernel programming".

Do you mean

A - creating your own kernel functions

or

B - using the existing kernel functions via the OS?
 

Thread Starter

microcontroller60

Joined Oct 1, 2019
43
I take your example I assume we have an analog sensor, MP3008 and raspberry Pi. Output of sensor goes to input of mcp3008 and output of mcp3008 goes to input of raspberry Pi

This is source code show the SPI communication http://shaunsbennett.com/piblog/?p=266
C:
*  readMcp3008.c:
*  read value from ADC MCP3008

***********************************************************************
*/
#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>

#include <wiringPi.h>
#include <wiringPiSPI.h>

#define    TRUE                (1==1)
#define    FALSE                (!TRUE)
#define CHAN_CONFIG_SINGLE  8
#define CHAN_CONFIG_DIFF    0

static int myFd ;

char *usage = "Usage: mcp3008 all|analogChannel[1-8] [-l] [-ce1] [-d]";
// -l   = load SPI driver,  default: do not load
// -ce1  = spi analogChannel 1, default:  0
// -d   = differential analogChannel input, default: single ended

void loadSpiDriver()
{
    if (system("gpio load spi") == -1)
    {
        fprintf (stderr, "Can't load the SPI driver: %s\n", strerror (errno)) ;
        exit (EXIT_FAILURE) ;
    }
}

void spiSetup (int spiChannel)
{
    if ((myFd = wiringPiSPISetup (spiChannel, 1000000)) < 0)
    {
        fprintf (stderr, "Can't open the SPI bus: %s\n", strerror (errno)) ;
        exit (EXIT_FAILURE) ;
    }
}

int myAnalogRead(int spiChannel,int channelConfig,int analogChannel)
{
    if(analogChannel<0 || analogChannel>7)
        return -1;
    unsigned char buffer[3] = {1}; // start bit
    buffer[1] = (channelConfig+analogChannel) << 4;
    wiringPiSPIDataRW(spiChannel, buffer, 3);
    return ( (buffer[1] & 3 ) << 8 ) + buffer[2]; // get last 10 bits
}

int main (int argc, char *argv [])
{
    int loadSpi=FALSE;
    int analogChannel=0;
    int spiChannel=0;
    int channelConfig=CHAN_CONFIG_SINGLE;
    if (argc < 2)
    {
        fprintf (stderr, "%s\n", usage) ;
        return 1 ;
    }
    if((strcasecmp (argv [1], "all") == 0) )
        argv[1] = "0";
    if ( (sscanf (argv[1], "%i", &analogChannel)!=1) || analogChannel < 0 || analogChannel > 8 )
    {
        printf ("%s\n",  usage) ;
        return 1 ;
    }
    int i;
    for(i=2; i<argc; i++)
    {
        if (strcasecmp (argv [i], "-l") == 0 || strcasecmp (argv [i], "-load") == 0)
            loadSpi=TRUE;
        else if (strcasecmp (argv [i], "-ce1") == 0)
            spiChannel=1;
        else if (strcasecmp (argv [i], "-d") == 0 || strcasecmp (argv [i], "-diff") == 0)
            channelConfig=CHAN_CONFIG_DIFF;
    }
    //
    if(loadSpi==TRUE)
        loadSpiDriver();
    wiringPiSetup () ;
    spiSetup(spiChannel);
    //
    if(analogChannel>0)
    {
        printf("MCP3008(CE%d,%s): analogChannel %d = %d\n",spiChannel,(channelConfig==CHAN_CONFIG_SINGLE)
               ?"single-ended":"differential",analogChannel,myAnalogRead(spiChannel,channelConfig,analogChannel-1));
    }
    else
    {
        for(i=0; i<8; i++)
        {
            printf("MCP3008(CE%d,%s): analogChannel %d = %d\n",spiChannel,(channelConfig==CHAN_CONFIG_SINGLE)
                   ?"single-ended":"differential",i+1,myAnalogRead(spiChannel,channelConfig,i));
        }
    }
    close (myFd) ;
    return 0;
}
I did not understand where kernel programming in the whole code.
 

nsaspook

Joined Aug 27, 2009
8,532
I take your example I assume we have an analog sensor, MP3008 and raspberry Pi. Output of sensor goes to input of mcp3008 and output of mcp3008 goes to input of raspberry Pi
...
I did not understand where kernel programming in the whole code.
That shows a good level of understanding because there is NO kernel programming in the whole code. You're just peek/poking the SPI port directly using the kernel SPI controller device driver (with a WiringPi wrapper) at the user program level.

This is a kernel driver that allows you to access the MCP3008 using the file system API.
C:
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013 Oskar Andero <oskar.andero@gmail.com>
* Copyright (C) 2014 Rose Technology
*        Allan Bendorff Jensen <abj@rosetechnology.dk>
*       Soren Andersen <san@rosetechnology.dk>
*
* Driver for following ADC chips from Microchip Technology's:
* 10 Bit converter
* MCP3001
* MCP3002
* MCP3004
* MCP3008
* ------------
* 12 bit converter
* MCP3201
* MCP3202
* MCP3204
* MCP3208
* ------------
* 13 bit converter
* MCP3301
* ------------
* 22 bit converter
* MCP3550
* MCP3551
* MCP3553
*
* Datasheet can be found here:
* http://ww1.microchip.com/downloads/en/DeviceDoc/21293C.pdf  mcp3001
* http://ww1.microchip.com/downloads/en/DeviceDoc/21294E.pdf  mcp3002
* http://ww1.microchip.com/downloads/en/DeviceDoc/21295d.pdf  mcp3004/08
* http://ww1.microchip.com/downloads/en/DeviceDoc/21290D.pdf  mcp3201
* http://ww1.microchip.com/downloads/en/DeviceDoc/21034D.pdf  mcp3202
* http://ww1.microchip.com/downloads/en/DeviceDoc/21298c.pdf  mcp3204/08
* http://ww1.microchip.com/downloads/en/DeviceDoc/21700E.pdf  mcp3301
* http://ww1.microchip.com/downloads/en/DeviceDoc/21950D.pdf  mcp3550/1/3
*/

#include <linux/err.h>
#include <linux/delay.h>
#include <linux/spi/spi.h>
#include <linux/module.h>
#include <linux/iio/iio.h>
#include <linux/regulator/consumer.h>

enum {
    mcp3001,
    mcp3002,
    mcp3004,
    mcp3008,
    mcp3201,
    mcp3202,
    mcp3204,
    mcp3208,
    mcp3301,
    mcp3550_50,
    mcp3550_60,
    mcp3551,
    mcp3553,
};

struct mcp320x_chip_info {
    const struct iio_chan_spec *channels;
    unsigned int num_channels;
    unsigned int resolution;
    unsigned int conv_time; /* usec */
};

/**
* struct mcp320x - Microchip SPI ADC instance
* @spi: SPI slave (parent of the IIO device)
* @msg: SPI message to select a channel and receive a value from the ADC
* @transfer: SPI transfers used by @msg
* @start_conv_msg: SPI message to start a conversion by briefly asserting CS
* @start_conv_transfer: SPI transfer used by @start_conv_msg
* @reg: regulator generating Vref
* @lock: protects read sequences
* @chip_info: ADC properties
* @tx_buf: buffer for @transfer[0] (not used on single-channel converters)
* @rx_buf: buffer for @transfer[1]
*/
struct mcp320x {
    struct spi_device *spi;
    struct spi_message msg;
    struct spi_transfer transfer[2];
    struct spi_message start_conv_msg;
    struct spi_transfer start_conv_transfer;

    struct regulator *reg;
    struct mutex lock;
    const struct mcp320x_chip_info *chip_info;

    u8 tx_buf ____cacheline_aligned;
    u8 rx_buf[4];
};

static int mcp320x_channel_to_tx_data(int device_index,
            const unsigned int channel, bool differential)
{
    int start_bit = 1;

    switch (device_index) {
    case mcp3002:
    case mcp3202:
        return ((start_bit << 4) | (!differential << 3) |
                            (channel << 2));
    case mcp3004:
    case mcp3204:
    case mcp3008:
    case mcp3208:
        return ((start_bit << 6) | (!differential << 5) |
                            (channel << 2));
    default:
        return -EINVAL;
    }
}

static int mcp320x_adc_conversion(struct mcp320x *adc, u8 channel,
                  bool differential, int device_index, int *val)
{
    int ret;

    if (adc->chip_info->conv_time) {
        ret = spi_sync(adc->spi, &adc->start_conv_msg);
        if (ret < 0)
            return ret;

        usleep_range(adc->chip_info->conv_time,
                 adc->chip_info->conv_time + 100);
    }

    memset(&adc->rx_buf, 0, sizeof(adc->rx_buf));
    if (adc->chip_info->num_channels > 1)
        adc->tx_buf = mcp320x_channel_to_tx_data(device_index, channel,
                             differential);

    ret = spi_sync(adc->spi, &adc->msg);
    if (ret < 0)
        return ret;

    switch (device_index) {
    case mcp3001:
        *val = (adc->rx_buf[0] << 5 | adc->rx_buf[1] >> 3);
        return 0;
    case mcp3002:
    case mcp3004:
    case mcp3008:
        *val = (adc->rx_buf[0] << 2 | adc->rx_buf[1] >> 6);
        return 0;
    case mcp3201:
        *val = (adc->rx_buf[0] << 7 | adc->rx_buf[1] >> 1);
        return 0;
    case mcp3202:
    case mcp3204:
    case mcp3208:
        *val = (adc->rx_buf[0] << 4 | adc->rx_buf[1] >> 4);
        return 0;
    case mcp3301:
        *val = sign_extend32((adc->rx_buf[0] & 0x1f) << 8
                    | adc->rx_buf[1], 12);
        return 0;
    case mcp3550_50:
    case mcp3550_60:
    case mcp3551:
    case mcp3553: {
        u32 raw = be32_to_cpup((__be32 *)adc->rx_buf);

        if (!(adc->spi->mode & SPI_CPOL))
            raw <<= 1; /* strip Data Ready bit in SPI mode 0,0 */

        /*
         * If the input is within -vref and vref, bit 21 is the sign.
         * Up to 12% overrange or underrange are allowed, in which case
         * bit 23 is the sign and bit 0 to 21 is the value.
         */
        raw >>= 8;
        if (raw & BIT(22) && raw & BIT(23))
            return -EIO; /* cannot have overrange AND underrange */
        else if (raw & BIT(22))
            raw &= ~BIT(22); /* overrange */
        else if (raw & BIT(23) || raw & BIT(21))
            raw |= GENMASK(31, 22); /* underrange or negative */

        *val = (s32)raw;
        return 0;
        }
    default:
        return -EINVAL;
    }
}

static int mcp320x_read_raw(struct iio_dev *indio_dev,
                struct iio_chan_spec const *channel, int *val,
                int *val2, long mask)
{
    struct mcp320x *adc = iio_priv(indio_dev);
    int ret = -EINVAL;
    int device_index = 0;

    mutex_lock(&adc->lock);

    device_index = spi_get_device_id(adc->spi)->driver_data;

    switch (mask) {
    case IIO_CHAN_INFO_RAW:
        ret = mcp320x_adc_conversion(adc, channel->address,
            channel->differential, device_index, val);
        if (ret < 0)
            goto out;

        ret = IIO_VAL_INT;
        break;

    case IIO_CHAN_INFO_SCALE:
        ret = regulator_get_voltage(adc->reg);
        if (ret < 0)
            goto out;

        /* convert regulator output voltage to mV */
        *val = ret / 1000;
        *val2 = adc->chip_info->resolution;
        ret = IIO_VAL_FRACTIONAL_LOG2;
        break;
    }

out:
    mutex_unlock(&adc->lock);

    return ret;
}

#define MCP320X_VOLTAGE_CHANNEL(num)                \
    {                            \
        .type = IIO_VOLTAGE,                \
        .indexed = 1,                    \
        .channel = (num),                \
        .address = (num),                \
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),    \
        .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) \
    }

#define MCP320X_VOLTAGE_CHANNEL_DIFF(chan1, chan2)        \
    {                            \
        .type = IIO_VOLTAGE,                \
        .indexed = 1,                    \
        .channel = (chan1),                \
        .channel2 = (chan2),                \
        .address = (chan1),                \
        .differential = 1,                \
        .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),    \
        .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) \
    }

static const struct iio_chan_spec mcp3201_channels[] = {
    MCP320X_VOLTAGE_CHANNEL_DIFF(0, 1),
};

static const struct iio_chan_spec mcp3202_channels[] = {
    MCP320X_VOLTAGE_CHANNEL(0),
    MCP320X_VOLTAGE_CHANNEL(1),
    MCP320X_VOLTAGE_CHANNEL_DIFF(0, 1),
    MCP320X_VOLTAGE_CHANNEL_DIFF(1, 0),
};

static const struct iio_chan_spec mcp3204_channels[] = {
    MCP320X_VOLTAGE_CHANNEL(0),
    MCP320X_VOLTAGE_CHANNEL(1),
    MCP320X_VOLTAGE_CHANNEL(2),
    MCP320X_VOLTAGE_CHANNEL(3),
    MCP320X_VOLTAGE_CHANNEL_DIFF(0, 1),
    MCP320X_VOLTAGE_CHANNEL_DIFF(1, 0),
    MCP320X_VOLTAGE_CHANNEL_DIFF(2, 3),
    MCP320X_VOLTAGE_CHANNEL_DIFF(3, 2),
};

static const struct iio_chan_spec mcp3208_channels[] = {
    MCP320X_VOLTAGE_CHANNEL(0),
    MCP320X_VOLTAGE_CHANNEL(1),
    MCP320X_VOLTAGE_CHANNEL(2),
    MCP320X_VOLTAGE_CHANNEL(3),
    MCP320X_VOLTAGE_CHANNEL(4),
    MCP320X_VOLTAGE_CHANNEL(5),
    MCP320X_VOLTAGE_CHANNEL(6),
    MCP320X_VOLTAGE_CHANNEL(7),
    MCP320X_VOLTAGE_CHANNEL_DIFF(0, 1),
    MCP320X_VOLTAGE_CHANNEL_DIFF(1, 0),
    MCP320X_VOLTAGE_CHANNEL_DIFF(2, 3),
    MCP320X_VOLTAGE_CHANNEL_DIFF(3, 2),
    MCP320X_VOLTAGE_CHANNEL_DIFF(4, 5),
    MCP320X_VOLTAGE_CHANNEL_DIFF(5, 4),
    MCP320X_VOLTAGE_CHANNEL_DIFF(6, 7),
    MCP320X_VOLTAGE_CHANNEL_DIFF(7, 6),
};

static const struct iio_info mcp320x_info = {
    .read_raw = mcp320x_read_raw,
};

static const struct mcp320x_chip_info mcp320x_chip_infos[] = {
    [mcp3001] = {
        .channels = mcp3201_channels,
        .num_channels = ARRAY_SIZE(mcp3201_channels),
        .resolution = 10
    },
    [mcp3002] = {
        .channels = mcp3202_channels,
        .num_channels = ARRAY_SIZE(mcp3202_channels),
        .resolution = 10
    },
    [mcp3004] = {
        .channels = mcp3204_channels,
        .num_channels = ARRAY_SIZE(mcp3204_channels),
        .resolution = 10
    },
    [mcp3008] = {
        .channels = mcp3208_channels,
        .num_channels = ARRAY_SIZE(mcp3208_channels),
        .resolution = 10
    },
    [mcp3201] = {
        .channels = mcp3201_channels,
        .num_channels = ARRAY_SIZE(mcp3201_channels),
        .resolution = 12
    },
    [mcp3202] = {
        .channels = mcp3202_channels,
        .num_channels = ARRAY_SIZE(mcp3202_channels),
        .resolution = 12
    },
    [mcp3204] = {
        .channels = mcp3204_channels,
        .num_channels = ARRAY_SIZE(mcp3204_channels),
        .resolution = 12
    },
    [mcp3208] = {
        .channels = mcp3208_channels,
        .num_channels = ARRAY_SIZE(mcp3208_channels),
        .resolution = 12
    },
    [mcp3301] = {
        .channels = mcp3201_channels,
        .num_channels = ARRAY_SIZE(mcp3201_channels),
        .resolution = 13
    },
    [mcp3550_50] = {
        .channels = mcp3201_channels,
        .num_channels = ARRAY_SIZE(mcp3201_channels),
        .resolution = 21,
        /* 2% max deviation + 144 clock periods to exit shutdown */
        .conv_time = 80000 * 1.02 + 144000 / 102.4,
    },
    [mcp3550_60] = {
        .channels = mcp3201_channels,
        .num_channels = ARRAY_SIZE(mcp3201_channels),
        .resolution = 21,
        .conv_time = 66670 * 1.02 + 144000 / 122.88,
    },
    [mcp3551] = {
        .channels = mcp3201_channels,
        .num_channels = ARRAY_SIZE(mcp3201_channels),
        .resolution = 21,
        .conv_time = 73100 * 1.02 + 144000 / 112.64,
    },
    [mcp3553] = {
        .channels = mcp3201_channels,
        .num_channels = ARRAY_SIZE(mcp3201_channels),
        .resolution = 21,
        .conv_time = 16670 * 1.02 + 144000 / 122.88,
    },
};

static int mcp320x_probe(struct spi_device *spi)
{
    struct iio_dev *indio_dev;
    struct mcp320x *adc;
    const struct mcp320x_chip_info *chip_info;
    int ret, device_index;

    indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*adc));
    if (!indio_dev)
        return -ENOMEM;

    adc = iio_priv(indio_dev);
    adc->spi = spi;

    indio_dev->dev.parent = &spi->dev;
    indio_dev->dev.of_node = spi->dev.of_node;
    indio_dev->name = spi_get_device_id(spi)->name;
    indio_dev->modes = INDIO_DIRECT_MODE;
    indio_dev->info = &mcp320x_info;
    spi_set_drvdata(spi, indio_dev);

    device_index = spi_get_device_id(spi)->driver_data;
    chip_info = &mcp320x_chip_infos[device_index];
    indio_dev->channels = chip_info->channels;
    indio_dev->num_channels = chip_info->num_channels;

    adc->chip_info = chip_info;

    adc->transfer[0].tx_buf = &adc->tx_buf;
    adc->transfer[0].len = sizeof(adc->tx_buf);
    adc->transfer[1].rx_buf = adc->rx_buf;
    adc->transfer[1].len = DIV_ROUND_UP(chip_info->resolution, 8);

    if (chip_info->num_channels == 1)
        /* single-channel converters are rx only (no MOSI pin) */
        spi_message_init_with_transfers(&adc->msg,
                        &adc->transfer[1], 1);
    else
        spi_message_init_with_transfers(&adc->msg, adc->transfer,
                        ARRAY_SIZE(adc->transfer));

    switch (device_index) {
    case mcp3550_50:
    case mcp3550_60:
    case mcp3551:
    case mcp3553:
        /* rx len increases from 24 to 25 bit in SPI mode 0,0 */
        if (!(spi->mode & SPI_CPOL))
            adc->transfer[1].len++;

        /* conversions are started by asserting CS pin for 8 usec */
        adc->start_conv_transfer.delay_usecs = 8;
        spi_message_init_with_transfers(&adc->start_conv_msg,
                        &adc->start_conv_transfer, 1);

        /*
         * If CS was previously kept low (continuous conversion mode)
         * and then changed to high, the chip is in shutdown.
         * Sometimes it fails to wake from shutdown and clocks out
         * only 0xffffff.  The magic sequence of performing two
         * conversions without delay between them resets the chip
         * and ensures all subsequent conversions succeed.
         */
        mcp320x_adc_conversion(adc, 0, 1, device_index, &ret);
        mcp320x_adc_conversion(adc, 0, 1, device_index, &ret);
    }

    adc->reg = devm_regulator_get(&spi->dev, "vref");
    if (IS_ERR(adc->reg))
        return PTR_ERR(adc->reg);

    ret = regulator_enable(adc->reg);
    if (ret < 0)
        return ret;

    mutex_init(&adc->lock);

    ret = iio_device_register(indio_dev);
    if (ret < 0)
        goto reg_disable;

    return 0;

reg_disable:
    regulator_disable(adc->reg);

    return ret;
}

static int mcp320x_remove(struct spi_device *spi)
{
    struct iio_dev *indio_dev = spi_get_drvdata(spi);
    struct mcp320x *adc = iio_priv(indio_dev);

    iio_device_unregister(indio_dev);
    regulator_disable(adc->reg);

    return 0;
}

#if defined(CONFIG_OF)
static const struct of_device_id mcp320x_dt_ids[] = {
    /* NOTE: The use of compatibles with no vendor prefix is deprecated. */
    { .compatible = "mcp3001" },
    { .compatible = "mcp3002" },
    { .compatible = "mcp3004" },
    { .compatible = "mcp3008" },
    { .compatible = "mcp3201" },
    { .compatible = "mcp3202" },
    { .compatible = "mcp3204" },
    { .compatible = "mcp3208" },
    { .compatible = "mcp3301" },
    { .compatible = "microchip,mcp3001" },
    { .compatible = "microchip,mcp3002" },
    { .compatible = "microchip,mcp3004" },
    { .compatible = "microchip,mcp3008" },
    { .compatible = "microchip,mcp3201" },
    { .compatible = "microchip,mcp3202" },
    { .compatible = "microchip,mcp3204" },
    { .compatible = "microchip,mcp3208" },
    { .compatible = "microchip,mcp3301" },
    { .compatible = "microchip,mcp3550-50" },
    { .compatible = "microchip,mcp3550-60" },
    { .compatible = "microchip,mcp3551" },
    { .compatible = "microchip,mcp3553" },
    { }
};
MODULE_DEVICE_TABLE(of, mcp320x_dt_ids);
#endif

static const struct spi_device_id mcp320x_id[] = {
    { "mcp3001", mcp3001 },
    { "mcp3002", mcp3002 },
    { "mcp3004", mcp3004 },
    { "mcp3008", mcp3008 },
    { "mcp3201", mcp3201 },
    { "mcp3202", mcp3202 },
    { "mcp3204", mcp3204 },
    { "mcp3208", mcp3208 },
    { "mcp3301", mcp3301 },
    { "mcp3550-50", mcp3550_50 },
    { "mcp3550-60", mcp3550_60 },
    { "mcp3551", mcp3551 },
    { "mcp3553", mcp3553 },
    { }
};
MODULE_DEVICE_TABLE(spi, mcp320x_id);

static struct spi_driver mcp320x_driver = {
    .driver = {
        .name = "mcp320x",
        .of_match_table = of_match_ptr(mcp320x_dt_ids),
    },
    .probe = mcp320x_probe,
    .remove = mcp320x_remove,
    .id_table = mcp320x_id,
};
module_spi_driver(mcp320x_driver);

MODULE_AUTHOR("Oskar Andero <oskar.andero@gmail.com>");
MODULE_DESCRIPTION("Microchip Technology MCP3x01/02/04/08 and MCP3550/1/3");
MODULE_LICENSE("GPL v2");
Kernel programming is in the whole code here.


A user level program using that kernel protocol driver to provide file-stream API access (via char iiosyspath[] = "/sys/bus/iio/devices/iio:device0/";) to the SPI device in a portable, (mainly) hardware independent manner.
C:
/*
   Copyright (C) 2017, Jumpnow Technologies, LLC

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <time.h>
#include <sys/time.h>
#include <getopt.h>
#include <errno.h>

#define ADC_READ_ERROR -100000

#define MAX_ADC 8

char iiosyspath[] = "/sys/bus/iio/devices/iio:device0/";

void register_sig_handler();
void sigint_handler(int sig);
void show_elapsed(struct timeval *start, struct timeval *end, int count);
int loop(int delay_us, int *list);
int open_adc(int adc);
int read_adc(int fd);

int abort_read;

void usage(char *argv_0)
{
    printf("\nUsage: %s <options> [adc-list]\n", argv_0);
    printf("  -d<delay-us>       Microsecond delay between reads, default 10000, min 0\n");
    printf("  adc-list           Space separated list of ADCs to monitor, 0-7\n");
    printf("\nExample:\n\t%s -d100 0 1\n", argv_0);

    exit(0);
}

int main(int argc, char **argv)
{
    int opt, delay_us, adc, i;
    struct timeval start, end;
    int adc_list[MAX_ADC];

    register_sig_handler();

    delay_us = 10000;

    while ((opt = getopt(argc, argv, "d:h")) != -1) {
        switch (opt) {
        case 'd':
            delay_us = atoi(optarg);

            if (delay_us < 0)
                delay_us = 0;

            break;

        case 'h':
        default:
            usage(argv[0]);
            break;
        }
    }

    // now get the adc list
    if (optind == argc) {
        printf("List of ADCs is required\n");
        usage(argv[0]);
    }

    memset(adc_list, 0, sizeof(adc_list));

    for (i = optind; i < argc; i++) {
        adc = atoi(argv[i]);

        if (adc < 0 || adc > 7) {
            printf("adc %d is out of range\n", adc);
            usage(argv[0]);
        }

        if (adc_list[adc]) {
            printf("adc %d listed more then once\n", adc + 2);
            usage(argv[0]);
        }

        adc_list[adc] = 1;
    }

    if (gettimeofday(&start, NULL) < 0) {
        perror("gettimeofday: start");
        return 1;
    }

    int count = loop(delay_us, adc_list);

    if (gettimeofday(&end, NULL) < 0)
        perror("gettimeofday: end");
    else
        show_elapsed(&start, &end, count);

    return 0;
}

// We know the diff is never going to be that big
void show_elapsed(struct timeval *start, struct timeval *end, int count)
{
    double diff;
    double rate;

    if (end->tv_usec > start->tv_usec) {
        diff = (double) (end->tv_usec - start->tv_usec);
    }
    else {
        diff = (double) ((1000000 + end->tv_usec) - start->tv_usec);
        end->tv_sec--;
    }

    diff /= 1000000.0;

    diff += (double)(end->tv_sec - start->tv_sec);

    if (diff > 0.0)
        rate = count / diff;
    else
        rate = 0.0;

    printf("Summary\n  Elapsed: %0.2lf seconds\n    Reads: %d\n     Rate: %0.2lf Hz\n\n",
        diff, count, rate);
}

int loop(int delay_us, int *list)
{
    int count, i, update, update_reset;
    int val[MAX_ADC];
    int fd[MAX_ADC];

    count = 0;
    memset(fd, 0, sizeof(fd));
    memset(val, 0, sizeof(val));

    // update_reset is just a quick hack to throttle screen updates
    if (delay_us < 1) {
        // with no usleep events, max sampling rate is around 20k Hz
        update_reset = 2000;
    }
    else {
        // with usleep, we top out around 8k Hz
        update_reset = 800000 / delay_us;

        if (update_reset > 800)
            update_reset = 800;
        else if (update_reset < 1)
            update_reset = 1;
    }

    fprintf(stdout, "\n(use ctrl-c to stop)\n\n");

    fprintf(stdout, "ADC          ");

    for (i = 0; i < MAX_ADC; i++) {
        if (list[i])
            fprintf(stdout, "      %d", i);
    }

    fprintf(stdout, "\n");

    for (i = 0; i < MAX_ADC; i++) {
        if (list[i]) {
            fd[i] = open_adc(i);

            if (fd[i] < 0)
                goto loop_done;
        }
    }

    // update first read
    update = 1;

    while (!abort_read) {
        for (i = 0; i < MAX_ADC; i++) {
            if (!list[i])
                continue;

            val[i] = read_adc(fd[i]);

            // reset for next read
            lseek(fd[i], 0, SEEK_SET);

            if (val[i] == ADC_READ_ERROR)
                break;
        }

        update--;

        if (update == 0) {
            update = update_reset;

            fprintf(stdout, "\rRead %8d: ", count + 1);

            for (i = 0; i < MAX_ADC; i++) {
                if (list[i])
                    fprintf(stdout, " %4d  ", val[i]);
            }

            fflush(stdout);
        }

        count++;

        if (delay_us)
            usleep(delay_us);
    }

    fprintf(stdout, "\n\n");

loop_done:

    for (i = 0; i < MAX_ADC; i++) {
        if (fd[i] > 0)
            close(fd[i]);
    }

    return count;
}

int read_adc(int fd)
{
    char buff[8];

    int val = ADC_READ_ERROR;

    memset(buff, 0, sizeof(buff));

    if (read(fd, buff, 8) < 0)
        perror("read()");
    else
        val = atoi(buff);

    return val;
}

int open_adc(int adc)
{
    char path[128];
    sprintf(path, "%sin_voltage%d_raw", iiosyspath, adc);

    int fd = open(path, O_RDONLY);

    if (fd < 0) {
        perror("open()");
        printf("%s\n", path);
    }

    return fd;
}

void register_sig_handler()
{
    struct sigaction sia;

    bzero(&sia, sizeof sia);
    sia.sa_handler = sigint_handler;

    if (sigaction(SIGINT, &sia, NULL) < 0) {
        perror("sigaction(SIGINT)");
        exit(1);
    }
}

void sigint_handler(int sig)
{
    abort_read = 1;
}
 

ApacheKid

Joined Jan 12, 2015
403
The term "kernel programming" is multi faceted. The term "kernel" (sometimes the term "nucleus" is used but rarely these days) refers to software components that execute when the CPU is in "privileged" mode. In that mode the CPU can execute certain special instructions and uses a special stack and access physical memory and modify special registers and so on. These are operations that are simply impossible when the CPU is not in privileged mode.

So the "kernel" refers to those bits of software that run in that mode and manipulate important system data and registers, this Is done to provide management services in a reliable way, ordinary code cannot interfere with this, if it tries it gets stopped.

"kernel programming" then refers to development with an emphasis on this area which includes interrupt handling, concurrency, deferred processing, system memory management etc.

This is what it means in an OS like Linux or Windows anyway, the term is and originated in operating system theory.
 

BobaMosfet

Joined Jul 1, 2009
1,832
I know a simple process, if I want to build an embedded system, then I need related hardware. After that I have to choose an operating system on which I will write code for embedded system. It can be Windows or Linux suppose I have a Linux. After that I installed a suitable IDE on which to write the code. In this way I can program any microcontroller.

I built a complete embedded system but I never used kernel programming. If you are working on a project then what does kernel programming mean regarding project?
In the context of 'nix systems, 'kernel' _IS_ the O/S. The core. An operating system serves one, and just one purpose: Manage resources. Those resources are: Time, Memory, and I/O. The 'kernel' is composed of internal sections- memory management, time-keeping, bus-management, event-handling, device management, and all the logic for managing the queues, tables, lists, trees, memory structures and other things necessary to regulate, inventory, track, and make available various resources to applications, drivers, and devices.

If you want a 'kernel on an embedded system, you can either utilize one prebuilt, or do what I did- write your own.

But, based on your questioning, kernel programming- that is, writing code for a kernel, is way beyond your skill set. Keep learning, and perhaps one day you'll be able to do it. Me, I've written O/S's (kernels), drivers, and made my own Devices and made drivers for them. I've got my own RTOS that is about ~7K in size and runs on 8-bit MCUs, giving me full capability to write applications to do many amazing, wonderful and powerful things because of the ease in which the kernel manages resources and makes things possible that the application no longer has to do itself.
 
A co-worker and I wrote a program that took us 9 months to write. It used RT-11 as a real-Time OS. It was written in FORTRAN.
I forget what we needed from the OS, but the OS provided one instruction that we needed called: Move from previous data space. I woud not call that kernel Programming. So, the entire real-time program was written in Fortran with "overlays" and I had to manage the overlays so that everything that was needed was available.

"Disk overlays" are not talked about much anymore, but these could be set up, so that the contents of a particular routine as it exists in memory be copied to disk and returned to memory. Not as elegant as global variables.

Creating a device driver that communicates with userspace is Kernel programming.

I did write an OS (A two person team for class) that simulated an instruction set and simulated a card reader and simulated a line printer.

I (instruction) and d(data space) should be separate for the user and the OS. Code should not be self-modifying either.
 
Top