use std::io::{stdin, Read, StdinLock};
use std::str::FromStr;
use std::cmp::max;
use std::collections::VecDeque;

#[allow(unused_macros)]
macro_rules! vecs {
    ($init:expr; $($dim:expr);*) => {
        vecs!($($dim);* => $init)
    };
    ($head:expr; $($tail:expr);* => $init:expr) => {
        vec![vecs!($($tail);* => $init); $head]
    };
    ($head:expr => $init:expr) => {
        vec![$init; $head]
    };
}

fn main() {
    let cin = stdin();
    let mut scan = Scanner::new(cin.lock());
    let h = scan.read::<usize>();
    let w = scan.read::<usize>();
    let mut s = vec![];
    for _ in 0..h {
        s.push(scan.chars());
    }
    let mut ret = 0;
    for y in 0..h {
        for x in 0..w {
            if s[y][x] == '.' {
                ret = max(ret, bfs(&s, h, w, y, x));
            }
        }
    }
    println!("{}", ret);
}
fn bfs(s: &Vec<Vec<char>>, h: usize, w: usize, sy: usize, sx: usize) -> i32 {
    let mut maxlen = 0;
    let mut d = vecs!(None;h;w);
    let mut que = VecDeque::new();
    que.push_front((sy, sx, 0));
    while let Some((y, x, k)) = que.pop_front() {
        maxlen = max(maxlen, k);
        for (ny, nx) in adjacent4(y, x, h, w) {
            if d[ny][nx].is_none() && s[ny][nx] == '.' {
                d[ny][nx] = Some(k + 1);
                que.push_back((ny, nx, k + 1));
            }
        }
    }
    maxlen
}

/// https://github.com/hatoo/competitive-rust-snippets
#[allow(dead_code)]
fn adjacent4(y: usize, x: usize, h: usize, w: usize) -> Box<Iterator<Item = (usize, usize)>> {
    // This code looks verbose but is needed to Rust 1.15.
    static DYDX: [(isize, isize); 4] = [(-1, 0), (1, 0), (0, -1), (0, 1)];

    Box::new(DYDX.iter().filter_map(move |&(dy, dx)| {
        let ny = y as isize + dy;
        let nx = x as isize + dx;
        if nx >= 0 && nx < w as isize && ny >= 0 && ny < h as isize {
            Some((ny as usize, nx as usize))
        } else {
            None
        }
    }))
}

// region template
#[allow(dead_code)]
fn iu(i: i64) -> usize { i as usize }

#[allow(dead_code)]
fn ui(i: usize) -> i64 { i as i64 }

#[allow(dead_code, deprecated)]
fn join<T: std::fmt::Display>(slice: &[T], sep: &str) -> String {
    let strings = slice.iter().map(|t| format!("{}", t)).collect::<Vec<_>>();
    strings.connect(sep)
}

#[allow(dead_code)]
fn arr<'a, S, T>(n: usize, mut f: S) -> Vec<T> where S: FnMut(usize) -> T + 'a {
    (0..n).map(|i| f(i)).collect::<Vec<T>>()
}

struct Scanner<'a> {
    cin: StdinLock<'a>,
}

#[allow(dead_code)]
impl<'a> Scanner<'a> {
    fn new(cin: StdinLock<'a>) -> Scanner<'a> {
        Scanner { cin: cin }
    }

    fn read1<T: FromStr>(&mut self) -> Option<T> {
        let token = self.cin.by_ref().bytes().map(|c| c.unwrap() as char)
            .skip_while(|c| c.is_whitespace())
            .take_while(|c| !c.is_whitespace())
            .collect::<String>();
        token.parse::<T>().ok()
    }

    fn read<T: FromStr>(&mut self) -> T {
        self.read1().unwrap()
    }

    fn readn<T: FromStr>(&mut self, n: usize) -> Vec<T> {
        (0..n).map(|_| self.read::<T>()).collect()
    }

    fn chars(&mut self) -> Vec<char> {
        self.read::<String>().chars().collect()
    }
}
// endregion